From: Daniel Lenski Date: Sat, 16 May 2020 22:51:46 +0000 (-0700) Subject: allegedly universal MTU calculator: use for GPST and PPP X-Git-Tag: v8.20~328^2~3 X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=2b90f33353598ffb0540e6ab5e303107fe9c703a;p=users%2Fdwmw2%2Fopenconnect.git allegedly universal MTU calculator: use for GPST and PPP Signed-off-by: Daniel Lenski --- diff --git a/Makefile.am b/Makefile.am index 636e183f..6b512500 100644 --- a/Makefile.am +++ b/Makefile.am @@ -30,7 +30,7 @@ openconnect_LDADD = libopenconnect.la $(SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIB if OPENCONNECT_WIN32 openconnect_SOURCES += openconnect.rc endif -library_srcs = ssl.c http.c http-auth.c auth-common.c auth-html.c library.c compat.c lzs.c mainloop.c script.c ntlm.c digest.c openconnect-internal.h +library_srcs = ssl.c http.c http-auth.c auth-common.c auth-html.c library.c compat.c lzs.c mainloop.c script.c ntlm.c digest.c mtucalc.c openconnect-internal.h lib_srcs_cisco = auth.c cstp.c lib_srcs_juniper = oncp.c lzo.c auth-juniper.c lib_srcs_pulse = pulse.c diff --git a/cstp.c b/cstp.c index 280e4242..c2c6c83b 100644 --- a/cstp.c +++ b/cstp.c @@ -96,7 +96,7 @@ static const struct pkt dpd_resp_pkt = { * If we don't even have TCP_MAXSEG, then default to sending a legacy MTU of * 1406 which is what we always used to do. */ -static void calculate_mtu(struct openconnect_info *vpninfo, int *base_mtu, int *mtu) +static void calculate_dtls_mtu(struct openconnect_info *vpninfo, int *base_mtu, int *mtu) { *mtu = vpninfo->reqmtu; *base_mtu = vpninfo->basemtu; @@ -275,7 +275,7 @@ static int start_cstp_connection(struct openconnect_info *vpninfo) free_split_routes(vpninfo); retry: - calculate_mtu(vpninfo, &base_mtu, &mtu); + calculate_dtls_mtu(vpninfo, &base_mtu, &mtu); vpninfo->cstp_basemtu = base_mtu; reqbuf = buf_alloc(); diff --git a/gpst.c b/gpst.c index 42d290d6..345d5143 100644 --- a/gpst.c +++ b/gpst.c @@ -318,93 +318,6 @@ out: #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 can_use_esp) -{ - int mtu = vpninfo->reqmtu, base_mtu = vpninfo->basemtu; - int mss = 0; - -#if defined(__linux__) && defined(TCP_INFO) - if (!mtu) { - struct tcp_info ti; - socklen_t ti_size = sizeof(ti); - - if (!getsockopt(vpninfo->ssl_fd, IPPROTO_TCP, TCP_INFO, - &ti, &ti_size)) { - vpn_progress(vpninfo, PRG_DEBUG, - _("TCP_INFO rcv mss %d, snd mss %d, adv mss %d, pmtu %d\n"), - ti.tcpi_rcv_mss, ti.tcpi_snd_mss, ti.tcpi_advmss, ti.tcpi_pmtu); - - if (!base_mtu) { - base_mtu = ti.tcpi_pmtu; - } - - /* 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 (!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); - } - } -#endif - if (!base_mtu) { - /* Default */ - base_mtu = 1406; - } - - if (base_mtu < 1280) - base_mtu = 1280; - -#ifdef HAVE_ESP - /* If we can use the ESP tunnel then we should pick the optimal MTU for ESP. */ - if (!mtu && can_use_esp) { - /* remove ESP, UDP, IP headers from base (wire) MTU */ - mtu = ( base_mtu - UDP_HEADER_SIZE - ESP_HEADER_SIZE - - vpninfo->hmac_out_len - - MAX_IV_SIZE); - if (vpninfo->peer_addr->sa_family == AF_INET6) - mtu -= IPV6_HEADER_SIZE; - else - mtu -= IPV4_HEADER_SIZE; - /* round down to a multiple of blocksize (16 bytes for both AES-128 and AES-256) */ - mtu -= mtu % 16; - /* 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, 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 check_hmac_algo(struct openconnect_info *v, const char *s) @@ -746,7 +659,15 @@ static int gpst_get_config(struct openconnect_info *vpninfo) #else no_esp_reason = _("ESP support not available in this build"); #endif - vpninfo->ip_info.mtu = calculate_mtu(vpninfo, !no_esp_reason); + if (!no_esp_reason) + vpninfo->ip_info.mtu = calculate_mtu( + vpninfo, 1, + ESP_HEADER_SIZE + vpninfo->hmac_out_len + MAX_IV_SIZE, /* ESP header size */ + ESP_FOOTER_SIZE, /* ESP footer (contributes to payload before padding) */ + 16 /* blocksize for both AES-128 and AES-256 */ ); + else + vpninfo->ip_info.mtu = calculate_mtu(vpninfo, 0, TLS_OVERHEAD, 0, 1); + vpn_progress(vpninfo, PRG_ERR, _("No MTU received. Calculated %d for %s%s\n"), vpninfo->ip_info.mtu, no_esp_reason ? "SSL tunnel. " : "ESP tunnel", no_esp_reason ? : ""); diff --git a/mtucalc.c b/mtucalc.c new file mode 100644 index 00000000..7da9ebff --- /dev/null +++ b/mtucalc.c @@ -0,0 +1,119 @@ +/* + * OpenConnect (SSL + DTLS) VPN client + * + * Copyright © 2020 Daniel Lenski + * + * Authors: Daniel Lenski + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + +#include +#include "openconnect-internal.h" + +#if defined(__linux__) +/* For TCP_INFO */ +# include +#endif + +#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 + +/* Calculate MTU of a tunnel. + * + * is_udp: 1 if outer packet is UDP, 0 if TCP + * unpadded_overhead: overhead that does not get padded (headers or footers) + * padded_overhead: overhead that gets before padding the payload (typically footers) + * block_size: block size that payload will be padded to AFTER adding padded overhead + */ + +int calculate_mtu(struct openconnect_info *vpninfo, int is_udp, + int unpadded_overhead, int padded_overhead, int block_size) +{ + int mtu = vpninfo->reqmtu, base_mtu = vpninfo->basemtu; + int mss = 0; + + /* Try to figure out base_mtu (from TCP PMTU), and save TCP MSS for later */ +#if defined(__linux__) && defined(TCP_INFO) + if (!mtu) { + struct tcp_info ti; + socklen_t ti_size = sizeof(ti); + + if (!getsockopt(vpninfo->ssl_fd, IPPROTO_TCP, TCP_INFO, + &ti, &ti_size)) { + vpn_progress(vpninfo, PRG_DEBUG, + _("TCP_INFO rcv mss %d, snd mss %d, adv mss %d, pmtu %d\n"), + ti.tcpi_rcv_mss, ti.tcpi_snd_mss, ti.tcpi_advmss, ti.tcpi_pmtu); + + /* If base_mtu unknown, use TCP PMTU */ + if (!base_mtu) { + base_mtu = ti.tcpi_pmtu; + } + + /* Use largest MSS */ + mss = MAX(ti.tcpi_rcv_mss, ti.tcpi_snd_mss); + mss = MAX(mss, ti.tcpi_advmss); + } + } +#endif +#ifdef TCP_MAXSEG + 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); + } + } +#endif + + /* Default base_mtu needs to be big enough for IPv6 (1280 minimum) */ + if (!base_mtu) { + /* Default */ + base_mtu = 1406; + } + + if (base_mtu < 1280) + base_mtu = 1280; + + vpn_progress(vpninfo, PRG_TRACE, _("Using base_mtu of %d\n"), base_mtu); + + /* base_mtu is now (we hope) the PMTU between our external network interface + * and the VPN gateway */ + + if (!mtu) { + if (!is_udp && mss) + /* MSS already has IP, TCP header size removed */ + mtu = mss; + else { + /* remove TCP/UDP, IP headers from base (wire) MTU */ + mtu = base_mtu - (is_udp ? UDP_HEADER_SIZE : TCP_HEADER_SIZE); + mtu -= (vpninfo->peer_addr->sa_family == AF_INET6) ? IPV6_HEADER_SIZE : IPV4_HEADER_SIZE; + } + } + + vpn_progress(vpninfo, PRG_TRACE, _("After removing %s/IPv%d headers, MTU of %d\n"), + (is_udp ? "UDP" : "TCP"), vpninfo->peer_addr->sa_family == AF_INET6 ? 6 : 4, mtu); + + /* MTU is now (we hope) the number of payload bytes that can fit in a UDP or + * TCP packet exchanged with the VPN gateway. */ + + mtu -= unpadded_overhead; /* remove protocol-specific overhead that isn't affected by padding */ + mtu -= mtu % block_size; /* round down to a multiple of blocksize */ + mtu -= padded_overhead; /* remove protocol-specific overhead that contributes to payload padding */ + + vpn_progress(vpninfo, PRG_TRACE, _("After removing protocol specific overhead (%d unpadded, %d padded, %d blocksize), MTU of %d\n"), + unpadded_overhead, padded_overhead, block_size, mtu); + + return mtu; +} diff --git a/openconnect-internal.h b/openconnect-internal.h index b9a00456..974248dc 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -351,6 +351,7 @@ static inline void init_pkt_queue(struct pkt_q *q) q->tail = &q->head; } +#define TLS_OVERHEAD 5 /* packet + header */ #define DTLS_OVERHEAD (1 /* packet + header */ + 13 /* DTLS header */ + \ 20 /* biggest supported MAC (SHA1) */ + 32 /* biggest supported IV (AES-256) */ + \ 16 /* max padding */) @@ -910,6 +911,10 @@ int openconnect_dtls_write(struct openconnect_info *vpninfo, void *buf, size_t l char *openconnect_bin2hex(const char *prefix, const uint8_t *data, unsigned len); char *openconnect_bin2base64(const char *prefix, const uint8_t *data, unsigned len); +/* mtucalc.c */ + +int calculate_mtu(struct openconnect_info *vpninfo, int is_udp, int unpadded_overhead, int padded_overhead, int block_size); + /* cstp.c */ int check_address_sanity(struct openconnect_info *vpninfo, const char *old_addr, const char *old_netmask, const char *old_addr6, const char *old_netmask6); void cstp_common_headers(struct openconnect_info *vpninfo, struct oc_text_buf *buf);