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 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License version 2 as
   8 * published by the Free Software Foundation.
   9 */
  10
  11#include <linux/module.h>
  12#include <linux/skbuff.h>
  13#include <linux/ip.h>
  14#include <linux/ipv6.h>
  15#include <linux/tcp.h>
  16#include <net/dst.h>
  17#include <net/flow.h>
  18#include <net/ipv6.h>
  19#include <net/route.h>
  20#include <net/tcp.h>
  21
  22#include <linux/netfilter_ipv4/ip_tables.h>
  23#include <linux/netfilter_ipv6/ip6_tables.h>
  24#include <linux/netfilter/x_tables.h>
  25#include <linux/netfilter/xt_tcpudp.h>
  26#include <linux/netfilter/xt_TCPMSS.h>
  27
  28MODULE_LICENSE("GPL");
  29MODULE_AUTHOR("Marc Boucher <marc@mbsi.ca>");
  30MODULE_DESCRIPTION("Xtables: TCP Maximum Segment Size (MSS) adjustment");
  31MODULE_ALIAS("ipt_TCPMSS");
  32MODULE_ALIAS("ip6t_TCPMSS");
  33
  34static inline unsigned int
  35optlen(const u_int8_t *opt, unsigned int offset)
  36{
  37        /* Beware zero-length options: make finite progress */
  38        if (opt[offset] <= TCPOPT_NOP || opt[offset+1] == 0)
  39                return 1;
  40        else
  41                return opt[offset+1];
  42}
  43
  44static int
  45tcpmss_mangle_packet(struct sk_buff *skb,
  46                     const struct xt_tcpmss_info *info,
  47                     unsigned int in_mtu,
  48                     unsigned int tcphoff,
  49                     unsigned int minlen)
  50{
  51        struct tcphdr *tcph;
  52        unsigned int tcplen, i;
  53        __be16 oldval;
  54        u16 newmss;
  55        u8 *opt;
  56
  57        if (!skb_make_writable(skb, skb->len))
  58                return -1;
  59
  60        tcplen = skb->len - tcphoff;
  61        tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
  62
  63        /* Since it passed flags test in tcp match, we know it is is
  64           not a fragment, and has data >= tcp header length.  SYN
  65           packets should not contain data: if they did, then we risk
  66           running over MTU, sending Frag Needed and breaking things
  67           badly. --RR */
  68        if (tcplen != tcph->doff*4) {
  69                if (net_ratelimit())
  70                        printk(KERN_ERR "xt_TCPMSS: bad length (%u bytes)\n",
  71                               skb->len);
  72                return -1;
  73        }
  74
  75        if (info->mss == XT_TCPMSS_CLAMP_PMTU) {
  76                if (dst_mtu(skb_dst(skb)) <= minlen) {
  77                        if (net_ratelimit())
  78                                printk(KERN_ERR "xt_TCPMSS: "
  79                                       "unknown or invalid path-MTU (%u)\n",
  80                                       dst_mtu(skb_dst(skb)));
  81                        return -1;
  82                }
  83                if (in_mtu <= minlen) {
  84                        if (net_ratelimit())
  85                                printk(KERN_ERR "xt_TCPMSS: unknown or "
  86                                       "invalid path-MTU (%u)\n", in_mtu);
  87                        return -1;
  88                }
  89                newmss = min(dst_mtu(skb_dst(skb)), in_mtu) - minlen;
  90        } else
  91                newmss = info->mss;
  92
  93        opt = (u_int8_t *)tcph;
  94        for (i = sizeof(struct tcphdr); i < tcph->doff*4; i += optlen(opt, i)) {
  95                if (opt[i] == TCPOPT_MSS && tcph->doff*4 - i >= TCPOLEN_MSS &&
  96                    opt[i+1] == TCPOLEN_MSS) {
  97                        u_int16_t oldmss;
  98
  99                        oldmss = (opt[i+2] << 8) | opt[i+3];
 100
 101                        /* Never increase MSS, even when setting it, as
 102                         * doing so results in problems for hosts that rely
 103                         * on MSS being set correctly.
 104                         */
 105                        if (oldmss <= newmss)
 106                                return 0;
 107
 108                        opt[i+2] = (newmss & 0xff00) >> 8;
 109                        opt[i+3] = newmss & 0x00ff;
 110
 111                        inet_proto_csum_replace2(&tcph->check, skb,
 112                                                 htons(oldmss), htons(newmss),
 113                                                 0);
 114                        return 0;
 115                }
 116        }
 117
 118        /*
 119         * MSS Option not found ?! add it..
 120         */
 121        if (skb_tailroom(skb) < TCPOLEN_MSS) {
 122                if (pskb_expand_head(skb, 0,
 123                                     TCPOLEN_MSS - skb_tailroom(skb),
 124                                     GFP_ATOMIC))
 125                        return -1;
 126                tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
 127        }
 128
 129        skb_put(skb, TCPOLEN_MSS);
 130
 131        opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
 132        memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr));
 133
 134        inet_proto_csum_replace2(&tcph->check, skb,
 135                                 htons(tcplen), htons(tcplen + TCPOLEN_MSS), 1);
 136        opt[0] = TCPOPT_MSS;
 137        opt[1] = TCPOLEN_MSS;
 138        opt[2] = (newmss & 0xff00) >> 8;
 139        opt[3] = newmss & 0x00ff;
 140
 141        inet_proto_csum_replace4(&tcph->check, skb, 0, *((__be32 *)opt), 0);
 142
 143        oldval = ((__be16 *)tcph)[6];
 144        tcph->doff += TCPOLEN_MSS/4;
 145        inet_proto_csum_replace2(&tcph->check, skb,
 146                                 oldval, ((__be16 *)tcph)[6], 0);
 147        return TCPOLEN_MSS;
 148}
 149
 150static u_int32_t tcpmss_reverse_mtu(const struct sk_buff *skb,
 151                                    unsigned int family)
 152{
 153        struct flowi fl = {};
 154        const struct nf_afinfo *ai;
 155        struct rtable *rt = NULL;
 156        u_int32_t mtu     = ~0U;
 157
 158        if (family == PF_INET)
 159                fl.fl4_dst = ip_hdr(skb)->saddr;
 160        else
 161                fl.fl6_dst = ipv6_hdr(skb)->saddr;
 162
 163        rcu_read_lock();
 164        ai = nf_get_afinfo(family);
 165        if (ai != NULL)
 166                ai->route((struct dst_entry **)&rt, &fl);
 167        rcu_read_unlock();
 168
 169        if (rt != NULL) {
 170                mtu = dst_mtu(&rt->u.dst);
 171                dst_release(&rt->u.dst);
 172        }
 173        return mtu;
 174}
 175
 176static unsigned int
 177tcpmss_tg4(struct sk_buff *skb, const struct xt_target_param *par)
 178{
 179        struct iphdr *iph = ip_hdr(skb);
 180        __be16 newlen;
 181        int ret;
 182
 183        ret = tcpmss_mangle_packet(skb, par->targinfo,
 184                                   tcpmss_reverse_mtu(skb, PF_INET),
 185                                   iph->ihl * 4,
 186                                   sizeof(*iph) + sizeof(struct tcphdr));
 187        if (ret < 0)
 188                return NF_DROP;
 189        if (ret > 0) {
 190                iph = ip_hdr(skb);
 191                newlen = htons(ntohs(iph->tot_len) + ret);
 192                csum_replace2(&iph->check, iph->tot_len, newlen);
 193                iph->tot_len = newlen;
 194        }
 195        return XT_CONTINUE;
 196}
 197
 198#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
 199static unsigned int
 200tcpmss_tg6(struct sk_buff *skb, const struct xt_target_param *par)
 201{
 202        struct ipv6hdr *ipv6h = ipv6_hdr(skb);
 203        u8 nexthdr;
 204        int tcphoff;
 205        int ret;
 206
 207        nexthdr = ipv6h->nexthdr;
 208        tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr);
 209        if (tcphoff < 0)
 210                return NF_DROP;
 211        ret = tcpmss_mangle_packet(skb, par->targinfo,
 212                                   tcpmss_reverse_mtu(skb, PF_INET6),
 213                                   tcphoff,
 214                                   sizeof(*ipv6h) + sizeof(struct tcphdr));
 215        if (ret < 0)
 216                return NF_DROP;
 217        if (ret > 0) {
 218                ipv6h = ipv6_hdr(skb);
 219                ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
 220        }
 221        return XT_CONTINUE;
 222}
 223#endif
 224
 225#define TH_SYN 0x02
 226
 227/* Must specify -p tcp --syn */
 228static inline bool find_syn_match(const struct xt_entry_match *m)
 229{
 230        const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
 231
 232        if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
 233            tcpinfo->flg_cmp & TH_SYN &&
 234            !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
 235                return true;
 236
 237        return false;
 238}
 239
 240static bool tcpmss_tg4_check(const struct xt_tgchk_param *par)
 241{
 242        const struct xt_tcpmss_info *info = par->targinfo;
 243        const struct ipt_entry *e = par->entryinfo;
 244
 245        if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
 246            (par->hook_mask & ~((1 << NF_INET_FORWARD) |
 247                           (1 << NF_INET_LOCAL_OUT) |
 248                           (1 << NF_INET_POST_ROUTING))) != 0) {
 249                printk("xt_TCPMSS: path-MTU clamping only supported in "
 250                       "FORWARD, OUTPUT and POSTROUTING hooks\n");
 251                return false;
 252        }
 253        if (IPT_MATCH_ITERATE(e, find_syn_match))
 254                return true;
 255        printk("xt_TCPMSS: Only works on TCP SYN packets\n");
 256        return false;
 257}
 258
 259#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
 260static bool tcpmss_tg6_check(const struct xt_tgchk_param *par)
 261{
 262        const struct xt_tcpmss_info *info = par->targinfo;
 263        const struct ip6t_entry *e = par->entryinfo;
 264
 265        if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
 266            (par->hook_mask & ~((1 << NF_INET_FORWARD) |
 267                           (1 << NF_INET_LOCAL_OUT) |
 268                           (1 << NF_INET_POST_ROUTING))) != 0) {
 269                printk("xt_TCPMSS: path-MTU clamping only supported in "
 270                       "FORWARD, OUTPUT and POSTROUTING hooks\n");
 271                return false;
 272        }
 273        if (IP6T_MATCH_ITERATE(e, find_syn_match))
 274                return true;
 275        printk("xt_TCPMSS: Only works on TCP SYN packets\n");
 276        return false;
 277}
 278#endif
 279
 280static struct xt_target tcpmss_tg_reg[] __read_mostly = {
 281        {
 282                .family         = NFPROTO_IPV4,
 283                .name           = "TCPMSS",
 284                .checkentry     = tcpmss_tg4_check,
 285                .target         = tcpmss_tg4,
 286                .targetsize     = sizeof(struct xt_tcpmss_info),
 287                .proto          = IPPROTO_TCP,
 288                .me             = THIS_MODULE,
 289        },
 290#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
 291        {
 292                .family         = NFPROTO_IPV6,
 293                .name           = "TCPMSS",
 294                .checkentry     = tcpmss_tg6_check,
 295                .target         = tcpmss_tg6,
 296                .targetsize     = sizeof(struct xt_tcpmss_info),
 297                .proto          = IPPROTO_TCP,
 298                .me             = THIS_MODULE,
 299        },
 300#endif
 301};
 302
 303static int __init tcpmss_tg_init(void)
 304{
 305        return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
 306}
 307
 308static void __exit tcpmss_tg_exit(void)
 309{
 310        xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
 311}
 312
 313module_init(tcpmss_tg_init);
 314module_exit(tcpmss_tg_exit);
 315