linux/net/netfilter/xt_TCPMSS.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * This is a module which is used for setting the MSS option in TCP packets.
   4 *
   5 * Copyright (C) 2000 Marc Boucher <marc@mbsi.ca>
   6 * Copyright (C) 2007 Patrick McHardy <kaber@trash.net>
   7 */
   8#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
   9#include <linux/module.h>
  10#include <linux/skbuff.h>
  11#include <linux/ip.h>
  12#include <linux/gfp.h>
  13#include <linux/ipv6.h>
  14#include <linux/tcp.h>
  15#include <net/dst.h>
  16#include <net/flow.h>
  17#include <net/ipv6.h>
  18#include <net/route.h>
  19#include <net/tcp.h>
  20
  21#include <linux/netfilter_ipv4/ip_tables.h>
  22#include <linux/netfilter_ipv6/ip6_tables.h>
  23#include <linux/netfilter/x_tables.h>
  24#include <linux/netfilter/xt_tcpudp.h>
  25#include <linux/netfilter/xt_TCPMSS.h>
  26
  27MODULE_LICENSE("GPL");
  28MODULE_AUTHOR("Marc Boucher <marc@mbsi.ca>");
  29MODULE_DESCRIPTION("Xtables: TCP Maximum Segment Size (MSS) adjustment");
  30MODULE_ALIAS("ipt_TCPMSS");
  31MODULE_ALIAS("ip6t_TCPMSS");
  32
  33static inline unsigned int
  34optlen(const u_int8_t *opt, unsigned int offset)
  35{
  36        /* Beware zero-length options: make finite progress */
  37        if (opt[offset] <= TCPOPT_NOP || opt[offset+1] == 0)
  38                return 1;
  39        else
  40                return opt[offset+1];
  41}
  42
  43static u_int32_t tcpmss_reverse_mtu(struct net *net,
  44                                    const struct sk_buff *skb,
  45                                    unsigned int family)
  46{
  47        struct flowi fl;
  48        struct rtable *rt = NULL;
  49        u_int32_t mtu     = ~0U;
  50
  51        if (family == PF_INET) {
  52                struct flowi4 *fl4 = &fl.u.ip4;
  53                memset(fl4, 0, sizeof(*fl4));
  54                fl4->daddr = ip_hdr(skb)->saddr;
  55        } else {
  56                struct flowi6 *fl6 = &fl.u.ip6;
  57
  58                memset(fl6, 0, sizeof(*fl6));
  59                fl6->daddr = ipv6_hdr(skb)->saddr;
  60        }
  61
  62        nf_route(net, (struct dst_entry **)&rt, &fl, false, family);
  63        if (rt != NULL) {
  64                mtu = dst_mtu(&rt->dst);
  65                dst_release(&rt->dst);
  66        }
  67        return mtu;
  68}
  69
  70static int
  71tcpmss_mangle_packet(struct sk_buff *skb,
  72                     const struct xt_action_param *par,
  73                     unsigned int family,
  74                     unsigned int tcphoff,
  75                     unsigned int minlen)
  76{
  77        const struct xt_tcpmss_info *info = par->targinfo;
  78        struct tcphdr *tcph;
  79        int len, tcp_hdrlen;
  80        unsigned int i;
  81        __be16 oldval;
  82        u16 newmss;
  83        u8 *opt;
  84
  85        /* This is a fragment, no TCP header is available */
  86        if (par->fragoff != 0)
  87                return 0;
  88
  89        if (skb_ensure_writable(skb, skb->len))
  90                return -1;
  91
  92        len = skb->len - tcphoff;
  93        if (len < (int)sizeof(struct tcphdr))
  94                return -1;
  95
  96        tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
  97        tcp_hdrlen = tcph->doff * 4;
  98
  99        if (len < tcp_hdrlen || tcp_hdrlen < sizeof(struct tcphdr))
 100                return -1;
 101
 102        if (info->mss == XT_TCPMSS_CLAMP_PMTU) {
 103                struct net *net = xt_net(par);
 104                unsigned int in_mtu = tcpmss_reverse_mtu(net, skb, family);
 105                unsigned int min_mtu = min(dst_mtu(skb_dst(skb)), in_mtu);
 106
 107                if (min_mtu <= minlen) {
 108                        net_err_ratelimited("unknown or invalid path-MTU (%u)\n",
 109                                            min_mtu);
 110                        return -1;
 111                }
 112                newmss = min_mtu - minlen;
 113        } else
 114                newmss = info->mss;
 115
 116        opt = (u_int8_t *)tcph;
 117        for (i = sizeof(struct tcphdr); i <= tcp_hdrlen - TCPOLEN_MSS; i += optlen(opt, i)) {
 118                if (opt[i] == TCPOPT_MSS && opt[i+1] == TCPOLEN_MSS) {
 119                        u_int16_t oldmss;
 120
 121                        oldmss = (opt[i+2] << 8) | opt[i+3];
 122
 123                        /* Never increase MSS, even when setting it, as
 124                         * doing so results in problems for hosts that rely
 125                         * on MSS being set correctly.
 126                         */
 127                        if (oldmss <= newmss)
 128                                return 0;
 129
 130                        opt[i+2] = (newmss & 0xff00) >> 8;
 131                        opt[i+3] = newmss & 0x00ff;
 132
 133                        inet_proto_csum_replace2(&tcph->check, skb,
 134                                                 htons(oldmss), htons(newmss),
 135                                                 false);
 136                        return 0;
 137                }
 138        }
 139
 140        /* There is data after the header so the option can't be added
 141         * without moving it, and doing so may make the SYN packet
 142         * itself too large. Accept the packet unmodified instead.
 143         */
 144        if (len > tcp_hdrlen)
 145                return 0;
 146
 147        /* tcph->doff has 4 bits, do not wrap it to 0 */
 148        if (tcp_hdrlen >= 15 * 4)
 149                return 0;
 150
 151        /*
 152         * MSS Option not found ?! add it..
 153         */
 154        if (skb_tailroom(skb) < TCPOLEN_MSS) {
 155                if (pskb_expand_head(skb, 0,
 156                                     TCPOLEN_MSS - skb_tailroom(skb),
 157                                     GFP_ATOMIC))
 158                        return -1;
 159                tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
 160        }
 161
 162        skb_put(skb, TCPOLEN_MSS);
 163
 164        /*
 165         * IPv4: RFC 1122 states "If an MSS option is not received at
 166         * connection setup, TCP MUST assume a default send MSS of 536".
 167         * IPv6: RFC 2460 states IPv6 has a minimum MTU of 1280 and a minimum
 168         * length IPv6 header of 60, ergo the default MSS value is 1220
 169         * Since no MSS was provided, we must use the default values
 170         */
 171        if (xt_family(par) == NFPROTO_IPV4)
 172                newmss = min(newmss, (u16)536);
 173        else
 174                newmss = min(newmss, (u16)1220);
 175
 176        opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
 177        memmove(opt + TCPOLEN_MSS, opt, len - sizeof(struct tcphdr));
 178
 179        inet_proto_csum_replace2(&tcph->check, skb,
 180                                 htons(len), htons(len + TCPOLEN_MSS), true);
 181        opt[0] = TCPOPT_MSS;
 182        opt[1] = TCPOLEN_MSS;
 183        opt[2] = (newmss & 0xff00) >> 8;
 184        opt[3] = newmss & 0x00ff;
 185
 186        inet_proto_csum_replace4(&tcph->check, skb, 0, *((__be32 *)opt), false);
 187
 188        oldval = ((__be16 *)tcph)[6];
 189        tcph->doff += TCPOLEN_MSS/4;
 190        inet_proto_csum_replace2(&tcph->check, skb,
 191                                 oldval, ((__be16 *)tcph)[6], false);
 192        return TCPOLEN_MSS;
 193}
 194
 195static unsigned int
 196tcpmss_tg4(struct sk_buff *skb, const struct xt_action_param *par)
 197{
 198        struct iphdr *iph = ip_hdr(skb);
 199        __be16 newlen;
 200        int ret;
 201
 202        ret = tcpmss_mangle_packet(skb, par,
 203                                   PF_INET,
 204                                   iph->ihl * 4,
 205                                   sizeof(*iph) + sizeof(struct tcphdr));
 206        if (ret < 0)
 207                return NF_DROP;
 208        if (ret > 0) {
 209                iph = ip_hdr(skb);
 210                newlen = htons(ntohs(iph->tot_len) + ret);
 211                csum_replace2(&iph->check, iph->tot_len, newlen);
 212                iph->tot_len = newlen;
 213        }
 214        return XT_CONTINUE;
 215}
 216
 217#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
 218static unsigned int
 219tcpmss_tg6(struct sk_buff *skb, const struct xt_action_param *par)
 220{
 221        struct ipv6hdr *ipv6h = ipv6_hdr(skb);
 222        u8 nexthdr;
 223        __be16 frag_off, oldlen, newlen;
 224        int tcphoff;
 225        int ret;
 226
 227        nexthdr = ipv6h->nexthdr;
 228        tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr, &frag_off);
 229        if (tcphoff < 0)
 230                return NF_DROP;
 231        ret = tcpmss_mangle_packet(skb, par,
 232                                   PF_INET6,
 233                                   tcphoff,
 234                                   sizeof(*ipv6h) + sizeof(struct tcphdr));
 235        if (ret < 0)
 236                return NF_DROP;
 237        if (ret > 0) {
 238                ipv6h = ipv6_hdr(skb);
 239                oldlen = ipv6h->payload_len;
 240                newlen = htons(ntohs(oldlen) + ret);
 241                if (skb->ip_summed == CHECKSUM_COMPLETE)
 242                        skb->csum = csum_add(csum_sub(skb->csum, oldlen),
 243                                             newlen);
 244                ipv6h->payload_len = newlen;
 245        }
 246        return XT_CONTINUE;
 247}
 248#endif
 249
 250/* Must specify -p tcp --syn */
 251static inline bool find_syn_match(const struct xt_entry_match *m)
 252{
 253        const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
 254
 255        if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
 256            tcpinfo->flg_cmp & TCPHDR_SYN &&
 257            !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
 258                return true;
 259
 260        return false;
 261}
 262
 263static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
 264{
 265        const struct xt_tcpmss_info *info = par->targinfo;
 266        const struct ipt_entry *e = par->entryinfo;
 267        const struct xt_entry_match *ematch;
 268
 269        if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
 270            (par->hook_mask & ~((1 << NF_INET_FORWARD) |
 271                           (1 << NF_INET_LOCAL_OUT) |
 272                           (1 << NF_INET_POST_ROUTING))) != 0) {
 273                pr_info_ratelimited("path-MTU clamping only supported in FORWARD, OUTPUT and POSTROUTING hooks\n");
 274                return -EINVAL;
 275        }
 276        if (par->nft_compat)
 277                return 0;
 278
 279        xt_ematch_foreach(ematch, e)
 280                if (find_syn_match(ematch))
 281                        return 0;
 282        pr_info_ratelimited("Only works on TCP SYN packets\n");
 283        return -EINVAL;
 284}
 285
 286#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
 287static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
 288{
 289        const struct xt_tcpmss_info *info = par->targinfo;
 290        const struct ip6t_entry *e = par->entryinfo;
 291        const struct xt_entry_match *ematch;
 292
 293        if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
 294            (par->hook_mask & ~((1 << NF_INET_FORWARD) |
 295                           (1 << NF_INET_LOCAL_OUT) |
 296                           (1 << NF_INET_POST_ROUTING))) != 0) {
 297                pr_info_ratelimited("path-MTU clamping only supported in FORWARD, OUTPUT and POSTROUTING hooks\n");
 298                return -EINVAL;
 299        }
 300        if (par->nft_compat)
 301                return 0;
 302
 303        xt_ematch_foreach(ematch, e)
 304                if (find_syn_match(ematch))
 305                        return 0;
 306        pr_info_ratelimited("Only works on TCP SYN packets\n");
 307        return -EINVAL;
 308}
 309#endif
 310
 311static struct xt_target tcpmss_tg_reg[] __read_mostly = {
 312        {
 313                .family         = NFPROTO_IPV4,
 314                .name           = "TCPMSS",
 315                .checkentry     = tcpmss_tg4_check,
 316                .target         = tcpmss_tg4,
 317                .targetsize     = sizeof(struct xt_tcpmss_info),
 318                .proto          = IPPROTO_TCP,
 319                .me             = THIS_MODULE,
 320        },
 321#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
 322        {
 323                .family         = NFPROTO_IPV6,
 324                .name           = "TCPMSS",
 325                .checkentry     = tcpmss_tg6_check,
 326                .target         = tcpmss_tg6,
 327                .targetsize     = sizeof(struct xt_tcpmss_info),
 328                .proto          = IPPROTO_TCP,
 329                .me             = THIS_MODULE,
 330        },
 331#endif
 332};
 333
 334static int __init tcpmss_tg_init(void)
 335{
 336        return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
 337}
 338
 339static void __exit tcpmss_tg_exit(void)
 340{
 341        xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
 342}
 343
 344module_init(tcpmss_tg_init);
 345module_exit(tcpmss_tg_exit);
 346