OpenVPN 3 Core Library
Loading...
Searching...
No Matches
mssfix.hpp
Go to the documentation of this file.
1// OpenVPN -- An application to securely tunnel IP networks
2// over a single port, with support for SSL/TLS-based
3// session authentication and key exchange,
4// packet encryption, packet authentication, and
5// packet compression.
6//
7// Copyright (C) 2012- OpenVPN Inc.
8//
9// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
10//
11
12#pragma once
13
17#include <openvpn/ip/ip4.hpp>
18#include <openvpn/ip/ip6.hpp>
19#include <openvpn/ip/tcp.hpp>
20
21#if OPENVPN_DEBUG_PROTO >= 2
22#define OPENVPN_LOG_MSSFIX(x) OPENVPN_LOG(x)
23#else
24#define OPENVPN_LOG_MSSFIX(x)
25#endif
26
27namespace openvpn {
28class MSSFix
29{
30 public:
31 static void mssfix(BufferAllocated &buf, uint16_t mss_fix)
32 {
33 if (buf.empty())
34 return;
35
36 switch (IPCommon::version(buf[0]))
37 {
38 case IPCommon::IPv4:
39 {
40 if (buf.length() <= sizeof(struct IPv4Header))
41 break;
42
43 const IPv4Header *iphdr = (const IPv4Header *)buf.c_data();
44
45 auto ipv4hlen = IPv4Header::length(iphdr->version_len);
46
47 if (iphdr->protocol == IPCommon::TCP
48 && ntohs(iphdr->tot_len) == buf.length()
49 && (ntohs(iphdr->frag_off) & IPv4Header::OFFMASK) == 0
50 && ipv4hlen <= buf.length()
51 && buf.length() - ipv4hlen >= sizeof(struct TCPHeader))
52 {
53 TCPHeader *tcphdr = reinterpret_cast<TCPHeader *>(buf.data() + ipv4hlen);
54 auto ip_payload_len = buf.length() - ipv4hlen;
55
56 do_mssfix(tcphdr, mss_fix, ip_payload_len);
57 }
58 }
59 break;
60
61 case IPCommon::IPv6:
62 {
63 if (buf.length() <= sizeof(struct IPv6Header))
64 break;
65
66 const IPv6Header *iphdr = (const IPv6Header *)buf.c_data();
67
68 if (buf.length() != ntohs(iphdr->payload_len) + sizeof(struct IPv6Header))
69 break;
70
71 /* follow header chain until we reach final header, then check for TCP
72 *
73 * An IPv6 packet could, theoretically, have a chain of multiple headers
74 * before the final header (TCP, UDP, ...), so we'd need to walk that
75 * chain (see RFC 2460 and RFC 6564 for details).
76 *
77 * In practice, "most typically used" extention headers (AH, routing,
78 * fragment, mobility) are very unlikely to be seen inside an OpenVPN
79 * tun, so for now, we only handle the case of "single next header = TCP"
80 */
81 if (iphdr->nexthdr != IPCommon::TCP)
82 break;
83
84 /* skip IPv6 header (40 bytes),
85 * verify remainder is large enough to contain a full TCP header
86 */
87 auto payload_len = buf.length() - sizeof(struct IPv6Header);
88 if (payload_len >= sizeof(struct TCPHeader))
89 {
90 TCPHeader *tcphdr = reinterpret_cast<TCPHeader *>(buf.data() + sizeof(struct IPv6Header));
91 // mssfix is calculated for IPv4, and since IPv6 header is 20 bytes larger we need to account for it
92 do_mssfix(tcphdr, mss_fix - 20, payload_len);
93 }
94 }
95 break;
96 }
97 }
98
99 private:
100 static void do_mssfix(TCPHeader *tcphdr, uint16_t max_mss, size_t ip_payload_len)
101 {
102 if ((tcphdr->flags & TCPHeader::FLAG_SYN) == 0)
103 return;
104
105 auto tcphlen = TCPHeader::length(tcphdr->doff_res);
106 if (tcphlen <= (int)sizeof(struct TCPHeader) || tcphlen > ip_payload_len)
107 return;
108
109 size_t olen, optlen; // length of options field and Option-Length
110 uint8_t *opt; // option type
111
112 for (olen = tcphlen - sizeof(struct TCPHeader), opt = (uint8_t *)(tcphdr + 1);
113 olen > 1;
114 olen -= optlen, opt += optlen)
115 {
116 if (*opt == TCPHeader::OPT_EOL)
117 break;
118 else if (*opt == TCPHeader::OPT_NOP)
119 optlen = 1;
120 else
121 {
122 optlen = *(opt + 1);
123 if (optlen <= 0 || optlen > olen)
124 break;
125 if ((*opt == TCPHeader::OPT_MAXSEG) && (optlen == TCPHeader::OPTLEN_MAXSEG))
126 {
127 auto mssRaw = (opt[2] << 8) + opt[3];
128 if (is_safe_conversion<uint16_t>(mssRaw))
129 {
130 uint16_t mssval = static_cast<uint16_t>(mssRaw);
131 if (mssval > max_mss)
132 {
133 OPENVPN_LOG_MSSFIX("MTU MSS " << mssval << " -> " << max_mss);
134 int accumulate = htons(mssval);
135 opt[2] = static_cast<uint8_t>((max_mss >> 8) & 0xff);
136 opt[3] = static_cast<uint8_t>(max_mss & 0xff);
137 accumulate -= htons(max_mss);
138 tcp_adjust_checksum(accumulate, tcphdr->check);
139 }
140 }
141 else
142 {
143 OPENVPN_LOG_MSSFIX("Rejecting MSS fix: value out of bounds for type " << ((opt[2] << 8) + opt[3]));
144 break;
145 }
146 }
147 }
148 }
149 }
150};
151} // namespace openvpn
const T * c_data() const
Returns a const pointer to the start of the buffer.
Definition buffer.hpp:1194
size_t length() const
Returns the length of the buffer.
Definition buffer.hpp:1188
T * data()
Get a mutable pointer to the start of the array.
Definition buffer.hpp:1450
bool empty() const
Returns true if the buffer is empty.
Definition buffer.hpp:1236
static void mssfix(BufferAllocated &buf, uint16_t mss_fix)
Definition mssfix.hpp:31
static void do_mssfix(TCPHeader *tcphdr, uint16_t max_mss, size_t ip_payload_len)
Definition mssfix.hpp:100
#define OPENVPN_LOG_MSSFIX(x)
Definition mssfix.hpp:24
unsigned int version(const std::uint8_t version_len_prio)
Definition ipcommon.hpp:35
void tcp_adjust_checksum(int acc, std::uint16_t &cksum)
Definition tcp.hpp:64
std::uint8_t protocol
Definition ip4.hpp:57
static unsigned int length(const std::uint8_t version_len)
Definition ip4.hpp:25
std::uint8_t version_len
Definition ip4.hpp:42
std::uint16_t frag_off
Definition ip4.hpp:53
std::uint16_t tot_len
Definition ip4.hpp:45
std::uint16_t payload_len
Definition ip6.hpp:31
std::uint8_t nexthdr
Definition ip6.hpp:32
std::uint16_t check
Definition tcp.hpp:36
static unsigned int length(const std::uint8_t doff_res)
Definition tcp.hpp:24
std::uint8_t flags
Definition tcp.hpp:34
std::uint8_t doff_res
Definition tcp.hpp:33