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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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




[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux