From d6db0ec03394234d41fbec7ffc794ceeb486a8f0 Mon Sep 17 00:00:00 2001
From: Daniel Lenski
Date: Fri, 12 Jan 2018 01:44:17 -0800
Subject: [PATCH] Incomplete, speculative IPv6 for GlobalProtect
Client-side IPv6 support was added in v4.0:
https://live.paloaltonetworks.com/t5/Colossal-Event-Blog/New-GlobalProtect-4-0-announced-with-IPv6-support/ba-p/141593
Server-side IPv6 support was added in PanOS 8.0:
https://www.paloaltonetworks.com/documentation/80/pan-os/newfeaturesguide/globalprotect-features
I've been wanting to get IPv6 working for a while, but don't have access to
a GP VPN that supports IPv6, and haven't found anyone else who does. I'm
adding incomplete, speculative IPv6 support here in the hopes that someone
will use it and report back on partial success/failure:
* Known from Windows client: `ipv6-support=yes` in `/ssl-vpn/login.esp`
request, `preferred-ipv6` in `/ssl-vpn/getconfig.esp` request,
`client-ipv6` in `/ssl-vpn/hipreport{,check}.esp` requests,
`app-version=4.0.5-8`,
* Educated guess: 0x0800 in GPST packet header represents IPv4 ethertype,
and will be replaced with 0x86DD for IPv6 packets.
* Unknown: IPv6 routing configuration tags to expect in
`/ssl-vpn/getconfig.esp` response. This build prints a prominent
error message if it encounters any unknown configuration tags
containing the character '6', and requests feedback to the mailing
list.
Signed-off-by: Daniel Lenski
---
auth-globalprotect.c | 1 +
gpst.c | 84 ++++++++++++++++++++++++++++++++++---------
trojans/hipreport.sh | 22 ++++++------
www/globalprotect.xml | 12 ++++---
www/hip.xml | 6 ++--
5 files changed, 90 insertions(+), 35 deletions(-)
diff --git a/auth-globalprotect.c b/auth-globalprotect.c
index 8cc543b5..ca9331f9 100644
--- a/auth-globalprotect.c
+++ b/auth-globalprotect.c
@@ -489,6 +489,7 @@ static int gpst_login(struct openconnect_info *vpninfo, int portal, struct login
/* submit gateway login (ssl-vpn/login.esp) or portal config (global-protect/getconfig.esp) request */
buf_truncate(request_body);
buf_append(request_body, "jnlpReady=jnlpReady&ok=Login&direct=yes&clientVer=4100&prot=https:");
+ append_opt(request_body, "ipv6-support", vpninfo->disable_ipv6 ? "no" : "yes");
if (!strcmp(vpninfo->platname, "mac-intel") || !strcmp(vpninfo->platname, "apple-ios"))
append_opt(request_body, "clientos", "Mac");
else if (!strcmp(vpninfo->platname, "linux-64") || !strcmp(vpninfo->platname, "android"))
diff --git a/gpst.c b/gpst.c
index 390411d0..7d123d75 100644
--- a/gpst.c
+++ b/gpst.c
@@ -561,13 +561,31 @@ static int gpst_parse_config_xml(struct openconnect_info *vpninfo, xmlNode *xml_
#else
vpn_progress(vpninfo, PRG_DEBUG, _("Ignoring ESP keys since ESP support not available in this build\n"));
#endif
+ } else if (xmlnode_is_named(xml_node, "need-tunnel")
+ || xmlnode_is_named(xml_node, "bw-c2s")
+ || xmlnode_is_named(xml_node, "bw-s2c")
+ || xmlnode_is_named(xml_node, "default-gateway")
+ || xmlnode_is_named(xml_node, "no-direct-access-to-local-network")
+ || xmlnode_is_named(xml_node, "ip-address-preferred")
+ || xmlnode_is_named(xml_node, "portal")
+ || xmlnode_is_named(xml_node, "user")) {
+ /* XX: Do these have any potential value at all for routing configuration or diagnostics? */
+ } else if (xml_node->type == XML_ELEMENT_NODE) {
+ /* XX: Don't know what tags are used for IPv6 addresses and networks, since
+ * we haven't yet seen a real GlobalProtect VPN with IPv6 internal addresses.
+ */
+ free(s);
+ s = (char *)xmlNodeGetContent(xml_node);
+ if (strchr((char *)xml_node->name, '6'))
+ vpn_progress(vpninfo, PRG_ERR, _("Potential IPv6-related GlobalProtect config tag <%s>: %s\n"
+ "This build does not support GlobalProtect IPv6 due to a lack of\n"
+ "of information on how it is configured. Please report this\n"
+ "to .\n"), xml_node->name, s);
+ else
+ vpn_progress(vpninfo, PRG_DEBUG, _("Unknown GlobalProtect config tag <%s>: %s\n"), xml_node->name, s);
}
}
- /* No IPv6 support for SSL VPN:
- * https://live.paloaltonetworks.com/t5/Learning-Articles/IPv6-Support-on-the-Palo-Alto-Networks-Firewall/ta-p/52994 */
- openconnect_disable_ipv6(vpninfo);
-
/* Set 10-second DPD/keepalive (same as Windows client) unless
* overridden with --force-dpd */
if (!vpninfo->ssl_times.dpd)
@@ -585,12 +603,13 @@ static int gpst_get_config(struct openconnect_info *vpninfo)
struct oc_text_buf *request_body = buf_alloc();
struct oc_vpn_option *old_cstp_opts = vpninfo->cstp_options;
const char *old_addr = vpninfo->ip_info.addr, *old_netmask = vpninfo->ip_info.netmask;
+ const char *old_addr6 = vpninfo->ip_info.addr6, *old_netmask6 = vpninfo->ip_info.netmask6;
const char *request_body_type = "application/x-www-form-urlencoded";
const char *method = "POST";
char *xml_buf=NULL;
/* submit getconfig request */
- buf_append(request_body, "client-type=1&protocol-version=p1&app-version=3.0.1-10");
+ buf_append(request_body, "client-type=1&protocol-version=p1&app-version=4.0.5-8");
if (!strcmp(vpninfo->platname, "mac-intel") || !strcmp(vpninfo->platname, "apple-ios"))
append_opt(request_body, "clientos", "Mac");
else if (!strcmp(vpninfo->platname, "linux-64") || !strcmp(vpninfo->platname, "android"))
@@ -600,9 +619,10 @@ static int gpst_get_config(struct openconnect_info *vpninfo)
append_opt(request_body, "os-version", vpninfo->platname);
append_opt(request_body, "hmac-algo", "sha1,md5");
append_opt(request_body, "enc-algo", "aes-128-cbc,aes-256-cbc");
- if (old_addr) {
+ if (old_addr || old_addr6) {
append_opt(request_body, "preferred-ip", old_addr);
- filter_opts(request_body, vpninfo->cookie, "preferred-ip", 0);
+ append_opt(request_body, "preferred-ipv6", old_addr6);
+ filter_opts(request_body, vpninfo->cookie, "preferred-ip,preferred-ipv6", 0);
} else
buf_append(request_body, "&%s", vpninfo->cookie);
if ((result = buf_error(request_body)))
@@ -638,7 +658,8 @@ static int gpst_get_config(struct openconnect_info *vpninfo)
no_esp_reason ? "SSL tunnel. " : "ESP tunnel", no_esp_reason ? : "");
/* return -EINVAL; */
}
- if (!vpninfo->ip_info.addr) {
+ if (!vpninfo->ip_info.addr && !vpninfo->ip_info.addr6 &&
+ !vpninfo->ip_info.netmask6) {
vpn_progress(vpninfo, PRG_ERR,
_("No IP address received. Aborting\n"));
result = -EINVAL;
@@ -662,6 +683,22 @@ static int gpst_get_config(struct openconnect_info *vpninfo)
goto out;
}
}
+ if (old_addr6) {
+ if (strcmp(old_addr6, vpninfo->ip_info.addr6)) {
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Reconnect gave different IPv6 address (%s != %s)\n"),
+ vpninfo->ip_info.addr6, old_addr6);
+ return -EINVAL;
+ }
+ }
+ if (old_netmask6) {
+ if (strcmp(old_netmask6, vpninfo->ip_info.netmask6)) {
+ vpn_progress(vpninfo, PRG_ERR,
+ _("Reconnect gave different IPv6 netmask (%s != %s)\n"),
+ vpninfo->ip_info.netmask6, old_netmask6);
+ return -EINVAL;
+ }
+ }
out:
free_optlist(old_cstp_opts);
@@ -820,7 +857,10 @@ static int check_or_submit_hip_report(struct openconnect_info *vpninfo, const ch
/* cookie gives us these fields: authcookie, portal, user, domain, computer, and (maybe the unnecessary) preferred-ip */
buf_append(request_body, "client-role=global-protect-full&%s", vpninfo->cookie);
- append_opt(request_body, "client-ip", vpninfo->ip_info.addr);
+ if (vpninfo->ip_info.addr)
+ append_opt(request_body, "client-ip", vpninfo->ip_info.addr);
+ if (vpninfo->ip_info.addr6)
+ append_opt(request_body, "client-ipv6", vpninfo->ip_info.addr6);
if (report) {
/* XML report contains many characters requiring URL-encoding (%xx) */
buf_ensure_space(request_body, strlen(report)*3);
@@ -932,8 +972,14 @@ static int run_hip_script(struct openconnect_info *vpninfo)
hip_argv[i++] = openconnect_utf8_to_legacy(vpninfo, vpninfo->csd_wrapper);
hip_argv[i++] = (char *)"--cookie";
hip_argv[i++] = vpninfo->cookie;
- hip_argv[i++] = (char *)"--client-ip";
- hip_argv[i++] = (char *)vpninfo->ip_info.addr;
+ if (vpninfo->ip_info.addr) {
+ hip_argv[i++] = (char *)"--client-ip";
+ hip_argv[i++] = (char *)vpninfo->ip_info.addr;
+ }
+ if (vpninfo->ip_info.addr6) {
+ hip_argv[i++] = (char *)"--client-ipv6";
+ hip_argv[i++] = (char *)vpninfo->ip_info.addr6;
+ }
hip_argv[i++] = (char *)"--md5";
hip_argv[i++] = vpninfo->csd_token;
hip_argv[i++] = NULL;
@@ -1090,9 +1136,10 @@ int gpst_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable)
}
continue;
case 0x0800:
+ case 0x86DD:
vpn_progress(vpninfo, PRG_TRACE,
- _("Received data packet of %d bytes\n"),
- payload_len);
+ _("Received IPv%d data packet of %d bytes\n"),
+ ethertype == 0x86DD ? 6 : 4, payload_len);
vpninfo->cstp_pkt->len = payload_len;
queue_packet(&vpninfo->incoming_queue, vpninfo->cstp_pkt);
vpninfo->cstp_pkt = NULL;
@@ -1193,16 +1240,19 @@ int gpst_mainloop(struct openconnect_info *vpninfo, int *timeout, int readable)
(vpninfo->current_ssl_pkt = dequeue_packet(&vpninfo->outgoing_queue))) {
struct pkt *this = vpninfo->current_ssl_pkt;
+ /* IPv4 or IPv6 EtherType */
+ int ethertype = this->len && (this->data[0] & 0xF0) == 0x60 ? 0x86DD : 0x0800;
+
/* store header */
store_be32(this->gpst.hdr, 0x1a2b3c4d);
- store_be16(this->gpst.hdr + 4, 0x0800); /* IPv4 EtherType */
+ store_be16(this->gpst.hdr + 4, ethertype);
store_be16(this->gpst.hdr + 6, this->len);
store_le32(this->gpst.hdr + 8, 1);
store_le32(this->gpst.hdr + 12, 0);
vpn_progress(vpninfo, PRG_TRACE,
- _("Sending data packet of %d bytes\n"),
- this->len);
+ _("Sending IPv%d data packet of %d bytes\n"),
+ (ethertype == 0x86DD ? 6 : 4), this->len);
goto handle_outgoing;
}
@@ -1304,7 +1354,7 @@ int gpst_esp_catch_probe(struct openconnect_info *vpninfo, struct pkt *pkt)
{
struct ip *iph = (void *)(pkt->data);
- return ( pkt->len >= 21
+ return ( pkt->len >= 21 && iph->ip_v==4 /* IPv4 header */
&& 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) + ICMP_MINLEN + sizeof(magic_ping_payload) /* No short-packet segfaults */
diff --git a/trojans/hipreport.sh b/trojans/hipreport.sh
index e3e9deb0..9ac04b56 100755
--- a/trojans/hipreport.sh
+++ b/trojans/hipreport.sh
@@ -8,9 +8,9 @@
# --authenticate --protocol=gp, which includes parameters
# from the /ssl-vpn/login.esp response
#
-# --client-ip: IPv4 address allocated by the GlobalProtect VPN for
-# this client (included in /ssl-vpn/getconfig.esp
-# response)
+# --client-ip{,v6}: IPv4/6 addresses allocated by the GlobalProtect
+# VPN for this client (included in
+# /ssl-vpn/getconfig.esp response)
#
# --md5: The md5 digest to encode into this HIP report. I'm not sure
# exactly what this is the md5 digest *of*, but all that
@@ -20,17 +20,19 @@
# Read command line arguments into variables
COOKIE=
IP=
+IPv6=
MD5=
while [ "$1" ]; do
- if [ "$1" = "--cookie" ]; then shift; COOKIE="$1"; fi
- if [ "$1" = "--client-ip" ]; then shift; IP="$1"; fi
- if [ "$1" = "--md5" ]; then shift; MD5="$1"; fi
+ if [ "$1" = "--cookie" ]; then shift; COOKIE="$1"; fi
+ if [ "$1" = "--client-ip" ]; then shift; IP="$1"; fi
+ if [ "$1" = "--client-ipv6" ]; then shift; IPV6="$1"; fi
+ if [ "$1" = "--md5" ]; then shift; MD5="$1"; fi
shift
done
-if [ -z "$COOKIE" -o -z "$IP" -o -z "$MD5" ]; then
- echo "Parameters --cookie, --client-ip, and --md5 are required" >&2
+if [ -z "$COOKIE" -o -z "$MD5" -o -z "$IP$IPV6" ]; then
+ echo "Parameters --cookie, --md5, and --client-ip and/or --client-ipv6 are required" >&2
exit 1;
fi
@@ -58,7 +60,7 @@ cat <$COMPUTER
$HOSTID
$IP
-
+ $IPV6
$NOW
@@ -76,7 +78,7 @@ cat <
-
+
diff --git a/www/globalprotect.xml b/www/globalprotect.xml
index a9de423a..3984b3c8 100644
--- a/www/globalprotect.xml
+++ b/www/globalprotect.xml
@@ -64,10 +64,12 @@ always shows . OpenConnect attempt
calculate the MTU by starting from the base MTU with the overhead of
encapsulating each packets within ESP, UDP, and IP.
-There is currently no IPv6 support. PAN's
-documentation suggests that recent versions of GlobalProtect may support
-IPv6 over the ESP tunnel, though not the SSL tunnel.
+IPv6 support was added in GlobalProtect
+4.0 in 2017 but OpenConnect support for GlobalProtect IPv6 is incomplete
+due to developers' lack of access to a GlobalProtect VPN server that supports it.
+If you have access to a GlobalProtect VPN that supports IPv6, please send information
+to the mailing list so we can add complete support.
The ESP and HTTPS tunnels cannot be connected simultaneously. The ESP
tunnel becomes unresponsive as soon as the HTTPS tunnel is started, and
@@ -77,7 +79,7 @@ re-fetched.
Compared to the AnyConnect or Juniper protocols, the GlobalProtect
protocol appears to have very little in the way of in-band
-signaling. The HTTPS tunnel can only send or receive IPv4 packets and a
+signaling. The HTTPS tunnel can only send or receive IP packets and a
simple DPD/keepalive packet (always sent by the client and echoed by the
server). The ESP tunnel does not have any special DPD/keepalive packet, but
uses an