[PATCH 3/5] Add support for GlobalProtect ESP tunnel

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

 



Most of the existing ESP support code (written for Juniper/nc) can be reused
for GlobalProtect ESP. The ESP algorithms, SPIs, and keys are sent as part of the
getconfig XML response.

GlobalProtect requires a fairly awkward "tap dance" between the TCP mainloop and
the UDP mainloop in order to support ESP:

* Prior to the getconfig XML request, the HTTPS tunnel will not work (even though
  the authcookie is already known from the login response) and the ESP tunnel
  also will not work (because the ESP keys are not known).
* After the getconfig XML request, either the ESP tunnel or the HTTPS tunnel can
  be connected, but not both.  As soon as the HTTPS tunnel is disconnected,
  the ESP keys are invalidated.  On the other hand, if the ESP tunnel stops
  responding due to some firewall that interferes with UDP, the HTTPS tunnel
  can still be connected.
* Therefore, in order to allow the ESP tunnel to start, the TCP mainloop must
  refrain from actually connecting to the HTTPS tunnel unless the ESP tunnel
  is disabled or has failed to connect... but it can't wait *too* long
  because then the HTTPS keepalive connection may be dropped, and the user
  will wonder why no traffic is flowing even though the VPN has allegedly
  started.  The wait time is currently hard-coded at 5 seconds (half the DPD
  interval used by the official clients).

Another quirk of the GlobalProtect ESP support: it uses specially
constructed ICMP request/reply ("ping") packets as the probes for ESP
initiation and DPD.

* These packets must contain a "magic payload" in order to work.
* In most GlobalProtect VPNs, the packets are addressed to the public, external IPv4
  address of the VPN gateway server even though they are sent over the ESP
  tunnel (???), but in some cases they must be addressed to a different address
  which is misleading described as <gw-address> in the getconfig XML response.

Don't blame me. I didn't design this.

GlobalProtect also has the strange quirk that incoming (server ? client) ESP
sequence numbers start at 1, not 0, but this just causes a one-time offset
for the replay protection checker.

Signed-off-by: Daniel Lenski <dlenski at gmail.com>
---
 esp.c                  |  14 ++-
 gpst.c                 | 290 +++++++++++++++++++++++++++++++++++++++++++++----
 library.c              |   8 ++
 mainloop.c             |   2 +-
 openconnect-internal.h |   7 +-
 www/globalprotect.xml  |  17 ++-
 6 files changed, 307 insertions(+), 31 deletions(-)

diff --git a/esp.c b/esp.c
index 5cea3fb..7a74b1f 100644
--- a/esp.c
+++ b/esp.c
@@ -103,15 +103,12 @@ int esp_mainloop(struct openconnect_info *vpninfo, int *timeout)
 	int ret;
 
 	if (vpninfo->dtls_state == DTLS_SLEEPING) {
-		int when = vpninfo->new_dtls_started + vpninfo->dtls_attempt_period - time(NULL);
-		if (when <= 0 || vpninfo->dtls_need_reconnect) {
+		if (ka_check_deadline(timeout, time(NULL), vpninfo->new_dtls_started + vpninfo->dtls_attempt_period)
+		    || vpninfo->dtls_need_reconnect) {
 			vpn_progress(vpninfo, PRG_DEBUG, _("Send ESP probes\n"));
 			if (vpninfo->proto->udp_send_probes)
 				vpninfo->proto->udp_send_probes(vpninfo);
-			when = vpninfo->dtls_attempt_period;
 		}
-		if (*timeout > when * 1000)
-			*timeout = when * 1000;
 	}
 	if (vpninfo->dtls_fd == -1)
 		return 0;
@@ -305,6 +302,13 @@ void esp_close(struct openconnect_info *vpninfo)
 		vpninfo->dtls_state = DTLS_SLEEPING;
 }
 
