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