diff --git a/openvpn/client/cliproto.hpp b/openvpn/client/cliproto.hpp index 3d228715..d2c005e6 100644 --- a/openvpn/client/cliproto.hpp +++ b/openvpn/client/cliproto.hpp @@ -381,9 +381,12 @@ namespace openvpn { if (buf.size()) { const ProtoContext::Config& c = Base::conf(); - if (c.mss_inter > 0 && buf.size() > c.mss_inter) + // when calculating mss, we take IPv4 and TCP headers into account + // here we need to add it back since we check the whole IP packet size, not just TCP payload + size_t mss_no_tcp_ip_encap = (size_t)c.mss_fix + (20 + 20); + if (c.mss_fix > 0 && buf.size() > mss_no_tcp_ip_encap) { - Ptb::generate_icmp_ptb(buf, c.mss_inter); + Ptb::generate_icmp_ptb(buf, mss_no_tcp_ip_encap); tun->tun_send(buf); } else diff --git a/openvpn/dco/dcocli.hpp b/openvpn/dco/dcocli.hpp index 60d626df..ebc1efa7 100644 --- a/openvpn/dco/dcocli.hpp +++ b/openvpn/dco/dcocli.hpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -93,7 +94,7 @@ public: // set a default MTU if (!tun.tun_prop.mtu) - tun.tun_prop.mtu = 1500; + tun.tun_prop.mtu = TUN_MTU_DEFAULT; // parse "dev" option { diff --git a/openvpn/ssl/mssparms.hpp b/openvpn/ssl/mssparms.hpp index 7289fb02..3d73e692 100644 --- a/openvpn/ssl/mssparms.hpp +++ b/openvpn/ssl/mssparms.hpp @@ -28,10 +28,9 @@ namespace openvpn { struct MSSParms { - MSSParms() : mssfix(0), - mtu(false) - { - } + enum { + MSSFIX_DEFAULT = 1492, + }; void parse(const OptionList& opt, bool nothrow=false) { @@ -44,6 +43,7 @@ namespace openvpn { if (nothrow) { OPENVPN_LOG("Missing mssfix value, mssfix functionality disabled"); + mssfix_default = false; return; } else @@ -63,17 +63,25 @@ namespace openvpn { if (*val != "0") { OPENVPN_LOG("Invalid mssfix value " << *val << ", mssfix functionality disabled"); + mssfix_default = false; } } else throw option_error("mssfix: parse/range issue"); } + else + { + mssfix_default = false; + } mtu = (o->get_optional(2, 16) == "mtu"); + fixed = (o->get_optional(2, 16) == "fixed"); } } - unsigned int mssfix; // standard OpenVPN mssfix parm - bool mtu; // consider transport packet overhead in MSS adjustment + unsigned int mssfix = 0; // standard OpenVPN mssfix parm + bool mtu = false; // include overhead from IP and TCP/UDP encapsulation + bool fixed = false; // use mssfix value without any encapsulation adjustments + bool mssfix_default = true; }; struct MSSCtrlParms diff --git a/openvpn/ssl/proto.hpp b/openvpn/ssl/proto.hpp index 0c0f56af..faf46e3c 100644 --- a/openvpn/ssl/proto.hpp +++ b/openvpn/ssl/proto.hpp @@ -351,9 +351,9 @@ namespace openvpn { int local_peer_id = -1; // -1 to disable // MTU - unsigned int tun_mtu = 1500; + unsigned int tun_mtu = TUN_MTU_DEFAULT; MSSParms mss_parms; - unsigned int mss_inter = 0; + unsigned int mss_fix = 0; // Debugging int debug_level = 1; @@ -571,6 +571,19 @@ namespace openvpn { // mssfix mss_parms.parse(opt, true); + if (mss_parms.mssfix_default) + { + if (tun_mtu == TUN_MTU_DEFAULT) + { + mss_parms.mssfix = MSSParms::MSSFIX_DEFAULT; + mss_parms.mtu = true; + } + else + { + mss_parms.mssfix = tun_mtu; + mss_parms.fixed = true; + } + } // load parameters that can be present in both config file or pushed options load_common(opt, pco, server ? LOAD_COMMON_SERVER : LOAD_COMMON_CLIENT); @@ -1636,8 +1649,8 @@ namespace openvpn { compress->decompress(buf); // set MSS for segments server can receive - if (proto.config->mss_inter > 0) - MSSFix::mssfix(buf, proto.config->mss_inter); + if (proto.config->mss_fix > 0) + MSSFix::mssfix(buf, proto.config->mss_fix); } else buf.reset_size(); // no crypto context available @@ -1830,6 +1843,67 @@ namespace openvpn { dck.swap(data_channel_key); } + void calculate_mssfix(Config& c) + { + if (c.mss_parms.fixed) + { + // substract IPv4 and TCP overhead, mssfix method will add extra 20 bytes for IPv6 + c.mss_fix = c.mss_parms.mssfix - (20 + 20); + OPENVPN_LOG("fixed mssfix=" << c.mss_fix); + return; + } + + int payload_overhead = 0; + + // compv2 doesn't increase payload size + switch (c.comp_ctx.type()) + { + case CompressContext::NONE: + case CompressContext::COMP_STUBv2: + case CompressContext::LZ4v2: + break; + default: + payload_overhead += 1; + } + + if (CryptoAlgs::mode(c.dc.cipher()) == CryptoAlgs::CBC_HMAC) + payload_overhead += PacketID::size(PacketID::SHORT_FORM); + + // account for IPv4 and TCP headers of the payload, mssfix method will add 20 extra bytes if payload is IPv6 + payload_overhead += 20 + 20; + + int overhead = c.protocol.extra_transport_bytes() + + (enable_op32 ? OP_SIZE_V2 : 1) + + c.dc.context().encap_overhead(); + + // in CBC mode, the packet id is part of the payload size / overhead + if (CryptoAlgs::mode(c.dc.cipher()) != CryptoAlgs::CBC_HMAC) + overhead += PacketID::size(PacketID::SHORT_FORM); + + if (c.mss_parms.mtu) + { + overhead += c.protocol.is_ipv6() ? sizeof(struct IPv6Header) : sizeof(struct IPv4Header); + overhead += proto.is_tcp() ? sizeof(struct TCPHeader) : sizeof(struct UDPHeader); + } + + int target = c.mss_parms.mssfix - overhead; + if (CryptoAlgs::mode(c.dc.cipher()) == CryptoAlgs::CBC_HMAC) + { + // openvpn3 crypto includes blocksize in overhead, but we can be a bit smarter here + // and instead make sure that resulting ciphertext size (which is always multiple blocksize) + // is not larger than target by running down target to the nearest multiple of multiple and substracting 1. + + int block_size = CryptoAlgs::block_size(c.dc.cipher()); + target += block_size; + target = (target / block_size) * block_size; + target -= 1; + } + + c.mss_fix = target - payload_overhead; + OPENVPN_LOG("mssfix=" << c.mss_fix << " (upper bound=" << c.mss_parms.mssfix << ", overhead=" << + overhead << ", payload_overhead=" << payload_overhead << ", target=" << target << ")"); + } + // Initialize the components of the OpenVPN data channel protocol void init_data_channel() { @@ -1890,34 +1964,7 @@ namespace openvpn { // cache op32 for hot path in do_encrypt cache_op32(); - int crypto_encap = (enable_op32 ? OP_SIZE_V2 : 1) + - c.comp_ctx.extra_payload_bytes() + - PacketID::size(PacketID::SHORT_FORM) + - c.dc.context().encap_overhead(); - - int transport_encap = 0; - if (c.mss_parms.mtu) - { - if (proto.is_tcp()) - transport_encap += sizeof(struct TCPHeader); - else - transport_encap += sizeof(struct UDPHeader); - - if (c.protocol.is_ipv6()) - transport_encap += sizeof(struct IPv6Header); - else - transport_encap += sizeof(struct IPv4Header); - - transport_encap += c.protocol.extra_transport_bytes(); - } - - if (c.mss_parms.mssfix != 0) - { - OPENVPN_LOG_PROTO("MTU mssfix=" << c.mss_parms.mssfix << - " crypto_encap=" << crypto_encap << - " transport_encap=" << transport_encap); - c.mss_inter = c.mss_parms.mssfix - (crypto_encap + transport_encap); - } + calculate_mssfix(c); } void data_limit_notify(const DataLimit::Mode cdl_mode, @@ -2069,8 +2116,8 @@ namespace openvpn { bool pid_wrap; // set MSS for segments client can receive - if (proto.config->mss_inter > 0) - MSSFix::mssfix(buf, proto.config->mss_inter); + if (proto.config->mss_fix > 0) + MSSFix::mssfix(buf, proto.config->mss_fix); // compress packet if (compress) diff --git a/openvpn/transport/mssfix.hpp b/openvpn/transport/mssfix.hpp index e275f6e9..ac833c5e 100644 --- a/openvpn/transport/mssfix.hpp +++ b/openvpn/transport/mssfix.hpp @@ -36,7 +36,7 @@ namespace openvpn { class MSSFix { public: - static void mssfix(BufferAllocated& buf, int mss_inter) + static void mssfix(BufferAllocated& buf, int mss_fix) { if (buf.empty()) return; @@ -61,7 +61,7 @@ namespace openvpn { TCPHeader* tcphdr = (TCPHeader*)(buf.data() + ipv4hlen); int ip_payload_len = buf.length() - ipv4hlen; - do_mssfix(tcphdr, mss_inter - (sizeof(struct IPv4Header) + sizeof(struct TCPHeader)), ip_payload_len); + do_mssfix(tcphdr, mss_fix, ip_payload_len); } } break; @@ -96,8 +96,8 @@ namespace openvpn { if (payload_len >= (int) sizeof(struct TCPHeader)) { TCPHeader *tcphdr = (TCPHeader *)(buf.data() + sizeof(struct IPv6Header)); - do_mssfix(tcphdr, mss_inter - (sizeof(struct IPv6Header) + sizeof(struct TCPHeader)), - payload_len); + // mssfix is calculated for IPv4, and since IPv6 header is 20 bytes larger we need to account for it + do_mssfix(tcphdr, mss_fix - 20, payload_len); } } break; diff --git a/openvpn/tun/linux/client/tuncli.hpp b/openvpn/tun/linux/client/tuncli.hpp index accfd93e..87114240 100644 --- a/openvpn/tun/linux/client/tuncli.hpp +++ b/openvpn/tun/linux/client/tuncli.hpp @@ -89,7 +89,7 @@ namespace openvpn { { // set a default MTU if (!tun_prop.mtu) - tun_prop.mtu = 1500; + tun_prop.mtu = TUN_MTU_DEFAULT; // parse "dev" option if (dev_name.empty()) diff --git a/openvpn/tun/linux/client/tunnetlink.hpp b/openvpn/tun/linux/client/tunnetlink.hpp index f0c45060..edb58962 100644 --- a/openvpn/tun/linux/client/tunnetlink.hpp +++ b/openvpn/tun/linux/client/tunnetlink.hpp @@ -97,7 +97,7 @@ namespace openvpn { std::string dev; bool up = true; - int mtu = 1500; + int mtu = TUN_MTU_DEFAULT; }; struct NetlinkAddr4 : public Action diff --git a/openvpn/tun/mac/client/tuncli.hpp b/openvpn/tun/mac/client/tuncli.hpp index c6dbfae9..10267b43 100644 --- a/openvpn/tun/mac/client/tuncli.hpp +++ b/openvpn/tun/mac/client/tuncli.hpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #ifdef TEST_EER // test emulated exclude routes @@ -199,7 +200,7 @@ namespace openvpn { // handle MTU default if (!po->mtu) - po->mtu = 1500; + po->mtu = TUN_MTU_DEFAULT; OPENVPN_LOG("CAPTURED OPTIONS:" << std::endl << po->to_string()); diff --git a/openvpn/tun/tunmtu.hpp b/openvpn/tun/tunmtu.hpp index 7b1181c7..6fc96b6c 100644 --- a/openvpn/tun/tunmtu.hpp +++ b/openvpn/tun/tunmtu.hpp @@ -25,6 +25,10 @@ #include namespace openvpn { + enum { + TUN_MTU_DEFAULT = 1500, + }; + inline unsigned int parse_tun_mtu(const OptionList& opt, unsigned int default_value) { return opt.get_num("tun-mtu", 1, default_value, 576, 65535);