+void esp_close_secret(struct openconnect_info *vpninfo)
+{
+	esp_close(vpninfo);
+	if (vpninfo->dtls_state > DTLS_DISABLED)
+		vpninfo->dtls_state = DTLS_NOSECRET;
+}
+
 void esp_shutdown(struct openconnect_info *vpninfo)
 {
 	destroy_esp_ciphers(&vpninfo->esp_in[0]);
diff --git a/gpst.c b/gpst.c
index d7e9998..f66b61a 100644
--- a/gpst.c
+++ b/gpst.c
@@ -31,6 +31,13 @@
 #include <lz4.h>
 #endif
 
+#ifdef __FreeBSD__
+#include <sys/types.h>
+#include <netinet/in.h>
+#endif
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+
 #if defined(__linux__)
 /* For TCP_INFO */
 # include <linux/tcp.h>
@@ -292,20 +299,26 @@ out:
 	return result;
 }
 
-#define ESP_OVERHEAD (4 /* SPI */ + 4 /* sequence number */ + \
-         20 /* biggest supported MAC (SHA1) */ + 32 /* biggest supported IV (AES-256) */ + \
-	 1 /* pad length */ + 1 /* next header */ + \
-         16 /* max padding */ )
+
+#define ESP_HEADER_SIZE (4 /* SPI */ + 4 /* sequence number */)
+#define ESP_FOOTER_SIZE (1 /* pad length */ + 1 /* next header */)
 #define UDP_HEADER_SIZE 8
+#define TCP_HEADER_SIZE 20 /* with no options */
 #define IPV4_HEADER_SIZE 20
 #define IPV6_HEADER_SIZE 40
 
+/* Based on cstp.c's calculate_mtu().
+ *
+ * With HTTPS tunnel, there are 21 bytes of overhead beyond the
+ * TCP MSS: 5 bytes for TLS and 16 for GPST.
+ */
 static int calculate_mtu(struct openconnect_info *vpninfo)
 {
 	int mtu = vpninfo->reqmtu, base_mtu = vpninfo->basemtu;
+	int mss = 0;
 
 #if defined(__linux__) && defined(TCP_INFO)
-	if (!mtu || !base_mtu) {
+	if (!mtu) {
 		struct tcp_info ti;
 		socklen_t ti_size = sizeof(ti);
 
@@ -319,23 +332,19 @@ static int calculate_mtu(struct openconnect_info *vpninfo)
 				base_mtu = ti.tcpi_pmtu;
 			}
 
-			if (!base_mtu) {
-				if (ti.tcpi_rcv_mss < ti.tcpi_snd_mss)
-					base_mtu = ti.tcpi_rcv_mss - 13;
-				else
-					base_mtu = ti.tcpi_snd_mss - 13;
-			}
+			/* XXX: GlobalProtect has no mechanism to inform the server about the
+			 * desired MTU, so could just ignore the "incoming" MSS (tcpi_rcv_mss).
+			 */
+			mss = MIN(ti.tcpi_rcv_mss, ti.tcpi_snd_mss);
 		}
 	}
 #endif
 #ifdef TCP_MAXSEG
