xt_TCPMSS has local logic for scanning TCP options and lowering an
existing MSS option without increasing it.

Move that scan-and-clamp logic into a TCP helper so other networking
code can reuse it without duplicating TCP option parsing. Keep xt_TCPMSS
responsible for the policy-specific behavior of adding a missing MSS
option.

The helper returns 0 when an existing MSS option was handled, whether or
not it had to be lowered. It returns -ENOENT when no MSS option is
present and -EINVAL when option parsing finds a malformed option block.
xt_TCPMSS treats both non-zero cases like the previous local scan
failing to find a clampable MSS option, so the target keeps its existing
fallback behavior for adding a missing MSS option or accepting the
packet unchanged.

The helper expects the caller to ensure that the TCP header and options
are linear and writable.

Signed-off-by: Ralf Lici <[email protected]>
---
 include/net/tcp.h         |  2 ++
 net/ipv4/tcp.c            | 60 +++++++++++++++++++++++++++++++++++++++
 net/netfilter/xt_TCPMSS.c | 36 ++---------------------
 3 files changed, 64 insertions(+), 34 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index 3c4e6adb0dbd..e722c7d936bf 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -538,6 +538,8 @@ void tcp_parse_options(const struct net *net, const struct 
sk_buff *skb,
                       struct tcp_options_received *opt_rx,
                       int estab, struct tcp_fastopen_cookie *foc);
 
+int tcp_clamp_mss_option(struct sk_buff *skb, struct tcphdr *th, u16 maxmss);
+
 /*
  *     BPF SKB-less helpers
  */
diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
index 21ece4c71612..ef10e530a1ad 100644
--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -721,6 +721,66 @@ static inline void tcp_mark_urg(struct tcp_sock *tp, int 
flags)
                tp->snd_up = tp->write_seq;
 }
 
+/**
+ * tcp_clamp_mss_option - clamp an existing TCP MSS option
+ * @skb: skb containing the TCP segment
+ * @th: TCP header in @skb
+ * @maxmss: upper bound for the TCP MSS option value
+ *
+ * Parse the TCP option block and lower an existing MSS option to @maxmss.
+ * The MSS value is never increased. If the MSS value is changed, the TCP
+ * checksum in @th is updated.
+ *
+ * The caller must ensure that @th and the complete TCP option block are
+ * present in the linear data area and writable.
+ *
+ * Return: 0 on success or when no update is needed, -ENOENT when no MSS
+ * option is present, or -EINVAL when the TCP option block is malformed.
+ */
+int tcp_clamp_mss_option(struct sk_buff *skb, struct tcphdr *th, u16 maxmss)
+{
+       int length = th->doff * 4 - sizeof(*th);
+       u8 *ptr = (u8 *)(th + 1);
+       u16 oldmss;
+
+       while (length > 0) {
+               int opcode = *ptr++;
+               int opsize;
+
+               switch (opcode) {
+               case TCPOPT_EOL:
+                       return -ENOENT;
+               case TCPOPT_NOP:
+                       length--;
+                       continue;
+               default:
+                       if (length < 2)
+                               return -EINVAL;
+
+                       opsize = *ptr++;
+                       if (opsize < 2 || opsize > length)
+                               return -EINVAL;
+
+                       if (opcode == TCPOPT_MSS && opsize == TCPOLEN_MSS) {
+                               oldmss = get_unaligned_be16(ptr);
+                               if (!oldmss || oldmss <= maxmss)
+                                       return 0;
+
+                               put_unaligned_be16(maxmss, ptr);
+                               inet_proto_csum_replace2(&th->check, skb,
+                                                        htons(oldmss),
+                                                        htons(maxmss), false);
+                               return 0;
+                       }
+
+                       ptr += opsize - 2;
+                       length -= opsize;
+               }
+       }
+       return -ENOENT;
+}
+EXPORT_SYMBOL_GPL(tcp_clamp_mss_option);
+
 /* If a not yet filled skb is pushed, do not send it if
  * we have data packets in Qdisc or NIC queues :
  * Because TX completion will happen shortly, it gives a chance
diff --git a/net/netfilter/xt_TCPMSS.c b/net/netfilter/xt_TCPMSS.c
index 80e1634bc51f..70983b757229 100644
--- a/net/netfilter/xt_TCPMSS.c
+++ b/net/netfilter/xt_TCPMSS.c
@@ -30,16 +30,6 @@ MODULE_DESCRIPTION("Xtables: TCP Maximum Segment Size (MSS) 
adjustment");
 MODULE_ALIAS("ipt_TCPMSS");
 MODULE_ALIAS("ip6t_TCPMSS");
 
-static inline unsigned int
-optlen(const u_int8_t *opt, unsigned int offset)
-{
-       /* Beware zero-length options: make finite progress */
-       if (opt[offset] <= TCPOPT_NOP || opt[offset+1] == 0)
-               return 1;
-       else
-               return opt[offset+1];
-}
-
 static u_int32_t tcpmss_reverse_mtu(struct net *net,
                                    const struct sk_buff *skb,
                                    unsigned int family)
@@ -77,7 +67,6 @@ tcpmss_mangle_packet(struct sk_buff *skb,
        const struct xt_tcpmss_info *info = par->targinfo;
        struct tcphdr *tcph;
        int len, tcp_hdrlen;
-       unsigned int i;
        __be16 oldval;
        u16 newmss;
        u8 *opt;
@@ -113,29 +102,8 @@ tcpmss_mangle_packet(struct sk_buff *skb,
        } else
                newmss = info->mss;
 
-       opt = (u_int8_t *)tcph;
-       for (i = sizeof(struct tcphdr); i <= tcp_hdrlen - TCPOLEN_MSS; i += 
optlen(opt, i)) {
-               if (opt[i] == TCPOPT_MSS && opt[i+1] == TCPOLEN_MSS) {
-                       u_int16_t oldmss;
-
-                       oldmss = (opt[i+2] << 8) | opt[i+3];
-
-                       /* Never increase MSS, even when setting it, as
-                        * doing so results in problems for hosts that rely
-                        * on MSS being set correctly.
-                        */
-                       if (oldmss <= newmss)
-                               return 0;
-
-                       opt[i+2] = (newmss & 0xff00) >> 8;
-                       opt[i+3] = newmss & 0x00ff;
-
-                       inet_proto_csum_replace2(&tcph->check, skb,
-                                                htons(oldmss), htons(newmss),
-                                                false);
-                       return 0;
-               }
-       }
+       if (tcp_clamp_mss_option(skb, tcph, newmss) == 0)
+               return 0;
 
        /* There is data after the header so the option can't be added
         * without moving it, and doing so may make the SYN packet
-- 
2.54.0



_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel

Reply via email to