[PATCH 1/2] dtls: Move MTU detection code into the GnuTLS section

Kevin Cernekee cernekee at gmail.com
Sun Jan 31 23:10:12 PST 2016


This code will need to be made more generic before it works with
OpenSSL.  For now, just skip it on OpenSSL builds so that we can
compile again.

Signed-off-by: Kevin Cernekee <cernekee at gmail.com>
---
 dtls.c | 1020 ++++++++++++++++++++++++++++++++--------------------------------
 1 file changed, 509 insertions(+), 511 deletions(-)

diff --git a/dtls.c b/dtls.c
index 7dc6ba9..f35d1a5 100644
--- a/dtls.c
+++ b/dtls.c
@@ -466,8 +466,6 @@ void append_dtls_ciphers(struct openconnect_info *vpninfo, struct oc_text_buf *b
 # define GNUTLS_CIPHER_CHACHA20_POLY1305 23
 #endif
 
-static void detect_mtu(struct openconnect_info *vpninfo);
-
 struct {
 	const char *name;
 	gnutls_protocol_t version;
@@ -613,179 +611,443 @@ static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
 	return 0;
 }
 
-static int dtls_try_handshake(struct openconnect_info *vpninfo)
-{
-	int err = gnutls_handshake(vpninfo->dtls_ssl);
-	char *str;
-
-	if (!err) {
-#ifdef HAVE_GNUTLS_DTLS_SET_DATA_MTU
-		/* Make sure GnuTLS's idea of the MTU is sufficient to take
-		   a full VPN MTU (with 1-byte header) in a data record. */
-		err = gnutls_dtls_set_data_mtu(vpninfo->dtls_ssl, vpninfo->ip_info.mtu + 1);
-		if (err) {
-			vpn_progress(vpninfo, PRG_ERR,
-				     _("Failed to set DTLS MTU: %s\n"),
-				     gnutls_strerror(err));
-			goto error;
-		}
-#else
-		/* If we don't have gnutls_dtls_set_data_mtu() then make sure
-		   we leave enough headroom by adding the worst-case overhead.
-		   We only support AES128-CBC and DES-CBC3-SHA anyway, so
-		   working out the worst case isn't hard. */
-		gnutls_dtls_set_mtu(vpninfo->dtls_ssl,
-				    vpninfo->ip_info.mtu + 1 /* packet + header */
-				    + 13 /* DTLS header */
-				    + 20 /* biggest supported MAC (SHA1) */
-				    + 16 /* biggest supported IV (AES-128) */
-				    + 16 /* max padding */);
+/* Old glibc doesn't define that */
+#if defined(__linux__) && !defined(IPV6_PATHMTU)
+# define IPV6_PATHMTU 61
 #endif
 
-		vpninfo->dtls_state = DTLS_CONNECTED;
-		str = get_gnutls_cipher(vpninfo->dtls_ssl);
-		if (str) {
-			const char *c;
-			vpn_progress(vpninfo, PRG_INFO,
-				     _("Established DTLS connection (using GnuTLS). Ciphersuite %s.\n"),
-				     str);
-			gnutls_free(str);
-			c = openconnect_get_dtls_compression(vpninfo);
-			if (c) {
-				vpn_progress(vpninfo, PRG_INFO,
-					     _("DTLS connection compression using %s.\n"), c);
-			}
-		}
-
-		vpninfo->dtls_times.last_rekey = vpninfo->dtls_times.last_rx = 
-			vpninfo->dtls_times.last_tx = time(NULL);
-
-		detect_mtu(vpninfo);
-		/* XXX: For OpenSSL we explicitly prevent retransmits here. */
-		return 0;
-	}
+static int is_cancelled(struct openconnect_info *vpninfo)
+{
+	fd_set rd_set;
+	int maxfd = 0;
+	FD_ZERO(&rd_set);
+	cmd_fd_set(vpninfo, &rd_set, &maxfd);
 
-	if (err == GNUTLS_E_AGAIN) {
-		if (time(NULL) < vpninfo->new_dtls_started + 12)
-			return 0;
-		vpn_progress(vpninfo, PRG_DEBUG, _("DTLS handshake timed out\n"));
+	if (is_cancel_pending(vpninfo, &rd_set)) {
+		vpn_progress(vpninfo, PRG_ERR, _("SSL operation cancelled\n"));
+		return -EINTR;
 	}
 
-	vpn_progress(vpninfo, PRG_ERR, _("DTLS handshake failed: %s\n"),
-		     gnutls_strerror(err));
-	if (err == GNUTLS_E_PUSH_ERROR)
-		vpn_progress(vpninfo, PRG_ERR,
-			     _("(Is a firewall preventing you from sending UDP packets?)\n"));
- error:
-	dtls_close(vpninfo);
-
-	vpninfo->dtls_state = DTLS_SLEEPING;
-	time(&vpninfo->new_dtls_started);
-	return -EINVAL;
+	return 0;
 }
 
-void dtls_shutdown(struct openconnect_info *vpninfo)
+static
+void ms_sleep(unsigned ms)
 {
-	dtls_close(vpninfo);
+	struct timespec tv;
+
+	tv.tv_sec = ms/1000;
+	tv.tv_nsec = (ms%1000) * 1000 * 1000;
+
+	nanosleep(&tv, NULL);
 }
-#endif
 
-static int connect_dtls_socket(struct openconnect_info *vpninfo)
+#define MTU_ID_SIZE 4
+#define MTU_FAIL_CONT { \
+		cur--; \
+		max = cur; \
+		continue; \
+	}
+
+/* Performs a binary search to detect MTU.
+ * @buf: is preallocated with MTU size
+ * @id: a unique ID for our DPD exchange
+ *
+ * Returns: new MTU or 0
+ */
+static int detect_mtu_ipv4(struct openconnect_info *vpninfo, unsigned char *buf)
 {
-	int dtls_fd, ret;
+	int max, min, cur, ret;
+	int max_tries = 100; /* maximum number of loops in bin search */
+	int max_recv_loops = 40; /* 4 secs max */
+	char id[MTU_ID_SIZE];
 
-	/* Sanity check for the removal of new_dtls_{fd,ssl} */
-	if (vpninfo->dtls_fd != -1) {
-		vpn_progress(vpninfo, PRG_ERR, _("DTLS connection attempted with an existing fd\n"));
-		vpninfo->dtls_attempt_period = 0;
-		return -EINVAL;
-	}
+	cur = max = vpninfo->ip_info.mtu;
+	min = vpninfo->ip_info.mtu/2;
 
-	if (!vpninfo->dtls_addr) {
-		vpn_progress(vpninfo, PRG_ERR, _("No DTLS address\n"));
-		vpninfo->dtls_attempt_period = 0;
-		return -EINVAL;
-	}
+	vpn_progress(vpninfo, PRG_DEBUG,
+	     _("Initiating IPv4 MTU detection (min=%d, max=%d)\n"), min, max);
 
-	if (!vpninfo->dtls_cipher) {
-		/* We probably didn't offer it any ciphers it liked */
-		vpn_progress(vpninfo, PRG_ERR, _("Server offered no DTLS cipher option\n"));
-		vpninfo->dtls_attempt_period = 0;
-		return -EINVAL;
-	}
+	while (max > min) {
+		if (max_tries-- <= 0) {
+			vpn_progress(vpninfo, PRG_ERR,
+			     _("Too long time in MTU detect loop; bailing out.\n"));
+			goto fail;
+		}
 
-	if (vpninfo->proxy) {
-		/* XXX: Theoretically, SOCKS5 proxies can do UDP too */
-		vpn_progress(vpninfo, PRG_ERR, _("No DTLS when connected via proxy\n"));
-		vpninfo->dtls_attempt_period = 0;
-		return -EINVAL;
-	}
+		/* generate unique ID */
+		if (gnutls_rnd(GNUTLS_RND_NONCE, id, sizeof(id)) < 0)
+			goto fail;
 
-	dtls_fd = udp_connect(vpninfo);
-	if (dtls_fd < 0)
-		return -EINVAL;
+		cur = (min + max + 1) / 2;
+		buf[0] = AC_PKT_DPD_OUT;
+		memcpy(&buf[1], id, sizeof(id));
 
+		do {
+			if (is_cancelled(vpninfo))
+				goto fail;
 
-	ret = start_dtls_handshake(vpninfo, dtls_fd);
-	if (ret) {
-		closesocket(dtls_fd);
-		return ret;
-	}
+			ret = DTLS_SEND(vpninfo->dtls_ssl, buf, cur+1);
+			if (ret < 0)
+				ms_sleep(100);
+		} while(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
 
-	vpninfo->dtls_state = DTLS_CONNECTING;
+		if (ret != cur+1) {
+			vpn_progress(vpninfo, PRG_ERR,
+				     _("Failed to send DPD request (%d): %s\n"), cur, gnutls_strerror(ret));
+			MTU_FAIL_CONT;
+		}
 
-	vpninfo->dtls_fd = dtls_fd;
-	monitor_fd_new(vpninfo, dtls);
-	monitor_read_fd(vpninfo, dtls);
-	monitor_except_fd(vpninfo, dtls);
+ reread:
+		memset(buf, 0, sizeof(id)+1);
+		do {
+			if (is_cancelled(vpninfo))
+				goto fail;
 
-	time(&vpninfo->new_dtls_started);
+			ret = DTLS_RECV(vpninfo->dtls_ssl, buf, cur + 1);
+			if (ret < 0)
+				ms_sleep(100);
 
-	return dtls_try_handshake(vpninfo);
-}
+			if (max_recv_loops-- <= 0)
+				goto fail;
+		} while(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
 
-void dtls_close(struct openconnect_info *vpninfo)
-{
-	if (vpninfo->dtls_ssl) {
-		DTLS_FREE(vpninfo->dtls_ssl);
-		closesocket(vpninfo->dtls_fd);
-		unmonitor_read_fd(vpninfo, dtls);
-		unmonitor_write_fd(vpninfo, dtls);
-		unmonitor_except_fd(vpninfo, dtls);
-		vpninfo->dtls_ssl = NULL;
-		vpninfo->dtls_fd = -1;
+		if (ret > 0 && (buf[0] != AC_PKT_DPD_RESP || memcmp(&buf[1], id, sizeof(buf) != 0))) {
+			vpn_progress(vpninfo, PRG_DEBUG,
+			     _("Received unexpected packet (%.2x) in MTU detection; skipping.\n"), (unsigned)buf[0]);
+			goto reread; /* resend */
+		}
+
+		if (ret <= 0) {
+			vpn_progress(vpninfo, PRG_ERR,
+			     _("Failed to recv DPD request (%d): %s\n"), cur, gnutls_strerror(ret));
+			MTU_FAIL_CONT;
+		}
+
+		/* If we reached the max, success */
+		if (cur == max) {
+			break;
+		}
+		min = cur;
 	}
-}
 
-static int dtls_reconnect(struct openconnect_info *vpninfo)
-{
-	dtls_close(vpninfo);
-	vpninfo->dtls_state = DTLS_SLEEPING;
-	return connect_dtls_socket(vpninfo);
+	return cur;
+ fail:
+ 	return 0;
 }
 
-int dtls_setup(struct openconnect_info *vpninfo, int dtls_attempt_period)
+#if defined(IPPROTO_IPV6)
+/* Verifies whether current MTU is ok, or detects new MTU using IPv6's ICMP6 messages
+ * @buf: is preallocated with MTU size
+ * @id: a unique ID for our DPD exchange
+ *
+ * Returns: new MTU or 0
+ */
+static int detect_mtu_ipv6(struct openconnect_info *vpninfo, unsigned char *buf)
 {
-	struct oc_vpn_option *dtls_opt = vpninfo->dtls_options;
-	int dtls_port = 0;
-
-	if (vpninfo->dtls_state == DTLS_DISABLED)
-		return -EINVAL;
+	int max, cur, ret;
+	int max_resends = 5; /* maximum number of resends */
+	int max_recv_loops = 40; /* 4 secs max */
+	char id[MTU_ID_SIZE];
 
-	vpninfo->dtls_attempt_period = dtls_attempt_period;
-	if (!dtls_attempt_period)
-		return 0;
+	cur = max = vpninfo->ip_info.mtu;
 
-#if defined(OPENCONNECT_GNUTLS) && defined(DTLS_OPENSSL)
-	/* If we're using GnuTLS for authentication but OpenSSL for DTLS,
-	   we'll need to initialise OpenSSL now... */
-	SSL_library_init();
-	ERR_clear_error();
-	SSL_load_error_strings();
-	OpenSSL_add_all_algorithms();
-#endif
+	vpn_progress(vpninfo, PRG_DEBUG,
+	     _("Initiating IPv6 MTU detection\n"));
 
-	while (dtls_opt) {
+	do {
+		/* generate unique ID */
+		if (gnutls_rnd(GNUTLS_RND_NONCE, id, sizeof(id)) < 0)
+			goto fail;
+
+		buf[0] = AC_PKT_DPD_OUT;
+		memcpy(&buf[1], id, sizeof(id));
+		do {
+			if (is_cancelled(vpninfo))
+				goto fail;
+
+			ret = DTLS_SEND(vpninfo->dtls_ssl, buf, cur+1);
+			if (ret < 0)
+				ms_sleep(100);
+		} while(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
+
+		if (ret != cur+1) {
+			vpn_progress(vpninfo, PRG_ERR,
+				     _("Failed to send DPD request (%d): %s\n"), cur, gnutls_strerror(ret));
+			goto mtu6_fail;
+		}
+
+ reread:
+		memset(buf, 0, sizeof(id)+1);
+		do {
+			if (is_cancelled(vpninfo))
+				goto fail;
+
+			ret = DTLS_RECV(vpninfo->dtls_ssl, buf, cur + 1);
+			if (ret < 0)
+				ms_sleep(100);
+
+			if (max_recv_loops-- <= 0)
+				goto fail;
+		} while(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
+
+		/* timeout, probably our original request was lost,
+		 * let's resend the DPD */
+		if (ret == GNUTLS_E_TIMEDOUT)
+			continue;
+
+		/* something unexpected was received, let's ignore it */
+		if (ret > 0 && (buf[0] != AC_PKT_DPD_RESP || memcmp(&buf[1], id, sizeof(buf) != 0))) {
+			vpn_progress(vpninfo, PRG_DEBUG,
+			     _("Received unexpected packet (%.2x) in MTU detection; skipping.\n"), (unsigned)buf[0]);
+			goto reread;
+		}
+
+		/* we received what we expected, move on */
+		break;
+	} while(max_resends-- > 0);
+
+	/* If we received back our DPD packet, do nothing; otherwise,
+	 * attempt to get MTU from the ICMP6 packet we received */
+	if (ret <= 0) {
+		struct ip6_mtuinfo mtuinfo;
+		socklen_t len = sizeof(mtuinfo);
+		max = 0;
+		vpn_progress(vpninfo, PRG_ERR,
+		     _("Failed to recv DPD request (%d): %s\n"), cur, gnutls_strerror(ret));
+ mtu6_fail:
+		if (getsockopt(vpninfo->dtls_fd, IPPROTO_IPV6, IPV6_PATHMTU, &mtuinfo, &len) >= 0) {
+			max = mtuinfo.ip6m_mtu;
+			if (max >= 0 && max < cur) {
+				gnutls_dtls_set_mtu(vpninfo->dtls_ssl, max);
+				cur = gnutls_dtls_get_data_mtu(vpninfo->dtls_ssl) - /*ipv6*/40 - /*udp*/20 - /*oc dtls*/1;
+			}
+		}
+	}
+
+	return cur;
+ fail:
+	return 0;
+}
+#endif
+
+static void detect_mtu(struct openconnect_info *vpninfo)
+{
+	int mtu = vpninfo->ip_info.mtu;
+	int prev_mtu = vpninfo->ip_info.mtu;
+	unsigned char *buf;
+
+	if (vpninfo->ip_info.mtu < 1+MTU_ID_SIZE)
+		return;
+
+	/* detect MTU */
+	buf = calloc(1, 1 + vpninfo->ip_info.mtu);
+	if (!buf) {
+		vpn_progress(vpninfo, PRG_ERR, _("Allocation failed\n"));
+		return;
+	}
+
+	/* enforce a timeout in receiving */
+	gnutls_record_set_timeout(vpninfo->dtls_ssl, 2400);
+	if (vpninfo->peer_addr->sa_family == AF_INET) { /* IPv4 */
+		mtu = detect_mtu_ipv4(vpninfo, buf);
+		if (mtu == 0)
+			goto skip_mtu;
+#if defined(IPPROTO_IPV6)
+	} else if (vpninfo->peer_addr->sa_family == AF_INET6) { /* IPv6 */
+		mtu = detect_mtu_ipv6(vpninfo, buf);
+		if (mtu == 0)
+			goto skip_mtu;
+#endif
+	}
+
+	vpninfo->ip_info.mtu = mtu;
+	if (prev_mtu != vpninfo->ip_info.mtu) {
+		vpn_progress(vpninfo, PRG_INFO,
+		     _("Detected MTU of %d bytes (was %d)\n"), vpninfo->ip_info.mtu, prev_mtu);
+	} else {
+		vpn_progress(vpninfo, PRG_DEBUG,
+		     _("No change in MTU after detection (was %d)\n"), prev_mtu);
+	}
+
+ skip_mtu:
+	gnutls_record_set_timeout(vpninfo->dtls_ssl, 0);
+	free(buf);
+}
+
+static int dtls_try_handshake(struct openconnect_info *vpninfo)
+{
+	int err = gnutls_handshake(vpninfo->dtls_ssl);
+	char *str;
+
+	if (!err) {
+#ifdef HAVE_GNUTLS_DTLS_SET_DATA_MTU
+		/* Make sure GnuTLS's idea of the MTU is sufficient to take
+		   a full VPN MTU (with 1-byte header) in a data record. */
+		err = gnutls_dtls_set_data_mtu(vpninfo->dtls_ssl, vpninfo->ip_info.mtu + 1);
+		if (err) {
+			vpn_progress(vpninfo, PRG_ERR,
+				     _("Failed to set DTLS MTU: %s\n"),
+				     gnutls_strerror(err));
+			goto error;
+		}
+#else
+		/* If we don't have gnutls_dtls_set_data_mtu() then make sure
+		   we leave enough headroom by adding the worst-case overhead.
+		   We only support AES128-CBC and DES-CBC3-SHA anyway, so
+		   working out the worst case isn't hard. */
+		gnutls_dtls_set_mtu(vpninfo->dtls_ssl,
+				    vpninfo->ip_info.mtu + 1 /* packet + header */
+				    + 13 /* DTLS header */
+				    + 20 /* biggest supported MAC (SHA1) */
+				    + 16 /* biggest supported IV (AES-128) */
+				    + 16 /* max padding */);
+#endif
+
+		vpninfo->dtls_state = DTLS_CONNECTED;
+		str = get_gnutls_cipher(vpninfo->dtls_ssl);
+		if (str) {
+			const char *c;
+			vpn_progress(vpninfo, PRG_INFO,
+				     _("Established DTLS connection (using GnuTLS). Ciphersuite %s.\n"),
+				     str);
+			gnutls_free(str);
+			c = openconnect_get_dtls_compression(vpninfo);
+			if (c) {
+				vpn_progress(vpninfo, PRG_INFO,
+					     _("DTLS connection compression using %s.\n"), c);
+			}
+		}
+
+		vpninfo->dtls_times.last_rekey = vpninfo->dtls_times.last_rx = 
+			vpninfo->dtls_times.last_tx = time(NULL);
+
+		detect_mtu(vpninfo);
+		/* XXX: For OpenSSL we explicitly prevent retransmits here. */
+		return 0;
+	}
+
+	if (err == GNUTLS_E_AGAIN) {
+		if (time(NULL) < vpninfo->new_dtls_started + 12)
+			return 0;
+		vpn_progress(vpninfo, PRG_DEBUG, _("DTLS handshake timed out\n"));
+	}
+
+	vpn_progress(vpninfo, PRG_ERR, _("DTLS handshake failed: %s\n"),
+		     gnutls_strerror(err));
+	if (err == GNUTLS_E_PUSH_ERROR)
+		vpn_progress(vpninfo, PRG_ERR,
+			     _("(Is a firewall preventing you from sending UDP packets?)\n"));
+ error:
+	dtls_close(vpninfo);
+
+	vpninfo->dtls_state = DTLS_SLEEPING;
+	time(&vpninfo->new_dtls_started);
+	return -EINVAL;
+}
+
+void dtls_shutdown(struct openconnect_info *vpninfo)
+{
+	dtls_close(vpninfo);
+}
+#endif
+
+static int connect_dtls_socket(struct openconnect_info *vpninfo)
+{
+	int dtls_fd, ret;
+
+	/* Sanity check for the removal of new_dtls_{fd,ssl} */
+	if (vpninfo->dtls_fd != -1) {
+		vpn_progress(vpninfo, PRG_ERR, _("DTLS connection attempted with an existing fd\n"));
+		vpninfo->dtls_attempt_period = 0;
+		return -EINVAL;
+	}
+
+	if (!vpninfo->dtls_addr) {
+		vpn_progress(vpninfo, PRG_ERR, _("No DTLS address\n"));
+		vpninfo->dtls_attempt_period = 0;
+		return -EINVAL;
+	}
+
+	if (!vpninfo->dtls_cipher) {
+		/* We probably didn't offer it any ciphers it liked */
+		vpn_progress(vpninfo, PRG_ERR, _("Server offered no DTLS cipher option\n"));
+		vpninfo->dtls_attempt_period = 0;
+		return -EINVAL;
+	}
+
+	if (vpninfo->proxy) {
+		/* XXX: Theoretically, SOCKS5 proxies can do UDP too */
+		vpn_progress(vpninfo, PRG_ERR, _("No DTLS when connected via proxy\n"));
+		vpninfo->dtls_attempt_period = 0;
+		return -EINVAL;
+	}
+
+	dtls_fd = udp_connect(vpninfo);
+	if (dtls_fd < 0)
+		return -EINVAL;
+
+
+	ret = start_dtls_handshake(vpninfo, dtls_fd);
+	if (ret) {
+		closesocket(dtls_fd);
+		return ret;
+	}
+
+	vpninfo->dtls_state = DTLS_CONNECTING;
+
+	vpninfo->dtls_fd = dtls_fd;
+	monitor_fd_new(vpninfo, dtls);
+	monitor_read_fd(vpninfo, dtls);
+	monitor_except_fd(vpninfo, dtls);
+
+	time(&vpninfo->new_dtls_started);
+
+	return dtls_try_handshake(vpninfo);
+}
+
+void dtls_close(struct openconnect_info *vpninfo)
+{
+	if (vpninfo->dtls_ssl) {
+		DTLS_FREE(vpninfo->dtls_ssl);
+		closesocket(vpninfo->dtls_fd);
+		unmonitor_read_fd(vpninfo, dtls);
+		unmonitor_write_fd(vpninfo, dtls);
+		unmonitor_except_fd(vpninfo, dtls);
+		vpninfo->dtls_ssl = NULL;
+		vpninfo->dtls_fd = -1;
+	}
+}
+
+static int dtls_reconnect(struct openconnect_info *vpninfo)
+{
+	dtls_close(vpninfo);
+	vpninfo->dtls_state = DTLS_SLEEPING;
+	return connect_dtls_socket(vpninfo);
+}
+
+int dtls_setup(struct openconnect_info *vpninfo, int dtls_attempt_period)
+{
+	struct oc_vpn_option *dtls_opt = vpninfo->dtls_options;
+	int dtls_port = 0;
+
+	if (vpninfo->dtls_state == DTLS_DISABLED)
+		return -EINVAL;
+
+	vpninfo->dtls_attempt_period = dtls_attempt_period;
+	if (!dtls_attempt_period)
+		return 0;
+
+#if defined(OPENCONNECT_GNUTLS) && defined(DTLS_OPENSSL)
+	/* If we're using GnuTLS for authentication but OpenSSL for DTLS,
+	   we'll need to initialise OpenSSL now... */
+	SSL_library_init();
+	ERR_clear_error();
+	SSL_load_error_strings();
+	OpenSSL_add_all_algorithms();
+#endif
+
+	while (dtls_opt) {
 		vpn_progress(vpninfo, PRG_DEBUG,
 			     _("DTLS option %s : %s\n"),
 			     dtls_opt->option, dtls_opt->value);
@@ -933,402 +1195,138 @@ int dtls_mainloop(struct openconnect_info *vpninfo, int *timeout)
 				break;
 			} else {
 			unknown_pkt:
-				vpninfo->quit_reason = "Unknown packet received";
-				return 1;
-			}
-
-		}
-	}
-
-	switch (keepalive_action(&vpninfo->dtls_times, timeout)) {
-	case KA_REKEY: {
-		int ret;
-
-		vpn_progress(vpninfo, PRG_INFO, _("DTLS rekey due\n"));
-
-		if (vpninfo->dtls_times.rekey_method == REKEY_SSL) {
-			time(&vpninfo->new_dtls_started);
-			vpninfo->dtls_state = DTLS_CONNECTING;
-			ret = dtls_try_handshake(vpninfo);
-			if (ret) {
-				vpn_progress(vpninfo, PRG_ERR, _("DTLS Rehandshake failed; reconnecting.\n"));
-				return connect_dtls_socket(vpninfo);
-			}
-		}
-
-		return 1;
-	}
-
-	case KA_DPD_DEAD:
-		vpn_progress(vpninfo, PRG_ERR, _("DTLS Dead Peer Detection detected dead peer!\n"));
-		/* Fall back to SSL, and start a new DTLS connection */
-		dtls_reconnect(vpninfo);
-		return 1;
-
-	case KA_DPD:
-		vpn_progress(vpninfo, PRG_DEBUG, _("Send DTLS DPD\n"));
-
-		magic_pkt = AC_PKT_DPD_OUT;
-		if (DTLS_SEND(vpninfo->dtls_ssl, &magic_pkt, 1) != 1)
-			vpn_progress(vpninfo, PRG_ERR,
-				     _("Failed to send DPD request. Expect disconnect\n"));
-
-		/* last_dpd will just have been set */
-		vpninfo->dtls_times.last_tx = vpninfo->dtls_times.last_dpd;
-		work_done = 1;
-		break;
-
-	case KA_KEEPALIVE:
-		/* No need to send an explicit keepalive
-		   if we have real data to send */
-		if (vpninfo->outgoing_queue.head)
-			break;
-
-		vpn_progress(vpninfo, PRG_DEBUG, _("Send DTLS Keepalive\n"));
-
-		magic_pkt = AC_PKT_KEEPALIVE;
-		if (DTLS_SEND(vpninfo->dtls_ssl, &magic_pkt, 1) != 1)
-			vpn_progress(vpninfo, PRG_ERR,
-				     _("Failed to send keepalive request. Expect disconnect\n"));
-		time(&vpninfo->dtls_times.last_tx);
-		work_done = 1;
-		break;
-
-	case KA_NONE:
-		;
-	}
-
-	/* Service outgoing packet queue */
-	unmonitor_write_fd(vpninfo, dtls);
-	while (vpninfo->outgoing_queue.head) {
-		struct pkt *this = dequeue_packet(&vpninfo->outgoing_queue);
-		struct pkt *send_pkt = this;
-		int ret;
-
-		/* One byte of header */
-		this->cstp.hdr[7] = AC_PKT_DATA;
-
-		/* We can compress into vpninfo->deflate_pkt unless CSTP
-		 * currently has a compressed packet pending — which it
-		 * shouldn't if DTLS is active. */
-		if (vpninfo->dtls_compr &&
-		    vpninfo->current_ssl_pkt != vpninfo->deflate_pkt &&
-		    !compress_packet(vpninfo, vpninfo->dtls_compr, this)) {
-				send_pkt = vpninfo->deflate_pkt;
-				send_pkt->cstp.hdr[7] = AC_PKT_COMPRESSED;
-		}
-
-#if defined(DTLS_OPENSSL)
-		ret = SSL_write(vpninfo->dtls_ssl, &send_pkt->cstp.hdr[7], send_pkt->len + 1);
-		if (ret <= 0) {
-			ret = SSL_get_error(vpninfo->dtls_ssl, ret);
-
-			if (ret == SSL_ERROR_WANT_WRITE) {
-				monitor_write_fd(vpninfo, dtls);
-				requeue_packet(&vpninfo->outgoing_queue, this);
-			} else if (ret != SSL_ERROR_WANT_READ) {
-				/* If it's a real error, kill the DTLS connection and
-				   requeue the packet to be sent over SSL */
-				vpn_progress(vpninfo, PRG_ERR,
-					     _("DTLS got write error %d. Falling back to SSL\n"),
-					     ret);
-				openconnect_report_ssl_errors(vpninfo);
-				dtls_reconnect(vpninfo);
-				requeue_packet(&vpninfo->outgoing_queue, this);
-				work_done = 1;
-			}
-			return work_done;
-		}
-#elif defined(DTLS_GNUTLS)
-		ret = gnutls_record_send(vpninfo->dtls_ssl, &send_pkt->cstp.hdr[7], send_pkt->len + 1);
-		if (ret <= 0) {
-			if (ret != GNUTLS_E_AGAIN) {
-				vpn_progress(vpninfo, PRG_ERR,
-					     _("DTLS got write error: %s. Falling back to SSL\n"),
-					     gnutls_strerror(ret));
-				dtls_reconnect(vpninfo);
-				work_done = 1;
-			} else {
-				/* Wake me up when it becomes writeable */
-				monitor_write_fd(vpninfo, dtls);
-			}
-
-			requeue_packet(&vpninfo->outgoing_queue, this);
-			return work_done;
-		}
-#endif
-		time(&vpninfo->dtls_times.last_tx);
-		vpn_progress(vpninfo, PRG_TRACE,
-			     _("Sent DTLS packet of %d bytes; DTLS send returned %d\n"),
-			     this->len, ret);
-		free(this);
-	}
-
-	return work_done;
-}
-
-/* Old glibc doesn't define that */
-#if defined(__linux__) && !defined(IPV6_PATHMTU)
-# define IPV6_PATHMTU 61
-#endif
-
-static int is_cancelled(struct openconnect_info *vpninfo)
-{
-	fd_set rd_set;
-	int maxfd = 0;
-	FD_ZERO(&rd_set);
-	cmd_fd_set(vpninfo, &rd_set, &maxfd);
-
-	if (is_cancel_pending(vpninfo, &rd_set)) {
-		vpn_progress(vpninfo, PRG_ERR, _("SSL operation cancelled\n"));
-		return -EINTR;
-	}
-
-	return 0;
-}
-
-static
-void ms_sleep(unsigned ms)
-{
-	struct timespec tv;
-
-	tv.tv_sec = ms/1000;
-	tv.tv_nsec = (ms%1000) * 1000 * 1000;
-
-	nanosleep(&tv, NULL);
-}
-
-#define MTU_ID_SIZE 4
-#define MTU_FAIL_CONT { \
-		cur--; \
-		max = cur; \
-		continue; \
-	}
-
-/* Performs a binary search to detect MTU.
- * @buf: is preallocated with MTU size
- * @id: a unique ID for our DPD exchange
- *
- * Returns: new MTU or 0
- */
-static int detect_mtu_ipv4(struct openconnect_info *vpninfo, unsigned char *buf)
-{
-	int max, min, cur, ret;
-	int max_tries = 100; /* maximum number of loops in bin search */
-	int max_recv_loops = 40; /* 4 secs max */
-	char id[MTU_ID_SIZE];
-
-	cur = max = vpninfo->ip_info.mtu;
-	min = vpninfo->ip_info.mtu/2;
-
-	vpn_progress(vpninfo, PRG_DEBUG,
-	     _("Initiating IPv4 MTU detection (min=%d, max=%d)\n"), min, max);
-
-	while (max > min) {
-		if (max_tries-- <= 0) {
-			vpn_progress(vpninfo, PRG_ERR,
-			     _("Too long time in MTU detect loop; bailing out.\n"));
-			goto fail;
-		}
-
-		/* generate unique ID */
-		if (gnutls_rnd(GNUTLS_RND_NONCE, id, sizeof(id)) < 0)
-			goto fail;
-
-		cur = (min + max + 1) / 2;
-		buf[0] = AC_PKT_DPD_OUT;
-		memcpy(&buf[1], id, sizeof(id));
-
-		do {
-			if (is_cancelled(vpninfo))
-				goto fail;
-
-			ret = DTLS_SEND(vpninfo->dtls_ssl, buf, cur+1);
-			if (ret < 0)
-				ms_sleep(100);
-		} while(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
+				vpninfo->quit_reason = "Unknown packet received";
+				return 1;
+			}
 
-		if (ret != cur+1) {
-			vpn_progress(vpninfo, PRG_ERR,
-				     _("Failed to send DPD request (%d): %s\n"), cur, gnutls_strerror(ret));
-			MTU_FAIL_CONT;
 		}
+	}
 
- reread:
-		memset(buf, 0, sizeof(id)+1);
-		do {
-			if (is_cancelled(vpninfo))
-				goto fail;
-
-			ret = DTLS_RECV(vpninfo->dtls_ssl, buf, cur + 1);
-			if (ret < 0)
-				ms_sleep(100);
-
-			if (max_recv_loops-- <= 0)
-				goto fail;
-		} while(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
+	switch (keepalive_action(&vpninfo->dtls_times, timeout)) {
+	case KA_REKEY: {
+		int ret;
 
-		if (ret > 0 && (buf[0] != AC_PKT_DPD_RESP || memcmp(&buf[1], id, sizeof(buf) != 0))) {
-			vpn_progress(vpninfo, PRG_DEBUG,
-			     _("Received unexpected packet (%.2x) in MTU detection; skipping.\n"), (unsigned)buf[0]);
-			goto reread; /* resend */
-		}
+		vpn_progress(vpninfo, PRG_INFO, _("DTLS rekey due\n"));
 
-		if (ret <= 0) {
-			vpn_progress(vpninfo, PRG_ERR,
-			     _("Failed to recv DPD request (%d): %s\n"), cur, gnutls_strerror(ret));
-			MTU_FAIL_CONT;
+		if (vpninfo->dtls_times.rekey_method == REKEY_SSL) {
+			time(&vpninfo->new_dtls_started);
+			vpninfo->dtls_state = DTLS_CONNECTING;
+			ret = dtls_try_handshake(vpninfo);
+			if (ret) {
+				vpn_progress(vpninfo, PRG_ERR, _("DTLS Rehandshake failed; reconnecting.\n"));
+				return connect_dtls_socket(vpninfo);
+			}
 		}
 
-		/* If we reached the max, success */
-		if (cur == max) {
-			break;
-		}
-		min = cur;
+		return 1;
 	}
 
-	return cur;
- fail:
- 	return 0;
-}
-
-#if defined(IPPROTO_IPV6)
-/* Verifies whether current MTU is ok, or detects new MTU using IPv6's ICMP6 messages
- * @buf: is preallocated with MTU size
- * @id: a unique ID for our DPD exchange
- *
- * Returns: new MTU or 0
- */
-static int detect_mtu_ipv6(struct openconnect_info *vpninfo, unsigned char *buf)
-{
-	int max, cur, ret;
-	int max_resends = 5; /* maximum number of resends */
-	int max_recv_loops = 40; /* 4 secs max */
-	char id[MTU_ID_SIZE];
+	case KA_DPD_DEAD:
+		vpn_progress(vpninfo, PRG_ERR, _("DTLS Dead Peer Detection detected dead peer!\n"));
+		/* Fall back to SSL, and start a new DTLS connection */
+		dtls_reconnect(vpninfo);
+		return 1;
 
-	cur = max = vpninfo->ip_info.mtu;
+	case KA_DPD:
+		vpn_progress(vpninfo, PRG_DEBUG, _("Send DTLS DPD\n"));
 
-	vpn_progress(vpninfo, PRG_DEBUG,
-	     _("Initiating IPv6 MTU detection\n"));
+		magic_pkt = AC_PKT_DPD_OUT;
+		if (DTLS_SEND(vpninfo->dtls_ssl, &magic_pkt, 1) != 1)
+			vpn_progress(vpninfo, PRG_ERR,
+				     _("Failed to send DPD request. Expect disconnect\n"));
 
-	do {
-		/* generate unique ID */
-		if (gnutls_rnd(GNUTLS_RND_NONCE, id, sizeof(id)) < 0)
-			goto fail;
+		/* last_dpd will just have been set */
+		vpninfo->dtls_times.last_tx = vpninfo->dtls_times.last_dpd;
+		work_done = 1;
+		break;
 
-		buf[0] = AC_PKT_DPD_OUT;
-		memcpy(&buf[1], id, sizeof(id));
-		do {
-			if (is_cancelled(vpninfo))
-				goto fail;
+	case KA_KEEPALIVE:
+		/* No need to send an explicit keepalive
+		   if we have real data to send */
+		if (vpninfo->outgoing_queue.head)
+			break;
 
-			ret = DTLS_SEND(vpninfo->dtls_ssl, buf, cur+1);
-			if (ret < 0)
-				ms_sleep(100);
-		} while(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
+		vpn_progress(vpninfo, PRG_DEBUG, _("Send DTLS Keepalive\n"));
 
-		if (ret != cur+1) {
+		magic_pkt = AC_PKT_KEEPALIVE;
+		if (DTLS_SEND(vpninfo->dtls_ssl, &magic_pkt, 1) != 1)
 			vpn_progress(vpninfo, PRG_ERR,
-				     _("Failed to send DPD request (%d): %s\n"), cur, gnutls_strerror(ret));
-			goto mtu6_fail;
-		}
-
- reread:
-		memset(buf, 0, sizeof(id)+1);
-		do {
-			if (is_cancelled(vpninfo))
-				goto fail;
+				     _("Failed to send keepalive request. Expect disconnect\n"));
+		time(&vpninfo->dtls_times.last_tx);
+		work_done = 1;
+		break;
 
-			ret = DTLS_RECV(vpninfo->dtls_ssl, buf, cur + 1);
-			if (ret < 0)
-				ms_sleep(100);
+	case KA_NONE:
+		;
+	}
 
-			if (max_recv_loops-- <= 0)
-				goto fail;
-		} while(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
+	/* Service outgoing packet queue */
+	unmonitor_write_fd(vpninfo, dtls);
+	while (vpninfo->outgoing_queue.head) {
+		struct pkt *this = dequeue_packet(&vpninfo->outgoing_queue);
+		struct pkt *send_pkt = this;
+		int ret;
 
-		/* timeout, probably our original request was lost,
-		 * let's resend the DPD */
-		if (ret == GNUTLS_E_TIMEDOUT)
-			continue;
+		/* One byte of header */
+		this->cstp.hdr[7] = AC_PKT_DATA;
 
-		/* something unexpected was received, let's ignore it */
-		if (ret > 0 && (buf[0] != AC_PKT_DPD_RESP || memcmp(&buf[1], id, sizeof(buf) != 0))) {
-			vpn_progress(vpninfo, PRG_DEBUG,
-			     _("Received unexpected packet (%.2x) in MTU detection; skipping.\n"), (unsigned)buf[0]);
-			goto reread;
+		/* We can compress into vpninfo->deflate_pkt unless CSTP
+		 * currently has a compressed packet pending — which it
+		 * shouldn't if DTLS is active. */
+		if (vpninfo->dtls_compr &&
+		    vpninfo->current_ssl_pkt != vpninfo->deflate_pkt &&
+		    !compress_packet(vpninfo, vpninfo->dtls_compr, this)) {
+				send_pkt = vpninfo->deflate_pkt;
+				send_pkt->cstp.hdr[7] = AC_PKT_COMPRESSED;
 		}
 
-		/* we received what we expected, move on */
-		break;
-	} while(max_resends-- > 0);
+#if defined(DTLS_OPENSSL)
+		ret = SSL_write(vpninfo->dtls_ssl, &send_pkt->cstp.hdr[7], send_pkt->len + 1);
+		if (ret <= 0) {
+			ret = SSL_get_error(vpninfo->dtls_ssl, ret);
 
-	/* If we received back our DPD packet, do nothing; otherwise,
-	 * attempt to get MTU from the ICMP6 packet we received */
-	if (ret <= 0) {
-		struct ip6_mtuinfo mtuinfo;
-		socklen_t len = sizeof(mtuinfo);
-		max = 0;
-		vpn_progress(vpninfo, PRG_ERR,
-		     _("Failed to recv DPD request (%d): %s\n"), cur, gnutls_strerror(ret));
- mtu6_fail:
-		if (getsockopt(vpninfo->dtls_fd, IPPROTO_IPV6, IPV6_PATHMTU, &mtuinfo, &len) >= 0) {
-			max = mtuinfo.ip6m_mtu;
-			if (max >= 0 && max < cur) {
-				gnutls_dtls_set_mtu(vpninfo->dtls_ssl, max);
-				cur = gnutls_dtls_get_data_mtu(vpninfo->dtls_ssl) - /*ipv6*/40 - /*udp*/20 - /*oc dtls*/1;
+			if (ret == SSL_ERROR_WANT_WRITE) {
+				monitor_write_fd(vpninfo, dtls);
+				requeue_packet(&vpninfo->outgoing_queue, this);
+			} else if (ret != SSL_ERROR_WANT_READ) {
+				/* If it's a real error, kill the DTLS connection and
+				   requeue the packet to be sent over SSL */
+				vpn_progress(vpninfo, PRG_ERR,
+					     _("DTLS got write error %d. Falling back to SSL\n"),
+					     ret);
+				openconnect_report_ssl_errors(vpninfo);
+				dtls_reconnect(vpninfo);
+				requeue_packet(&vpninfo->outgoing_queue, this);
+				work_done = 1;
 			}
+			return work_done;
 		}
-	}
-
-	return cur;
- fail:
-	return 0;
-}
-#endif
-
-static void detect_mtu(struct openconnect_info *vpninfo)
-{
-	int mtu = vpninfo->ip_info.mtu;
-	int prev_mtu = vpninfo->ip_info.mtu;
-	unsigned char *buf;
-
-	if (vpninfo->ip_info.mtu < 1+MTU_ID_SIZE)
-		return;
-
-	/* detect MTU */
-	buf = calloc(1, 1 + vpninfo->ip_info.mtu);
-	if (!buf) {
-		vpn_progress(vpninfo, PRG_ERR, _("Allocation failed\n"));
-		return;
-	}
+#elif defined(DTLS_GNUTLS)
+		ret = gnutls_record_send(vpninfo->dtls_ssl, &send_pkt->cstp.hdr[7], send_pkt->len + 1);
+		if (ret <= 0) {
+			if (ret != GNUTLS_E_AGAIN) {
+				vpn_progress(vpninfo, PRG_ERR,
+					     _("DTLS got write error: %s. Falling back to SSL\n"),
+					     gnutls_strerror(ret));
+				dtls_reconnect(vpninfo);
+				work_done = 1;
+			} else {
+				/* Wake me up when it becomes writeable */
+				monitor_write_fd(vpninfo, dtls);
+			}
 
-	/* enforce a timeout in receiving */
-	gnutls_record_set_timeout(vpninfo->dtls_ssl, 2400);
-	if (vpninfo->peer_addr->sa_family == AF_INET) { /* IPv4 */
-		mtu = detect_mtu_ipv4(vpninfo, buf);
-		if (mtu == 0)
-			goto skip_mtu;
-#if defined(IPPROTO_IPV6)
-	} else if (vpninfo->peer_addr->sa_family == AF_INET6) { /* IPv6 */
-		mtu = detect_mtu_ipv6(vpninfo, buf);
-		if (mtu == 0)
-			goto skip_mtu;
+			requeue_packet(&vpninfo->outgoing_queue, this);
+			return work_done;
+		}
 #endif
+		time(&vpninfo->dtls_times.last_tx);
+		vpn_progress(vpninfo, PRG_TRACE,
+			     _("Sent DTLS packet of %d bytes; DTLS send returned %d\n"),
+			     this->len, ret);
+		free(this);
 	}
 
-	vpninfo->ip_info.mtu = mtu;
-	if (prev_mtu != vpninfo->ip_info.mtu) {
-		vpn_progress(vpninfo, PRG_INFO,
-		     _("Detected MTU of %d bytes (was %d)\n"), vpninfo->ip_info.mtu, prev_mtu);
-	} else {
-		vpn_progress(vpninfo, PRG_DEBUG,
-		     _("No change in MTU after detection (was %d)\n"), prev_mtu);
-	}
-
- skip_mtu:
-	gnutls_record_set_timeout(vpninfo->dtls_ssl, 0);
-	free(buf);
+	return work_done;
 }
 
 #else /* !HAVE_DTLS */
-- 
1.9.1




More information about the openconnect-devel mailing list