@@ -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)