+From 2f55fec323730a94ed49d401d93b913d85e43b65 Mon Sep 17 00:00:00 2001
+From: Nikos Mavrogiannopoulos <nmav@gnutls.org>
+Date: Mon, 1 Dec 2014 20:10:06 +0100
+Subject: [PATCH 1/2] Re-resolve when reconnecting CSTP and the X-CSTP-DynDNS
+ is set by the server
+
+That is, when reconnecting CSTP due to peer tearing the connection
+down attempt to re-resolve its IP. That handles the case where
+the server is using dynamic DNS and is advertising it.
+
+[dwmw2: refactored to simplify it somewhat]
+
+Signed-off-by: Nikos Mavrogiannopoulos <nmav@gnutls.org>
+Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
+---
+ cstp.c | 3 +++
+ openconnect-internal.h | 1 +
+ ssl.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++-
+ 4 files changed, 52 insertions(+), 2 deletions(-)
+
diff --git a/cstp.c b/cstp.c
-index b1235ef..f955b82 100644
+index 3b93538..55225f4 100644
--- a/cstp.c
+++ b/cstp.c
-@@ -570,7 +570,10 @@ int openconnect_make_cstp_connection(struct openconnect_info *vpninfo)
- return ret;
- }
-
--static int cstp_reconnect(struct openconnect_info *vpninfo)
-+/* When dead peer is set, this function will re-attempt resolving
-+ * the peer in case its IP changed.
-+ */
-+static int cstp_reconnect(struct openconnect_info *vpninfo, unsigned dead_peer)
- {
- int ret;
- int timeout;
-@@ -591,6 +594,16 @@ static int cstp_reconnect(struct openconnect_info *vpninfo)
- timeout = vpninfo->reconnect_timeout;
- interval = vpninfo->reconnect_interval;
-
-+ /* handle cases with dynamic DNS by forcing a new resolve.
-+ * The original IP is saved to retry as fallback if resolving
-+ * fails.
-+ */
-+ if (dead_peer && vpninfo->first_peer_addr == NULL) {
-+ vpninfo->first_peer_addr = vpninfo->peer_addr;
-+ vpninfo->first_peer_addrlen = vpninfo->peer_addrlen;
-+ vpninfo->peer_addr = NULL;
-+ }
-+
- while ((ret = openconnect_make_cstp_connection(vpninfo))) {
- if (timeout <= 0)
- return ret;
-@@ -611,6 +624,11 @@ static int cstp_reconnect(struct openconnect_info *vpninfo)
- interval += vpninfo->reconnect_interval;
- if (interval > RECONNECT_INTERVAL_MAX)
- interval = RECONNECT_INTERVAL_MAX;
-+
-+ if (dead_peer && vpninfo->first_peer_addr != NULL) {
-+ free(vpninfo->peer_addr);
-+ vpninfo->peer_addr = NULL;
-+ }
- }
- script_config_tun(vpninfo, "reconnect");
- return 0;
-@@ -903,8 +921,15 @@ int cstp_mainloop(struct openconnect_info *vpninfo, int *timeout)
- /* Not that this will ever happen; we don't even process
- the setting when we're asked for it. */
- vpn_progress(vpninfo, PRG_INFO, _("CSTP rekey due\n"));
-- if (vpninfo->ssl_times.rekey_method == REKEY_TUNNEL)
-- goto do_reconnect;
-+ if (vpninfo->ssl_times.rekey_method == REKEY_TUNNEL) {
-+ ret = cstp_reconnect(vpninfo, 0);
-+ if (ret) {
-+ vpn_progress(vpninfo, PRG_ERR, _("Reconnect failed\n"));
-+ vpninfo->quit_reason = "CSTP reconnect failed";
-+ return ret;
-+ }
-+ goto do_dtls_reconnect;
-+ }
- else if (vpninfo->ssl_times.rekey_method == REKEY_SSL) {
- ret = cstp_handshake(vpninfo, 0);
- if (ret) {
-@@ -922,7 +947,7 @@ int cstp_mainloop(struct openconnect_info *vpninfo, int *timeout)
- vpn_progress(vpninfo, PRG_ERR,
- _("CSTP Dead Peer Detection detected dead peer!\n"));
- do_reconnect:
-- ret = cstp_reconnect(vpninfo);
-+ ret = cstp_reconnect(vpninfo, 1);
- if (ret) {
- vpn_progress(vpninfo, PRG_ERR, _("Reconnect failed\n"));
- vpninfo->quit_reason = "CSTP reconnect failed";
-diff --git a/library.c b/library.c
-index f5d3dc9..7c8d5ec 100644
---- a/library.c
-+++ b/library.c
-@@ -178,6 +178,7 @@ void openconnect_vpninfo_free(struct openconnect_info *vpninfo)
- CloseHandle(vpninfo->dtls_event);
- #endif
- free(vpninfo->peer_addr);
-+ free(vpninfo->first_peer_addr);
- free_optlist(vpninfo->csd_env);
- free_optlist(vpninfo->script_env);
- free_optlist(vpninfo->cookies);
-@@ -291,6 +292,8 @@ int openconnect_set_hostname(struct openconnect_info *vpninfo,
- vpninfo->unique_hostname = NULL;
- free(vpninfo->peer_addr);
- vpninfo->peer_addr = NULL;
-+ free(vpninfo->first_peer_addr);
-+ vpninfo->first_peer_addr = NULL;
-
- return 0;
- }
+@@ -378,6 +378,9 @@ static int start_cstp_connection(struct openconnect_info *vpninfo)
+ int cstpmtu = atol(colon);
+ if (cstpmtu > mtu)
+ mtu = cstpmtu;
++ } else if (!strcmp(buf + 7, "DynDNS")) {
++ if (!strcmp(colon, "true"))
++ vpninfo->is_dyndns = 1;
+ } else if (!strcmp(buf + 7, "Address-IP6")) {
+ vpninfo->ip_info.netmask6 = new_option->value;
+ } else if (!strcmp(buf + 7, "Address")) {
diff --git a/openconnect-internal.h b/openconnect-internal.h
-index 1bc79e5..cafbb3c 100644
+index 1bc79e5..db6c2ba 100644
--- a/openconnect-internal.h
+++ b/openconnect-internal.h
-@@ -424,6 +424,9 @@ struct openconnect_info {
- struct sockaddr *peer_addr;
- struct sockaddr *dtls_addr;
-
-+ struct sockaddr *first_peer_addr;
-+ socklen_t first_peer_addrlen;
-+
+@@ -427,6 +427,7 @@ struct openconnect_info {
int dtls_local_port;
int deflate;
++ int is_dyndns; /* Attempt to redo DNS lookup on each CSTP reconnect */
+ char *useragent;
+
+ const char *quit_reason;
diff --git a/ssl.c b/ssl.c
-index b50652d..e341871 100644
+index b50652d..d47a819 100644
--- a/ssl.c
+++ b/ssl.c
-@@ -110,6 +110,7 @@ int connect_https_socket(struct openconnect_info *vpninfo)
+@@ -106,6 +106,23 @@ unsigned string_is_hostname(const char *str)
+ return 1;
+ }
+
++static int match_sockaddr(struct sockaddr *a, struct sockaddr *b)
++{
++ if (a->sa_family == AF_INET) {
++ struct sockaddr_in *a4 = (void *)a;
++ struct sockaddr_in *b4 = (void *)b;
++
++ return (a4->sin_addr.s_addr == b4->sin_addr.s_addr) &&
++ (a4->sin_port == b4->sin_port);
++ } else if (a->sa_family == AF_INET6) {
++ struct sockaddr_in6 *a6 = (void *)a;
++ struct sockaddr_in6 *b6 = (void *)b;
++ return !memcmp(&a6->sin6_addr, &b6->sin6_addr, sizeof(a6->sin6_addr) &&
++ a6->sin6_port == b6->sin6_port);
++ } else
++ return 0;
++}
++
+ int connect_https_socket(struct openconnect_info *vpninfo)
{
int ssl_sock = -1;
- int err;
-+ unsigned retry_old_ip = 0;
-
+@@ -114,7 +131,11 @@ int connect_https_socket(struct openconnect_info *vpninfo)
if (!vpninfo->port)
vpninfo->port = 443;
-@@ -230,6 +231,8 @@ int connect_https_socket(struct openconnect_info *vpninfo)
+
+- if (vpninfo->peer_addr) {
++ /* If we're talking to a server which told us it has dynamic DNS, don't
++ just re-use its previous IP address. If we're talking to a proxy, we
++ can use *its* previous IP address. We expect it'll re-do the DNS
++ lookup for the server anyway. */
++ if (vpninfo->peer_addr && (!vpninfo->is_dyndns || vpninfo->proxy)) {
+ reconnect:
+ #ifdef SOCK_CLOEXEC
+ ssl_sock = socket(vpninfo->peer_addr->sa_family, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_IP);
+@@ -230,6 +251,13 @@ int connect_https_socket(struct openconnect_info *vpninfo)
if (hints.ai_flags & AI_NUMERICHOST)
free(hostname);
ssl_sock = -EINVAL;
-+ if (vpninfo->first_peer_addr != NULL)
-+ retry_old_ip = 1;
++ /* If we were just retrying for dynamic DNS, reconnct using
++ the previously-known IP address */
++ if (vpninfo->peer_addr) {
++ vpn_progress(vpninfo, PRG_ERR,
++ _("Reconnecting to DynDNS server using previously cached IP address\n"));
++ goto reconnect;
++ }
goto out;
}
if (hints.ai_flags & AI_NUMERICHOST)
-@@ -291,7 +294,10 @@ int connect_https_socket(struct openconnect_info *vpninfo)
+@@ -257,6 +285,8 @@ int connect_https_socket(struct openconnect_info *vpninfo)
+ if (cancellable_connect(vpninfo, ssl_sock, rp->ai_addr, rp->ai_addrlen) >= 0) {
+ /* Store the peer address we actually used, so that DTLS can
+ use it again later */
++ free(vpninfo->peer_addr);
++ vpninfo->peer_addrlen = 0;
+ vpninfo->peer_addr = malloc(rp->ai_addrlen);
+ if (!vpninfo->peer_addr) {
+ vpn_progress(vpninfo, PRG_ERR,
+@@ -288,6 +318,17 @@ int connect_https_socket(struct openconnect_info *vpninfo)
+ }
+ closesocket(ssl_sock);
+ ssl_sock = -1;
++
++ /* If we're in DynDNS mode but this *was* the cached IP address,
++ * don't bother falling back to it if it didn't work. */
++ if (vpninfo->peer_addr && vpninfo->peer_addrlen == rp->ai_addrlen &&
++ match_sockaddr(vpninfo->peer_addr, rp->ai_addr)) {
++ vpn_progress(vpninfo, PRG_TRACE,
++ _("Forgetting non-functional previous peer address\n"));
++ free(vpninfo->peer_addr);
++ vpninfo->peer_addr = 0;
++ vpninfo->peer_addrlen = 0;
++ }
}
freeaddrinfo(result);
-+
- if (ssl_sock < 0) {
-+ if (vpninfo->first_peer_addr != NULL)
-+ retry_old_ip = 1;
- vpn_progress(vpninfo, PRG_ERR,
+@@ -296,6 +337,11 @@ int connect_https_socket(struct openconnect_info *vpninfo)
_("Failed to connect to host %s\n"),
vpninfo->proxy?:vpninfo->hostname);
-@@ -314,6 +320,21 @@ int connect_https_socket(struct openconnect_info *vpninfo)
+ ssl_sock = -EINVAL;
++ if (vpninfo->peer_addr) {
++ vpn_progress(vpninfo, PRG_ERR,
++ _("Reconnecting to DynDNS server using previously cached IP address\n"));
++ goto reconnect;
++ }
+ goto out;
}
}
- out:
-+ if (retry_old_ip != 0 && vpninfo->first_peer_addr != NULL) {
-+ vpn_progress(vpninfo, PRG_ERR,
-+ _("Retrying connection to host %s with original IP\n"),
-+ vpninfo->proxy?:vpninfo->hostname);
-+
-+ retry_old_ip = 0;
-+ if (vpninfo->first_peer_addrlen > vpninfo->peer_addrlen || vpninfo->peer_addr == NULL)
-+ realloc_inplace(vpninfo->peer_addr, vpninfo->first_peer_addrlen);
-+
-+ if (vpninfo->peer_addr != NULL) {
-+ memcpy(vpninfo->peer_addr, vpninfo->first_peer_addr, vpninfo->first_peer_addrlen);
-+ goto reconnect;
-+ }
-+ }
-+
- /* If proxy processing returned -EAGAIN to reconnect before attempting
- further auth, and we failed to reconnect, we have to clean up here. */
- cleanup_proxy_auth(vpninfo);
+--
+2.1.3
+