-	if (!base_mtu) {
-		int mss;
+	if (!mtu && !mss) {
 		socklen_t mss_size = sizeof(mss);
 		if (!getsockopt(vpninfo->ssl_fd, IPPROTO_TCP, TCP_MAXSEG,
 				&mss, &mss_size)) {
 			vpn_progress(vpninfo, PRG_DEBUG, _("TCP_MAXSEG %d\n"), mss);
-			base_mtu = mss - 13;
 		}
 	}
 #endif
@@ -347,17 +356,79 @@ static int calculate_mtu(struct openconnect_info *vpninfo)
 	if (base_mtu < 1280)
 		base_mtu = 1280;
 
-	if (!mtu) {
-		/* remove IP/UDP and ESP overhead from base MTU to calculate tunnel MTU */
-		mtu = base_mtu - ESP_OVERHEAD - UDP_HEADER_SIZE;
+#ifdef HAVE_ESP
+	/* If we can use the ESP tunnel (got secrets in config),
+	 * then we should pick the optimal MTU for ESP.
+	 */
+	if (!mtu && vpninfo->dtls_state == DTLS_SECRET) {
+		/* remove ESP, UDP, IP headers from base (wire) MTU */
+		mtu = ( base_mtu - UDP_HEADER_SIZE - ESP_HEADER_SIZE
+		        - 12 /* both supported algos (SHA1 and MD5) have 96-bit MAC lengths (RFC2403 and RFC2404) */
+		        - (vpninfo->enc_key_len ? : 32) /* biggest supported IV (AES-256) */ );
 		if (vpninfo->peer_addr->sa_family == AF_INET6)
 			mtu -= IPV6_HEADER_SIZE;
 		else
 			mtu -= IPV4_HEADER_SIZE;
+		/* round down to a multiple of blocksize */
+		mtu -= mtu % (vpninfo->enc_key_len ? : 32);
+		/* subtract ESP footer, which is included in the payload before padding to the blocksize */
+		mtu -= ESP_FOOTER_SIZE;
+
+	} else
+#endif
+
+    /* We are definitely using the TLS tunnel (no ESP secrets)
+	 * so we should base our MTU on the TCP MSS.
+	 */
+	if (!mtu) {
+		if (mss)
+			mtu = mss - 21;
+		else {
+			mtu = base_mtu - TCP_HEADER_SIZE - 21;
+			if (vpninfo->peer_addr->sa_family == AF_INET6)
+				mtu -= IPV6_HEADER_SIZE;
+			else
+				mtu -= IPV4_HEADER_SIZE;
+		}
 	}
 	return mtu;
 }
 
+#ifdef HAVE_ESP
+static int set_esp_algo(struct openconnect_info *vpninfo, const char *s, int hmac)
+{
+	if (hmac) {
+		if (!strcmp(s, "sha1"))		{ vpninfo->esp_hmac = HMAC_SHA1; vpninfo->hmac_key_len = 20; return 0; }
+		if (!strcmp(s, "md5"))		{ vpninfo->esp_hmac = HMAC_MD5;  vpninfo->hmac_key_len = 16; return 0; }
+	} else {
+		if (!strcmp(s, "aes128") || !strcmp(s, "aes-128-cbc"))
+		                                { vpninfo->esp_enc = ENC_AES_128_CBC; vpninfo->enc_key_len = 16; return 0; }
+		if (!strcmp(s, "aes-256-cbc"))	{ vpninfo->esp_enc = ENC_AES_256_CBC; vpninfo->enc_key_len = 32; return 0; }
+	}
+	vpn_progress(vpninfo, PRG_ERR, _("Unknown ESP %s algorithm: %s"), hmac ? "MAC" : "encryption", s);
+	return -ENOENT;
+}
+
+static int get_key_bits(xmlNode *xml_node, unsigned char *dest)
+{
+	int bits = -1;
+	xmlNode *child;
+	char *s, *p;
+
+	for (child = xml_node->children; child; child=child->next) {
+		if (xmlnode_get_text(child, "bits", &s) == 0) {
+			bits = atoi(s);
+			free(s);
+		} else if (xmlnode_get_text(child, "val", &s) == 0) {
+			for (p=s; *p && *(p+1) && (bits-=8)>=0; p+=2)
+				*dest++ = unhex(p);
+			free(s);
+		}
+	}
+	return (bits == 0) ? 0 : -EINVAL;
+}
+#endif
+
 /* Return value:
  *  < 0, on error
  *  = 0, on success; *form is populated
@@ -376,6 +447,8 @@ static int gpst_parse_config_xml(struct openconnect_info *vpninfo, xmlNode *xml_
 	vpninfo->ip_info.addr6 = vpninfo->ip_info.netmask6 = NULL;
 	vpninfo->ip_info.domain = NULL;
 	vpninfo->ip_info.mtu = 0;
+	vpninfo->esp_magic = inet_addr(vpninfo->ip_info.gateway_addr);
+	vpninfo->esp_replay_protect = 1;
 	vpninfo->ssl_times.rekey_method = REKEY_NONE;
 	vpninfo->cstp_options = NULL;
 
@@ -406,11 +479,13 @@ static int gpst_parse_config_xml(struct openconnect_info *vpninfo, xmlNode *xml_
 			free(s);
 		} else if (!xmlnode_get_text(xml_node, "gw-address", &s)) {
 			/* As remarked in oncp.c, "this is a tunnel; having a
-			 * gateway is meaningless."
+			 * gateway is meaningless." See esp_send_probes_gp for the
+			 * gory details of what this field actually means.
 			 */
 			if (strcmp(s, vpninfo->ip_info.gateway_addr))
 				vpn_progress(vpninfo, PRG_DEBUG,
 							 _("Gateway address in config XML (%s) differs from external gateway address (%s).\n"), s, vpninfo->ip_info.gateway_addr);
+			vpninfo->esp_magic = inet_addr(s);
 			free(s);
 		} else if (xmlnode_is_named(xml_node, "dns")) {
 			for (ii=0, member = xml_node->children; member && ii<3; member=member->next)
@@ -438,7 +513,34 @@ static int gpst_parse_config_xml(struct openconnect_info *vpninfo, xmlNode *xml_
 				}
 			}
 		} else if (xmlnode_is_named(xml_node, "ipsec")) {
+#ifdef HAVE_ESP
+			if (vpninfo->dtls_state != DTLS_DISABLED) {
+				int c = (vpninfo->current_esp_in ^= 1);
+				vpninfo->old_esp_maxseq = vpninfo->esp_in[c^1].seq + 32;
+				for (member = xml_node->children; member; member=member->next) {
+					s = NULL;
+					if (!xmlnode_get_text(member, "udp-port", &s))		udp_sockaddr(vpninfo, atoi(s));
+					else if (!xmlnode_get_text(member, "enc-algo", &s)) 	set_esp_algo(vpninfo, s, 0);
+					else if (!xmlnode_get_text(member, "hmac-algo", &s))	set_esp_algo(vpninfo, s, 1);
+					else if (!xmlnode_get_text(member, "c2s-spi", &s))	vpninfo->esp_out.spi = htonl(strtoul(s, NULL, 16));
+					else if (!xmlnode_get_text(member, "s2c-spi", &s))	vpninfo->esp_in[c].spi = htonl(strtoul(s, NULL, 16));
+					else if (xmlnode_is_named(member, "ekey-c2s"))		get_key_bits(member, vpninfo->esp_out.enc_key);
+					else if (xmlnode_is_named(member, "ekey-s2c"))		get_key_bits(member, vpninfo->esp_in[c].enc_key);
+					else if (xmlnode_is_named(member, "akey-c2s"))		get_key_bits(member, vpninfo->esp_out.hmac_key);
+					else if (xmlnode_is_named(member, "akey-s2c"))		get_key_bits(member, vpninfo->esp_in[c].hmac_key);
+					else if (!xmlnode_get_text(member, "ipsec-mode", &s) && strcmp(s, "esp-tunnel"))
+						vpn_progress(vpninfo, PRG_ERR, _("GlobalProtect config sent ipsec-mode=%s (expected esp-tunnel)\n"), s);
+					free(s);
+				}
+				if (setup_esp_keys(vpninfo, 0))
+					vpn_progress(vpninfo, PRG_ERR, "Failed to setup ESP keys.\n");
+				else
+					/* prevent race condition between esp_mainloop() and gpst_mainloop() timers */
+					vpninfo->dtls_times.last_rekey = time(&vpninfo->new_dtls_started);
+			}
+#else
 			vpn_progress(vpninfo, PRG_DEBUG, _("Ignoring ESP keys since ESP support not available in this build\n"));
+#endif
 		}
 	}
 
