blob: c5f4b9919e9a016ef3f3428440e2446571293920 [file] [log] [blame]
Patrick McHardycdd289a2007-02-07 15:09:46 -08001/*
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>
Tejun Heo5a0e3ad2010-03-24 17:04:11 +090014#include <linux/gfp.h>
Patrick McHardycdd289a2007-02-07 15:09:46 -080015#include <linux/ipv6.h>
16#include <linux/tcp.h>
Jan Engelhardt37c08382008-01-31 04:06:10 -080017#include <net/dst.h>
18#include <net/flow.h>
Patrick McHardycdd289a2007-02-07 15:09:46 -080019#include <net/ipv6.h>
Jan Engelhardt37c08382008-01-31 04:06:10 -080020#include <net/route.h>
Patrick McHardycdd289a2007-02-07 15:09:46 -080021#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>");
Jan Engelhardt2ae15b62008-01-14 23:42:28 -080031MODULE_DESCRIPTION("Xtables: TCP Maximum Segment Size (MSS) adjustment");
Patrick McHardycdd289a2007-02-07 15:09:46 -080032MODULE_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
Herbert Xu3db05fe2007-10-15 00:53:15 -070046tcpmss_mangle_packet(struct sk_buff *skb,
Patrick McHardycdd289a2007-02-07 15:09:46 -080047 const struct xt_tcpmss_info *info,
Jan Engelhardt37c08382008-01-31 04:06:10 -080048 unsigned int in_mtu,
Patrick McHardycdd289a2007-02-07 15:09:46 -080049 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
Herbert Xu3db05fe2007-10-15 00:53:15 -070058 if (!skb_make_writable(skb, skb->len))
Patrick McHardycdd289a2007-02-07 15:09:46 -080059 return -1;
60
Herbert Xu3db05fe2007-10-15 00:53:15 -070061 tcplen = skb->len - tcphoff;
62 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
Patrick McHardycdd289a2007-02-07 15:09:46 -080063
Simon Arlott10a19932010-02-02 15:33:38 +010064 /* Header cannot be larger than the packet */
65 if (tcplen < tcph->doff*4)
Patrick McHardycdd289a2007-02-07 15:09:46 -080066 return -1;
Patrick McHardycdd289a2007-02-07 15:09:46 -080067
68 if (info->mss == XT_TCPMSS_CLAMP_PMTU) {
Eric Dumazetadf30902009-06-02 05:19:30 +000069 if (dst_mtu(skb_dst(skb)) <= minlen) {
Patrick McHardycdd289a2007-02-07 15:09:46 -080070 if (net_ratelimit())
71 printk(KERN_ERR "xt_TCPMSS: "
72 "unknown or invalid path-MTU (%u)\n",
Eric Dumazetadf30902009-06-02 05:19:30 +000073 dst_mtu(skb_dst(skb)));
Patrick McHardycdd289a2007-02-07 15:09:46 -080074 return -1;
75 }
Jan Engelhardt37c08382008-01-31 04:06:10 -080076 if (in_mtu <= minlen) {
77 if (net_ratelimit())
78 printk(KERN_ERR "xt_TCPMSS: unknown or "
79 "invalid path-MTU (%u)\n", in_mtu);
80 return -1;
81 }
Eric Dumazetadf30902009-06-02 05:19:30 +000082 newmss = min(dst_mtu(skb_dst(skb)), in_mtu) - minlen;
Patrick McHardycdd289a2007-02-07 15:09:46 -080083 } else
84 newmss = info->mss;
85
86 opt = (u_int8_t *)tcph;
87 for (i = sizeof(struct tcphdr); i < tcph->doff*4; i += optlen(opt, i)) {
88 if (opt[i] == TCPOPT_MSS && tcph->doff*4 - i >= TCPOLEN_MSS &&
89 opt[i+1] == TCPOLEN_MSS) {
90 u_int16_t oldmss;
91
92 oldmss = (opt[i+2] << 8) | opt[i+3];
93
Benjamin LaHaise17008062007-12-17 22:27:36 -080094 /* Never increase MSS, even when setting it, as
95 * doing so results in problems for hosts that rely
96 * on MSS being set correctly.
97 */
98 if (oldmss <= newmss)
Patrick McHardycdd289a2007-02-07 15:09:46 -080099 return 0;
100
101 opt[i+2] = (newmss & 0xff00) >> 8;
Jan Engelhardt7c4e36b2007-07-07 22:19:08 -0700102 opt[i+3] = newmss & 0x00ff;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800103
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100104 inet_proto_csum_replace2(&tcph->check, skb,
105 htons(oldmss), htons(newmss),
106 0);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800107 return 0;
108 }
109 }
110
Simon Arlott10a19932010-02-02 15:33:38 +0100111 /* There is data after the header so the option can't be added
112 without moving it, and doing so may make the SYN packet
113 itself too large. Accept the packet unmodified instead. */
114 if (tcplen > tcph->doff*4)
115 return 0;
116
Patrick McHardycdd289a2007-02-07 15:09:46 -0800117 /*
118 * MSS Option not found ?! add it..
119 */
Herbert Xu3db05fe2007-10-15 00:53:15 -0700120 if (skb_tailroom(skb) < TCPOLEN_MSS) {
121 if (pskb_expand_head(skb, 0,
122 TCPOLEN_MSS - skb_tailroom(skb),
Herbert Xu2ca7b0a2007-10-14 00:39:55 -0700123 GFP_ATOMIC))
Patrick McHardycdd289a2007-02-07 15:09:46 -0800124 return -1;
Herbert Xu3db05fe2007-10-15 00:53:15 -0700125 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800126 }
127
Herbert Xu3db05fe2007-10-15 00:53:15 -0700128 skb_put(skb, TCPOLEN_MSS);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800129
130 opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
131 memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr));
132
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100133 inet_proto_csum_replace2(&tcph->check, skb,
134 htons(tcplen), htons(tcplen + TCPOLEN_MSS), 1);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800135 opt[0] = TCPOPT_MSS;
136 opt[1] = TCPOLEN_MSS;
137 opt[2] = (newmss & 0xff00) >> 8;
Jan Engelhardt7c4e36b2007-07-07 22:19:08 -0700138 opt[3] = newmss & 0x00ff;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800139
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100140 inet_proto_csum_replace4(&tcph->check, skb, 0, *((__be32 *)opt), 0);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800141
142 oldval = ((__be16 *)tcph)[6];
143 tcph->doff += TCPOLEN_MSS/4;
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100144 inet_proto_csum_replace2(&tcph->check, skb,
145 oldval, ((__be16 *)tcph)[6], 0);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800146 return TCPOLEN_MSS;
147}
148
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700149static u_int32_t tcpmss_reverse_mtu(const struct sk_buff *skb,
150 unsigned int family)
Jan Engelhardt37c08382008-01-31 04:06:10 -0800151{
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700152 struct flowi fl = {};
Jan Engelhardt37c08382008-01-31 04:06:10 -0800153 const struct nf_afinfo *ai;
154 struct rtable *rt = NULL;
155 u_int32_t mtu = ~0U;
156
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700157 if (family == PF_INET)
158 fl.fl4_dst = ip_hdr(skb)->saddr;
159 else
160 fl.fl6_dst = ipv6_hdr(skb)->saddr;
161
Jan Engelhardt37c08382008-01-31 04:06:10 -0800162 rcu_read_lock();
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700163 ai = nf_get_afinfo(family);
Jan Engelhardt37c08382008-01-31 04:06:10 -0800164 if (ai != NULL)
165 ai->route((struct dst_entry **)&rt, &fl);
166 rcu_read_unlock();
167
168 if (rt != NULL) {
169 mtu = dst_mtu(&rt->u.dst);
170 dst_release(&rt->u.dst);
171 }
172 return mtu;
173}
174
Patrick McHardycdd289a2007-02-07 15:09:46 -0800175static unsigned int
Jan Engelhardt7eb35582008-10-08 11:35:19 +0200176tcpmss_tg4(struct sk_buff *skb, const struct xt_target_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800177{
Herbert Xu3db05fe2007-10-15 00:53:15 -0700178 struct iphdr *iph = ip_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800179 __be16 newlen;
180 int ret;
181
Jan Engelhardt7eb35582008-10-08 11:35:19 +0200182 ret = tcpmss_mangle_packet(skb, par->targinfo,
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700183 tcpmss_reverse_mtu(skb, PF_INET),
Jan Engelhardt37c08382008-01-31 04:06:10 -0800184 iph->ihl * 4,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800185 sizeof(*iph) + sizeof(struct tcphdr));
186 if (ret < 0)
187 return NF_DROP;
188 if (ret > 0) {
Herbert Xu3db05fe2007-10-15 00:53:15 -0700189 iph = ip_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800190 newlen = htons(ntohs(iph->tot_len) + ret);
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100191 csum_replace2(&iph->check, iph->tot_len, newlen);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800192 iph->tot_len = newlen;
193 }
194 return XT_CONTINUE;
195}
196
197#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
198static unsigned int
Jan Engelhardt7eb35582008-10-08 11:35:19 +0200199tcpmss_tg6(struct sk_buff *skb, const struct xt_target_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800200{
Herbert Xu3db05fe2007-10-15 00:53:15 -0700201 struct ipv6hdr *ipv6h = ipv6_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800202 u8 nexthdr;
203 int tcphoff;
204 int ret;
205
206 nexthdr = ipv6h->nexthdr;
Herbert Xu3db05fe2007-10-15 00:53:15 -0700207 tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr);
Patrick McHardy9dc05642007-11-30 23:58:03 +1100208 if (tcphoff < 0)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800209 return NF_DROP;
Jan Engelhardt7eb35582008-10-08 11:35:19 +0200210 ret = tcpmss_mangle_packet(skb, par->targinfo,
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700211 tcpmss_reverse_mtu(skb, PF_INET6),
Jan Engelhardt37c08382008-01-31 04:06:10 -0800212 tcphoff,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800213 sizeof(*ipv6h) + sizeof(struct tcphdr));
214 if (ret < 0)
215 return NF_DROP;
216 if (ret > 0) {
Herbert Xu3db05fe2007-10-15 00:53:15 -0700217 ipv6h = ipv6_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800218 ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
219 }
220 return XT_CONTINUE;
221}
222#endif
223
224#define TH_SYN 0x02
225
226/* Must specify -p tcp --syn */
Jan Engelhardte1931b72007-07-07 22:16:26 -0700227static inline bool find_syn_match(const struct xt_entry_match *m)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800228{
229 const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
230
231 if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
232 tcpinfo->flg_cmp & TH_SYN &&
233 !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
Jan Engelhardte1931b72007-07-07 22:16:26 -0700234 return true;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800235
Jan Engelhardte1931b72007-07-07 22:16:26 -0700236 return false;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800237}
238
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200239static bool tcpmss_tg4_check(const struct xt_tgchk_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800240{
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200241 const struct xt_tcpmss_info *info = par->targinfo;
242 const struct ipt_entry *e = par->entryinfo;
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100243 const struct xt_entry_match *ematch;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800244
245 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200246 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
Patrick McHardy6e23ae22007-11-19 18:53:30 -0800247 (1 << NF_INET_LOCAL_OUT) |
248 (1 << NF_INET_POST_ROUTING))) != 0) {
Patrick McHardycdd289a2007-02-07 15:09:46 -0800249 printk("xt_TCPMSS: path-MTU clamping only supported in "
250 "FORWARD, OUTPUT and POSTROUTING hooks\n");
Jan Engelhardte1931b72007-07-07 22:16:26 -0700251 return false;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800252 }
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100253 xt_ematch_foreach(ematch, e)
254 if (find_syn_match(ematch))
255 return true;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800256 printk("xt_TCPMSS: Only works on TCP SYN packets\n");
Jan Engelhardte1931b72007-07-07 22:16:26 -0700257 return false;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800258}
259
260#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200261static bool tcpmss_tg6_check(const struct xt_tgchk_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800262{
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200263 const struct xt_tcpmss_info *info = par->targinfo;
264 const struct ip6t_entry *e = par->entryinfo;
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100265 const struct xt_entry_match *ematch;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800266
267 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200268 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
Patrick McHardy6e23ae22007-11-19 18:53:30 -0800269 (1 << NF_INET_LOCAL_OUT) |
270 (1 << NF_INET_POST_ROUTING))) != 0) {
Patrick McHardycdd289a2007-02-07 15:09:46 -0800271 printk("xt_TCPMSS: path-MTU clamping only supported in "
272 "FORWARD, OUTPUT and POSTROUTING hooks\n");
Jan Engelhardte1931b72007-07-07 22:16:26 -0700273 return false;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800274 }
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100275 xt_ematch_foreach(ematch, e)
276 if (find_syn_match(ematch))
277 return true;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800278 printk("xt_TCPMSS: Only works on TCP SYN packets\n");
Jan Engelhardte1931b72007-07-07 22:16:26 -0700279 return false;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800280}
281#endif
282
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800283static struct xt_target tcpmss_tg_reg[] __read_mostly = {
Patrick McHardycdd289a2007-02-07 15:09:46 -0800284 {
Jan Engelhardtee999d82008-10-08 11:35:01 +0200285 .family = NFPROTO_IPV4,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800286 .name = "TCPMSS",
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800287 .checkentry = tcpmss_tg4_check,
288 .target = tcpmss_tg4,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800289 .targetsize = sizeof(struct xt_tcpmss_info),
290 .proto = IPPROTO_TCP,
291 .me = THIS_MODULE,
292 },
293#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
294 {
Jan Engelhardtee999d82008-10-08 11:35:01 +0200295 .family = NFPROTO_IPV6,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800296 .name = "TCPMSS",
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800297 .checkentry = tcpmss_tg6_check,
298 .target = tcpmss_tg6,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800299 .targetsize = sizeof(struct xt_tcpmss_info),
300 .proto = IPPROTO_TCP,
301 .me = THIS_MODULE,
302 },
303#endif
304};
305
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800306static int __init tcpmss_tg_init(void)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800307{
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800308 return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
Patrick McHardycdd289a2007-02-07 15:09:46 -0800309}
310
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800311static void __exit tcpmss_tg_exit(void)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800312{
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800313 xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
Patrick McHardycdd289a2007-02-07 15:09:46 -0800314}
315
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800316module_init(tcpmss_tg_init);
317module_exit(tcpmss_tg_exit);