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