@@ -171,6 +171,14 @@ attribute-sets:
will advertise the tx-id to be used on the link.
checks:
max: 0xFFFFFF
+ -
+ name: mssfix
+ type: u16
+ doc: >-
+ OpenVPN mssfix value for this peer. TCP MSS options in SYN and
+ SYN-ACK packets traveling through the ovpn device are clamped to
+ this value for IPv4. For IPv6, 20 bytes are subtracted before
+ clamping to account for the larger IPv6 header.
-
name: peer-new-input
subset-of: peer
@@ -201,6 +209,8 @@ attribute-sets:
name: keepalive-timeout
-
name: tx-id
+ -
+ name: mssfix
-
name: peer-set-input
subset-of: peer
@@ -229,6 +239,8 @@ attribute-sets:
name: keepalive-timeout
-
name: tx-id
+ -
+ name: mssfix
-
name: peer-del-input
subset-of: peer
@@ -13,6 +13,7 @@
#include <net/gro_cells.h>
#include <net/gso.h>
#include <net/ip.h>
+#include <net/tcp.h>
#include "ovpnpriv.h"
#include "peer.h"
@@ -54,6 +55,83 @@ static bool ovpn_is_keepalive(struct sk_buff *skb)
return !memcmp(skb->data, ovpn_keepalive_message, OVPN_KEEPALIVE_SIZE);
}
+/**
+ * ovpn_apply_mssfix - clamp the MSS on TCP SYN or SYN-ACK packets
+ * @skb: skb to inspect and possibly modify
+ * @mssfix: maximum IPv4 MSS value to apply
+ *
+ * Verify that @skb carries a TCP SYN or SYN-ACK packet. If so, clamp the
+ * TCPOPT_MSS option to @mssfix for IPv4, or to @mssfix - 20 for IPv6 to
+ * account for the larger IPv6 header.
+ *
+ * Notes:
+ * - the function assumes the IP header is fully linear; this is currently
+ * guaranteed because both TX and RX paths call it only after
+ * ovpn_ip_check_protocol, which linearizes the IP header;
+ * - MSS clamping is performed only when a valid TCPOPT_MSS option is present,
+ * matching the behavior of OpenVPN userspace.
+ */
+static int ovpn_apply_mssfix(struct sk_buff *skb, u16 mssfix)
+{
+ const struct ipv6hdr *ipv6h;
+ const struct iphdr *iph;
+ struct tcphdr *th;
+ int thoff, thlen;
+ __be16 frag_off;
+ u16 maxmss;
+ u8 nexthdr;
+
+ switch (skb->protocol) {
+ case htons(ETH_P_IP):
+ iph = ip_hdr(skb);
+ if (iph->protocol != IPPROTO_TCP ||
+ unlikely(ip_is_fragment(iph)))
+ return 0;
+
+ thoff = ip_hdrlen(skb);
+ maxmss = mssfix;
+ break;
+ case htons(ETH_P_IPV6):
+ ipv6h = ipv6_hdr(skb);
+ nexthdr = ipv6h->nexthdr;
+ thoff = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr,
+ &frag_off);
+ if (unlikely(thoff < 0))
+ return thoff;
+ if (nexthdr != IPPROTO_TCP || unlikely(frag_off))
+ return 0;
+
+ maxmss = mssfix - 20;
+ break;
+ default:
+ return 0;
+ }
+
+ if (unlikely(skb->len < thoff + sizeof(struct tcphdr)))
+ return -EINVAL;
+
+ if (unlikely(skb_ensure_writable(skb, thoff + sizeof(struct tcphdr))))
+ return -ENOMEM;
+
+ th = (struct tcphdr *)(skb->data + thoff);
+ thlen = th->doff * 4;
+
+ if (unlikely(thlen < sizeof(*th)))
+ return -EINVAL;
+
+ if (likely(!th->syn))
+ return 0;
+
+ if (unlikely(skb->len < thoff + thlen))
+ return -EINVAL;
+
+ if (unlikely(skb_ensure_writable(skb, thoff + thlen)))
+ return -ENOMEM;
+
+ th = (struct tcphdr *)(skb->data + thoff);
+ return tcp_clamp_mss_option(skb, th, maxmss);
+}
+
/* Called after decrypt to write the IP packet to the device.
* This method is expected to manage/free the skb.
*/
@@ -74,6 +152,12 @@ static void ovpn_netdev_write(struct ovpn_peer *peer, struct sk_buff *skb)
*/
skb->ip_summed = CHECKSUM_NONE;
+ /* apply the MSS fix after resetting the checksum state in order to
+ * avoid using stale metadata when updating the checksum
+ */
+ if (peer->mssfix)
+ ovpn_apply_mssfix(skb, peer->mssfix);
+
/* skb hash for transport packet no longer valid after decapsulation */
skb_clear_hash(skb);
@@ -342,6 +426,9 @@ static void ovpn_send(struct ovpn_priv *ovpn, struct sk_buff *skb,
* independently
*/
skb_list_walk_safe(skb, curr, next) {
+ if (peer->mssfix)
+ ovpn_apply_mssfix(curr, peer->mssfix);
+
if (unlikely(!ovpn_encrypt_one(peer, curr))) {
dev_dstats_tx_dropped(ovpn->dev);
kfree_skb(curr);
@@ -55,7 +55,7 @@ const struct nla_policy ovpn_keydir_nl_policy[OVPN_A_KEYDIR_NONCE_TAIL + 1] = {
[OVPN_A_KEYDIR_NONCE_TAIL] = NLA_POLICY_EXACT_LEN(OVPN_NONCE_TAIL_SIZE),
};
-const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_TX_ID + 1] = {
+const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_MSSFIX + 1] = {
[OVPN_A_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_id_range),
[OVPN_A_PEER_REMOTE_IPV4] = { .type = NLA_BE32, },
[OVPN_A_PEER_REMOTE_IPV6] = NLA_POLICY_EXACT_LEN(16),
@@ -80,13 +80,14 @@ const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_TX_ID + 1] = {
[OVPN_A_PEER_LINK_RX_PACKETS] = { .type = NLA_UINT, },
[OVPN_A_PEER_LINK_TX_PACKETS] = { .type = NLA_UINT, },
[OVPN_A_PEER_TX_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_tx_id_range),
+ [OVPN_A_PEER_MSSFIX] = { .type = NLA_U16, },
};
const struct nla_policy ovpn_peer_del_input_nl_policy[OVPN_A_PEER_ID + 1] = {
[OVPN_A_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_id_range),
};
-const struct nla_policy ovpn_peer_new_input_nl_policy[OVPN_A_PEER_TX_ID + 1] = {
+const struct nla_policy ovpn_peer_new_input_nl_policy[OVPN_A_PEER_MSSFIX + 1] = {
[OVPN_A_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_id_range),
[OVPN_A_PEER_REMOTE_IPV4] = { .type = NLA_BE32, },
[OVPN_A_PEER_REMOTE_IPV6] = NLA_POLICY_EXACT_LEN(16),
@@ -100,9 +101,10 @@ const struct nla_policy ovpn_peer_new_input_nl_policy[OVPN_A_PEER_TX_ID + 1] = {
[OVPN_A_PEER_KEEPALIVE_INTERVAL] = { .type = NLA_U32, },
[OVPN_A_PEER_KEEPALIVE_TIMEOUT] = { .type = NLA_U32, },
[OVPN_A_PEER_TX_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_tx_id_range),
+ [OVPN_A_PEER_MSSFIX] = { .type = NLA_U16, },
};
-const struct nla_policy ovpn_peer_set_input_nl_policy[OVPN_A_PEER_TX_ID + 1] = {
+const struct nla_policy ovpn_peer_set_input_nl_policy[OVPN_A_PEER_MSSFIX + 1] = {
[OVPN_A_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_id_range),
[OVPN_A_PEER_REMOTE_IPV4] = { .type = NLA_BE32, },
[OVPN_A_PEER_REMOTE_IPV6] = NLA_POLICY_EXACT_LEN(16),
@@ -115,6 +117,7 @@ const struct nla_policy ovpn_peer_set_input_nl_policy[OVPN_A_PEER_TX_ID + 1] = {
[OVPN_A_PEER_KEEPALIVE_INTERVAL] = { .type = NLA_U32, },
[OVPN_A_PEER_KEEPALIVE_TIMEOUT] = { .type = NLA_U32, },
[OVPN_A_PEER_TX_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_tx_id_range),
+ [OVPN_A_PEER_MSSFIX] = { .type = NLA_U16, },
};
/* OVPN_CMD_PEER_NEW - do */
@@ -18,10 +18,10 @@ extern const struct nla_policy ovpn_keyconf_del_input_nl_policy[OVPN_A_KEYCONF_S
extern const struct nla_policy ovpn_keyconf_get_nl_policy[OVPN_A_KEYCONF_CIPHER_ALG + 1];
extern const struct nla_policy ovpn_keyconf_swap_input_nl_policy[OVPN_A_KEYCONF_PEER_ID + 1];
extern const struct nla_policy ovpn_keydir_nl_policy[OVPN_A_KEYDIR_NONCE_TAIL + 1];
-extern const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_TX_ID + 1];
+extern const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_MSSFIX + 1];
extern const struct nla_policy ovpn_peer_del_input_nl_policy[OVPN_A_PEER_ID + 1];
-extern const struct nla_policy ovpn_peer_new_input_nl_policy[OVPN_A_PEER_TX_ID + 1];
-extern const struct nla_policy ovpn_peer_set_input_nl_policy[OVPN_A_PEER_TX_ID + 1];
+extern const struct nla_policy ovpn_peer_new_input_nl_policy[OVPN_A_PEER_MSSFIX + 1];
+extern const struct nla_policy ovpn_peer_set_input_nl_policy[OVPN_A_PEER_MSSFIX + 1];
int ovpn_nl_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
struct genl_info *info);
@@ -283,6 +283,7 @@ static int ovpn_nl_peer_modify(struct ovpn_peer *peer, struct genl_info *info,
void *local_ip = NULL;
u32 interv, timeout;
bool rehash = false;
+ u16 mssfix;
int ret;
spin_lock_bh(&peer->lock);
@@ -311,6 +312,17 @@ static int ovpn_nl_peer_modify(struct ovpn_peer *peer, struct genl_info *info,
if (attrs[OVPN_A_PEER_TX_ID])
peer->tx_id = nla_get_u32(attrs[OVPN_A_PEER_TX_ID]);
+ if (attrs[OVPN_A_PEER_MSSFIX]) {
+ mssfix = nla_get_u16(attrs[OVPN_A_PEER_MSSFIX]);
+ if (mssfix > 0 && mssfix <= 20) {
+ NL_SET_ERR_MSG_FMT_MOD(info->extack,
+ "mssfix must be 0 (disable) or at least 21");
+ ret = -EINVAL;
+ goto err_unlock;
+ }
+ peer->mssfix = mssfix;
+ }
+
if (attrs[OVPN_A_PEER_VPN_IPV4]) {
rehash = true;
peer->vpn_addrs.ipv4.s_addr =
@@ -582,6 +594,9 @@ static int ovpn_nl_send_peer(struct sk_buff *skb, const struct genl_info *info,
if (nla_put_u32(skb, OVPN_A_PEER_TX_ID, peer->tx_id))
goto err;
+ if (peer->mssfix && nla_put_u16(skb, OVPN_A_PEER_MSSFIX, peer->mssfix))
+ goto err;
+
if (peer->vpn_addrs.ipv4.s_addr != htonl(INADDR_ANY))
if (nla_put_in_addr(skb, OVPN_A_PEER_VPN_IPV4,
peer->vpn_addrs.ipv4.s_addr))
@@ -23,6 +23,7 @@
* @dev_tracker: reference tracker for associated dev
* @id: unique identifier, used to match incoming packets
* @tx_id: identifier to be used in TX packets
+ * @mssfix: maximum IPv4 TCP MSS to advertise on tunnelled SYN packets
* @vpn_addrs: IP addresses assigned over the tunnel
* @vpn_addrs.ipv4: IPv4 assigned to peer on the tunnel
* @vpn_addrs.ipv6: IPv6 assigned to peer on the tunnel
@@ -66,6 +67,7 @@ struct ovpn_peer {
netdevice_tracker dev_tracker;
u32 id;
u32 tx_id;
+ u16 mssfix;
struct {
struct in_addr ipv4;
struct in6_addr ipv6;
@@ -56,6 +56,7 @@ enum {
OVPN_A_PEER_LINK_RX_PACKETS,
OVPN_A_PEER_LINK_TX_PACKETS,
OVPN_A_PEER_TX_ID,
+ OVPN_A_PEER_MSSFIX,
__OVPN_A_PEER_MAX,
OVPN_A_PEER_MAX = (__OVPN_A_PEER_MAX - 1)
Add support for OpenVPN mssfix in ovpn. This clamps TCP MSS options in SYN and SYN-ACK packets crossing the ovpn device so TCP peers choose segment sizes that fit within the VPN path MTU after OpenVPN encapsulation. Store the computed IPv4 MSS clamp value per peer and apply it on both RX and TX paths. IPv6 packets use the same value minus 20 bytes, matching OpenVPN userspace behavior for the larger IPv6 header. Only non-fragmented TCP packets are considered, and the shared TCP MSS option helper is used to update existing MSS options without increasing them. Signed-off-by: Ralf Lici <ralf@mandelbit.com> --- Documentation/netlink/specs/ovpn.yaml | 12 ++++ drivers/net/ovpn/io.c | 87 +++++++++++++++++++++++++++ drivers/net/ovpn/netlink-gen.c | 9 ++- drivers/net/ovpn/netlink-gen.h | 6 +- drivers/net/ovpn/netlink.c | 15 +++++ drivers/net/ovpn/peer.h | 2 + include/uapi/linux/ovpn.h | 1 + 7 files changed, 126 insertions(+), 6 deletions(-)