@@ -450,7 +552,7 @@ static int gpst_parse_config_xml(struct openconnect_info *vpninfo, xmlNode *xml_
 	 * overridden with --force-dpd */
 	if (!vpninfo->ssl_times.dpd)
 		vpninfo->ssl_times.dpd = 10;
-	vpninfo->ssl_times.keepalive = vpninfo->ssl_times.dpd;
+	vpninfo->ssl_times.keepalive = vpninfo->esp_ssl_fallback = vpninfo->ssl_times.dpd;
 
 	return 0;
 }
@@ -595,7 +697,9 @@ static int gpst_connect(struct openconnect_info *vpninfo)
 		monitor_fd_new(vpninfo, ssl);
 		monitor_read_fd(vpninfo, ssl);
 		monitor_except_fd(vpninfo, ssl);
-		vpninfo->ssl_times.last_rekey = vpninfo->ssl_times.last_rx = vpninfo->ssl_times.last_tx = time(NULL);
+		vpninfo->ssl_times.last_rx = vpninfo->ssl_times.last_tx = time(NULL);
+		if (vpninfo->proto->udp_close)
+			vpninfo->proto->udp_close(vpninfo);
 	}
 
 out:
@@ -612,7 +716,13 @@ int gpst_setup(struct openconnect_info *vpninfo)
 	if (ret)
 		return ret;
 
