Skip to content

Commit

Permalink
Add support for GlobalProtect ESP tunnel
Browse files Browse the repository at this point in the history
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@gmail.com>
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
  • Loading branch information
dlenski authored and dwmw2 committed May 31, 2018
1 parent 8e7efd5 commit 1ad22fc
Show file tree
Hide file tree
Showing 7 changed files with 453 additions and 34 deletions.
21 changes: 16 additions & 5 deletions esp.c
Expand Up @@ -24,6 +24,13 @@
#include <stdlib.h>
#include <errno.h>

#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include "win32-ipicmp.h"
#else
#endif

#include "openconnect-internal.h"
#include "lzo.h"

Expand Down Expand Up @@ -103,15 +110,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;
Expand Down Expand Up @@ -304,6 +308,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]);
Expand Down

0 comments on commit 1ad22fc

Please sign in to comment.