at v3.10-rc4 319 lines 8.3 kB view raw
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_tcpmss_info *info, 49 unsigned int in_mtu, 50 unsigned int tcphoff, 51 unsigned int minlen) 52{ 53 struct tcphdr *tcph; 54 unsigned int tcplen, i; 55 __be16 oldval; 56 u16 newmss; 57 u8 *opt; 58 59 if (!skb_make_writable(skb, skb->len)) 60 return -1; 61 62 tcplen = skb->len - tcphoff; 63 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff); 64 65 /* Header cannot be larger than the packet */ 66 if (tcplen < tcph->doff*4) 67 return -1; 68 69 if (info->mss == XT_TCPMSS_CLAMP_PMTU) { 70 if (dst_mtu(skb_dst(skb)) <= minlen) { 71 net_err_ratelimited("unknown or invalid path-MTU (%u)\n", 72 dst_mtu(skb_dst(skb))); 73 return -1; 74 } 75 if (in_mtu <= minlen) { 76 net_err_ratelimited("unknown or invalid path-MTU (%u)\n", 77 in_mtu); 78 return -1; 79 } 80 newmss = min(dst_mtu(skb_dst(skb)), in_mtu) - minlen; 81 } else 82 newmss = info->mss; 83 84 opt = (u_int8_t *)tcph; 85 for (i = sizeof(struct tcphdr); i < tcph->doff*4; i += optlen(opt, i)) { 86 if (opt[i] == TCPOPT_MSS && tcph->doff*4 - i >= TCPOLEN_MSS && 87 opt[i+1] == TCPOLEN_MSS) { 88 u_int16_t oldmss; 89 90 oldmss = (opt[i+2] << 8) | opt[i+3]; 91 92 /* Never increase MSS, even when setting it, as 93 * doing so results in problems for hosts that rely 94 * on MSS being set correctly. 95 */ 96 if (oldmss <= newmss) 97 return 0; 98 99 opt[i+2] = (newmss & 0xff00) >> 8; 100 opt[i+3] = newmss & 0x00ff; 101 102 inet_proto_csum_replace2(&tcph->check, skb, 103 htons(oldmss), htons(newmss), 104 0); 105 return 0; 106 } 107 } 108 109 /* There is data after the header so the option can't be added 110 without moving it, and doing so may make the SYN packet 111 itself too large. Accept the packet unmodified instead. */ 112 if (tcplen > tcph->doff*4) 113 return 0; 114 115 /* 116 * MSS Option not found ?! add it.. 117 */ 118 if (skb_tailroom(skb) < TCPOLEN_MSS) { 119 if (pskb_expand_head(skb, 0, 120 TCPOLEN_MSS - skb_tailroom(skb), 121 GFP_ATOMIC)) 122 return -1; 123 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff); 124 } 125 126 skb_put(skb, TCPOLEN_MSS); 127 128 opt = (u_int8_t *)tcph + sizeof(struct tcphdr); 129 memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr)); 130 131 inet_proto_csum_replace2(&tcph->check, skb, 132 htons(tcplen), htons(tcplen + TCPOLEN_MSS), 1); 133 opt[0] = TCPOPT_MSS; 134 opt[1] = TCPOLEN_MSS; 135 opt[2] = (newmss & 0xff00) >> 8; 136 opt[3] = newmss & 0x00ff; 137 138 inet_proto_csum_replace4(&tcph->check, skb, 0, *((__be32 *)opt), 0); 139 140 oldval = ((__be16 *)tcph)[6]; 141 tcph->doff += TCPOLEN_MSS/4; 142 inet_proto_csum_replace2(&tcph->check, skb, 143 oldval, ((__be16 *)tcph)[6], 0); 144 return TCPOLEN_MSS; 145} 146 147static u_int32_t tcpmss_reverse_mtu(const struct sk_buff *skb, 148 unsigned int family) 149{ 150 struct flowi fl; 151 const struct nf_afinfo *ai; 152 struct rtable *rt = NULL; 153 u_int32_t mtu = ~0U; 154 155 if (family == PF_INET) { 156 struct flowi4 *fl4 = &fl.u.ip4; 157 memset(fl4, 0, sizeof(*fl4)); 158 fl4->daddr = ip_hdr(skb)->saddr; 159 } else { 160 struct flowi6 *fl6 = &fl.u.ip6; 161 162 memset(fl6, 0, sizeof(*fl6)); 163 fl6->daddr = ipv6_hdr(skb)->saddr; 164 } 165 rcu_read_lock(); 166 ai = nf_get_afinfo(family); 167 if (ai != NULL) 168 ai->route(&init_net, (struct dst_entry **)&rt, &fl, false); 169 rcu_read_unlock(); 170 171 if (rt != NULL) { 172 mtu = dst_mtu(&rt->dst); 173 dst_release(&rt->dst); 174 } 175 return mtu; 176} 177 178static unsigned int 179tcpmss_tg4(struct sk_buff *skb, const struct xt_action_param *par) 180{ 181 struct iphdr *iph = ip_hdr(skb); 182 __be16 newlen; 183 int ret; 184 185 ret = tcpmss_mangle_packet(skb, par->targinfo, 186 tcpmss_reverse_mtu(skb, PF_INET), 187 iph->ihl * 4, 188 sizeof(*iph) + sizeof(struct tcphdr)); 189 if (ret < 0) 190 return NF_DROP; 191 if (ret > 0) { 192 iph = ip_hdr(skb); 193 newlen = htons(ntohs(iph->tot_len) + ret); 194 csum_replace2(&iph->check, iph->tot_len, newlen); 195 iph->tot_len = newlen; 196 } 197 return XT_CONTINUE; 198} 199 200#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) 201static unsigned int 202tcpmss_tg6(struct sk_buff *skb, const struct xt_action_param *par) 203{ 204 struct ipv6hdr *ipv6h = ipv6_hdr(skb); 205 u8 nexthdr; 206 __be16 frag_off; 207 int tcphoff; 208 int ret; 209 210 nexthdr = ipv6h->nexthdr; 211 tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr, &frag_off); 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 IS_ENABLED(CONFIG_IP6_NF_IPTABLES) 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 IS_ENABLED(CONFIG_IP6_NF_IPTABLES) 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);