-	ret = gpst_connect(vpninfo);
+	/* We do NOT actually start the HTTPS tunnel yet if we want to
+	 * use ESP, because the ESP tunnel won't work if the HTTPS tunnel
+	 * is connected! >:-(
+	 */
+	if (vpninfo->dtls_state == DTLS_DISABLED || vpninfo->dtls_state == DTLS_NOSECRET)
+		ret = gpst_connect(vpninfo);
+
 	return ret;
 }
 
@@ -623,6 +733,42 @@ int gpst_mainloop(struct openconnect_info *vpninfo, int *timeout)
 	uint16_t ethertype;
 	uint32_t one, zero, magic;
 
+	/* Starting the HTTPS tunnel kills ESP, so we avoid starting
+	 * it if the ESP tunnel is connected or connecting.
+	 */
+	switch (vpninfo->dtls_state) {
+	case DTLS_CONNECTING:
+		openconnect_close_https(vpninfo, 0); /* don't keep stale HTTPS socket */
+		vpn_progress(vpninfo, PRG_INFO,
+			     _("ESP tunnel connected; exiting HTTPS mainloop.\n"));
+		vpninfo->dtls_state = DTLS_CONNECTED;
+	case DTLS_CONNECTED:
+		/* Rekey if needed */
+		if (keepalive_action(&vpninfo->ssl_times, timeout) == KA_REKEY)
+			goto do_rekey;
+		return 0;
+	case DTLS_SECRET:
+	case DTLS_SLEEPING:
+		if (!ka_check_deadline(timeout, time(NULL), vpninfo->new_dtls_started + 5)) {
+			/* Allow 5 seconds after configuration for ESP to start */
+			return 0;
+		} else {
+			/* ... before we switch to HTTPS instead */
+			vpn_progress(vpninfo, PRG_ERR,
+				     _("Failed to connect ESP tunnel; using HTTPS instead.\n"));
+			if (gpst_connect(vpninfo)) {
+				vpninfo->quit_reason = "GPST connect failed";
+				return 1;
+			}
+		}
+		break;
+	case DTLS_NOSECRET:
+		/* HTTPS tunnel already started, or getconfig.esp did not provide any ESP keys */
+	case DTLS_DISABLED:
+		/* ESP is disabled */
+		;
+	}
+
 	if (vpninfo->ssl_fd == -1)
 		goto do_reconnect;
 
@@ -761,6 +907,8 @@ int gpst_mainloop(struct openconnect_info *vpninfo, int *timeout)
 			vpninfo->quit_reason = "GPST reconnect failed";
 			return ret;
 		}
+		if (vpninfo->proto->udp_setup)
+			vpninfo->proto->udp_setup(vpninfo, vpninfo->dtls_attempt_period);
 		return 1;
 
 	case KA_KEEPALIVE:
