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 fl.fl4_dst = ip_hdr(skb)->saddr; 158 else 159 fl.fl6_dst = ipv6_hdr(skb)->saddr; 160 161 rcu_read_lock(); 162 ai = nf_get_afinfo(family); 163 if (ai != NULL) 164 ai->route((struct dst_entry **)&rt, &fl); 165 rcu_read_unlock(); 166 167 if (rt != NULL) { 168 mtu = dst_mtu(&rt->dst); 169 dst_release(&rt->dst); 170 } 171 return mtu; 172} 173 174static unsigned int 175tcpmss_tg4(struct sk_buff *skb, const struct xt_action_param *par) 176{ 177 struct iphdr *iph = ip_hdr(skb); 178 __be16 newlen; 179 int ret; 180 181 ret = tcpmss_mangle_packet(skb, par->targinfo, 182 tcpmss_reverse_mtu(skb, PF_INET), 183 iph->ihl * 4, 184 sizeof(*iph) + sizeof(struct tcphdr)); 185 if (ret < 0) 186 return NF_DROP; 187 if (ret > 0) { 188 iph = ip_hdr(skb); 189 newlen = htons(ntohs(iph->tot_len) + ret); 190 csum_replace2(&iph->check, iph->tot_len, newlen); 191 iph->tot_len = newlen; 192 } 193 return XT_CONTINUE; 194} 195 196#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE) 197static unsigned int 198tcpmss_tg6(struct sk_buff *skb, const struct xt_action_param *par) 199{ 200 struct ipv6hdr *ipv6h = ipv6_hdr(skb); 201 u8 nexthdr; 202 int tcphoff; 203 int ret; 204 205 nexthdr = ipv6h->nexthdr; 206 tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr); 207 if (tcphoff < 0) 208 return NF_DROP; 209 ret = tcpmss_mangle_packet(skb, par->targinfo, 210 tcpmss_reverse_mtu(skb, PF_INET6), 211 tcphoff, 212 sizeof(*ipv6h) + sizeof(struct tcphdr)); 213 if (ret < 0) 214 return NF_DROP; 215 if (ret > 0) { 216 ipv6h = ipv6_hdr(skb); 217 ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret); 218 } 219 return XT_CONTINUE; 220} 221#endif 222 223/* Must specify -p tcp --syn */ 224static inline bool find_syn_match(const struct xt_entry_match *m) 225{ 226 const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data; 227 228 if (strcmp(m->u.kernel.match->name, "tcp") == 0 && 229 tcpinfo->flg_cmp & TCPHDR_SYN && 230 !(tcpinfo->invflags & XT_TCP_INV_FLAGS)) 231 return true; 232 233 return false; 234} 235 236static int tcpmss_tg4_check(const struct xt_tgchk_param *par) 237{ 238 const struct xt_tcpmss_info *info = par->targinfo; 239 const struct ipt_entry *e = par->entryinfo; 240 const struct xt_entry_match *ematch; 241 242 if (info->mss == XT_TCPMSS_CLAMP_PMTU && 243 (par->hook_mask & ~((1 << NF_INET_FORWARD) | 244 (1 << NF_INET_LOCAL_OUT) | 245 (1 << NF_INET_POST_ROUTING))) != 0) { 246 pr_info("path-MTU clamping only supported in " 247 "FORWARD, OUTPUT and POSTROUTING hooks\n"); 248 return -EINVAL; 249 } 250 xt_ematch_foreach(ematch, e) 251 if (find_syn_match(ematch)) 252 return 0; 253 pr_info("Only works on TCP SYN packets\n"); 254 return -EINVAL; 255} 256 257#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE) 258static int tcpmss_tg6_check(const struct xt_tgchk_param *par) 259{ 260 const struct xt_tcpmss_info *info = par->targinfo; 261 const struct ip6t_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#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