@@ -800,3 +948,103 @@ int gpst_mainloop(struct openconnect_info *vpninfo, int *timeout)
 	/* Work is not done if we just got rid of packets off the queue */
 	return work_done;
 }
+
+#ifdef HAVE_ESP
+static uint16_t csum(uint16_t *buf, int nwords)
+{
+	uint32_t sum = 0;
+	for(sum=0; nwords>0; nwords--)
+		sum += ntohs(*buf++);
+	sum = (sum >> 16) + (sum &0xffff);
+	sum += (sum >> 16);
+	return htons((uint16_t)(~sum));
+}
+
+int gpst_esp_send_probes(struct openconnect_info *vpninfo)
+{
+	/* The GlobalProtect VPN initiates and maintains the ESP connection
+	 * using specially-crafted ICMP ("ping") packets.
+	 *
+	 * 1) These ping packets have a special magic payload. It must
+	 *    include at least the 16 bytes below. The Windows client actually
+	 *    sends this 56-byte version, but the remaining bytes don't
+	 *    seem to matter:
+	 *
+	 *    "monitor\x00\x00pan ha 0123456789:;<=>? !\"#$%&\'()*+,-./\x10\x11\x12\x13\x14\x15\x16\x18";
+	 *
+	 * 2) The ping packets are addressed to the IP supplied in the
+	 *    config XML as as <gw-address>. In most cases, this is the
+	 *    same as the *external* IP address of the VPN gateway
+	 *    (vpninfo->ip_info.gateway_addr), but in some cases it is a
+	 *    separate address.
+	 *
+	 *    Don't blame me. I didn't design this.
+	 */
+	static char magic[16] = "monitor\x00\x00pan ha ";
+
+	int pktlen, seq;
+	struct pkt *pkt = malloc(sizeof(*pkt) + sizeof(struct ip) + ICMP_MINLEN + sizeof(magic) + vpninfo->pkt_trailer);
+	struct ip *iph = (void *)pkt->data;
+	struct icmp *icmph = (void *)(pkt->data + sizeof(*iph));
+	char *pmagic = (void *)(pkt->data + sizeof(*iph) + ICMP_MINLEN);
+	if (!pkt)
+		return -ENOMEM;
+
+	if (vpninfo->dtls_fd == -1) {
+		int fd = udp_connect(vpninfo);
+		if (fd < 0)
+			return fd;
+
+		/* We are not connected until we get an ESP packet back */
+		vpninfo->dtls_state = DTLS_SLEEPING;
+		vpninfo->dtls_fd = fd;
+		monitor_fd_new(vpninfo, dtls);
+		monitor_read_fd(vpninfo, dtls);
+		monitor_except_fd(vpninfo, dtls);
+	}
+
+	for (seq=1; seq <= (vpninfo->dtls_state==DTLS_CONNECTED ? 1 : 3); seq++) {
+		memset(pkt, 0, sizeof(*pkt) + sizeof(*iph) + ICMP_MINLEN + sizeof(magic));
+		pkt->len = sizeof(struct ip) + ICMP_MINLEN + sizeof(magic);
+
+		/* IP Header */
+		iph->ip_hl = 5;
+		iph->ip_v = 4;
+		iph->ip_len = htons(sizeof(*iph) + ICMP_MINLEN + sizeof(magic));
+		iph->ip_id = htons(0x4747); /* what the Windows client uses */
+		iph->ip_off = htons(IP_DF); /* don't fragment, frag offset = 0 */
+		iph->ip_ttl = 64; /* hops */
+		iph->ip_p = 1; /* ICMP */
+		iph->ip_src.s_addr = inet_addr(vpninfo->ip_info.addr);
+		iph->ip_dst.s_addr = vpninfo->esp_magic;
+		iph->ip_sum = csum((uint16_t *)iph, sizeof(*iph)/2);
+
+		/* ICMP echo request */
+		icmph->icmp_type = ICMP_ECHO;
+		icmph->icmp_hun.ih_idseq.icd_id = htons(0x4747);
+		icmph->icmp_hun.ih_idseq.icd_seq = htons(seq);
+		memcpy(pmagic, magic, sizeof(magic)); /* required to get gateway to respond */
+		icmph->icmp_cksum = csum((uint16_t *)icmph, (ICMP_MINLEN+sizeof(magic))/2);
+
+		pktlen = encrypt_esp_packet(vpninfo, pkt);
+		if (pktlen >= 0)
+			send(vpninfo->dtls_fd, (void *)&pkt->esp, pktlen, 0);
+	}
+
+	free(pkt);
+
+	vpninfo->dtls_times.last_tx = time(&vpninfo->new_dtls_started);
+
+	return 0;
+}
+
+int gpst_esp_catch_probe(struct openconnect_info *vpninfo, struct pkt *pkt)
+{
+	struct ip *iph = (void *)(pkt->data);
+	return ( pkt->len >= 21
+		 && iph->ip_p==1 /* IPv4 protocol field == ICMP */
+		 && iph->ip_src.s_addr == vpninfo->esp_magic /* source == magic address */
+		 && pkt->len >= (iph->ip_hl<<2)+1 /* No short-packet segfaults */
+		 && pkt->data[iph->ip_hl<<2]==0 /* ICMP reply */ );
+}
+#endif /* HAVE_ESP */
diff --git a/library.c b/library.c
index 6e503ab..f14fc93 100644
--- a/library.c
+++ b/library.c
@@ -151,6 +151,14 @@ const struct vpn_proto openconnect_protos[] = {
 		.tcp_mainloop = gpst_mainloop,
 		.add_http_headers = gpst_common_headers,
 		.obtain_cookie = gpst_obtain_cookie,
+#ifdef HAVE_ESP
+		.udp_setup = esp_setup,
+		.udp_mainloop = esp_mainloop,
+		.udp_close = esp_close_secret,
+		.udp_shutdown = esp_shutdown,
+		.udp_send_probes = gpst_esp_send_probes,
+		.udp_catch_probe = gpst_esp_catch_probe,
+#endif
 	},
 	{ /* NULL */ }
 };
diff --git a/mainloop.c b/mainloop.c
index 4124509..fe185fe 100644
--- a/mainloop.c
+++ b/mainloop.c
@@ -315,7 +315,7 @@ int openconnect_mainloop(struct openconnect_info *vpninfo,
 	return ret < 0 ? ret : -EIO;
 }
 
-static int ka_check_deadline(int *timeout, time_t now, time_t due)
+int ka_check_deadline(int *timeout, time_t now, time_t due)
 {
 	if (now >= due)
 		return 1;
diff --git a/openconnect-internal.h b/openconnect-internal.h
index 96bad7c..ba81486 100644
--- a/openconnect-internal.h
+++ b/openconnect-internal.h
@@ -337,7 +337,7 @@ static inline void init_pkt_queue(struct pkt_q *q)
 }
 
 #define DTLS_OVERHEAD (1 /* packet + header */ + 13 /* DTLS header */ + \
-	 20 /* biggest supported MAC (SHA1) */ +  16 /* biggest supported IV (AES-128) */ + \
+	 20 /* biggest supported MAC (SHA1) */ +  32 /* biggest supported IV (AES-256) */ + \
 	 16 /* max padding */)
 
 struct esp {
@@ -378,6 +378,7 @@ struct openconnect_info {
 	struct esp esp_out;
 	int enc_key_len;
 	int hmac_key_len;
+	in_addr_t esp_magic; /* GlobalProtect magic ping address (network-endian) */
 
 	int tncc_fd; /* For Juniper TNCC */
 	const char *csd_xmltag;
@@ -875,6 +876,8 @@ int gpst_xml_or_error(struct openconnect_info *vpninfo, int result, char *respon
 					  char **prompt, char **inputStr);
 int gpst_setup(struct openconnect_info *vpninfo);
 int gpst_mainloop(struct openconnect_info *vpninfo, int *timeout);
+int gpst_esp_send_probes(struct openconnect_info *vpninfo);
+int gpst_esp_catch_probe(struct openconnect_info *vpninfo, struct pkt *pkt);
 
 /* lzs.c */
 int lzs_decompress(unsigned char *dst, int dstlen, const unsigned char *src, int srclen);
@@ -920,6 +923,7 @@ int verify_packet_seqno(struct openconnect_info *vpninfo,
 int esp_setup(struct openconnect_info *vpninfo, int dtls_attempt_period);
 int esp_mainloop(struct openconnect_info *vpninfo, int *timeout);
 void esp_close(struct openconnect_info *vpninfo);
+void esp_close_secret(struct openconnect_info *vpninfo);
 void esp_shutdown(struct openconnect_info *vpninfo);
 int print_esp_keys(struct openconnect_info *vpninfo, const char *name, struct esp *esp);
 
@@ -960,6 +964,7 @@ int tun_mainloop(struct openconnect_info *vpninfo, int *timeout);
 int queue_new_packet(struct pkt_q *q, void *buf, int len);
 int keepalive_action(struct keepalive_info *ka, int *timeout);
 int ka_stalled_action(struct keepalive_info *ka, int *timeout);
+int ka_check_deadline(int *timeout, time_t now, time_t due);
 
 /* xml.c */
 ssize_t read_file_into_string(struct openconnect_info *vpninfo, const char *fname,
diff --git a/www/globalprotect.xml b/www/globalprotect.xml
index 408eb2e..ee45819 100644
--- a/www/globalprotect.xml
+++ b/www/globalprotect.xml
@@ -37,7 +37,10 @@ tunnel configuration information (<tt>POST /ssl-vpn/getconfig.esp</tt>).</p>
       ESP</a> tunnel.</li>
 </ol>
 
-<p>This version of OpenConnect supports <b>only</b> the HTTPS tunnel.</p>
+<p>Since <a href="http://sites.inka.de/~W1011/devel/tcp-tcp.html";>TCP over
+TCP is very suboptimal</a>, OpenConnect tries to always use ESP-over-UDP,
+and will only fall over to the HTTPS tunnel if that fails, or if disabled
+via the <tt>--no-dtls</tt> argument.</p>
 
 <h2>Quirks and issues</h2>
 
@@ -51,14 +54,22 @@ encapsulating each packets within ESP, UDP, and IP.</p>
 <p>There is currently no IPv6 support.  <a
 href="https://live.paloaltonetworks.com/t5/Learning-Articles/IPv6-Support-on-the-Palo-Alto-Networks-Firewall/ta-p/52994";>PAN's
 documentation</a> suggests that recent versions of GlobalProtect may support
-IPv6 over the ESP tunnel, though not the HTTPS tunnel.</p>
+IPv6 over the ESP tunnel, though not the SSL tunnel.</p>
+
+<p>The ESP and HTTPS tunnels cannot be connected simultaneously.  The ESP
+tunnel becomes unresponsive as soon as the HTTPS tunnel is started, and
+remains so unless/until the tunnel is closed and the configuration is
+re-fetched.</p>
 
 <p>Compared to the AnyConnect or Juniper protocols, the GlobalProtect
 protocol appears to have very little in the way of <a
 href="https://en.wikipedia.org/wiki/In-band_signaling";>in-band
 signaling</a>.  The HTTPS tunnel can only send or receive IPv4 packets and a
 simple DPD/keepalive packet (always sent by the client and echoed by the
-server).</p>
+server).  The ESP tunnel does not have any special DPD/keepalive packet, but
+uses an <a
+href="https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol";>ICMP</a>
+("ping") request to the server with a magic payload for this purpose</p>
 
 	<INCLUDE file="inc/footer.tmpl" />
 </PAGE>
-- 
2.7.4




[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