blob: eb81c380da1ba11d2f212499f43636ef09f9a327 [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 */
Jan Engelhardt8bee4ba2010-03-17 16:04:40 +010010#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
Patrick McHardycdd289a2007-02-07 15:09:46 -080011#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())
Jan Engelhardtff67e4e2010-03-19 21:08:16 +010071 pr_err("unknown or invalid path-MTU (%u)\n",
Eric Dumazetadf30902009-06-02 05:19:30 +000072 dst_mtu(skb_dst(skb)));
Patrick McHardycdd289a2007-02-07 15:09:46 -080073 return -1;
74 }
Jan Engelhardt37c08382008-01-31 04:06:10 -080075 if (in_mtu <= minlen) {
76 if (net_ratelimit())
Jan Engelhardtff67e4e2010-03-19 21:08:16 +010077 pr_err("unknown or invalid path-MTU (%u)\n",
78 in_mtu);
Jan Engelhardt37c08382008-01-31 04:06:10 -080079 return -1;
80 }
Eric Dumazetadf30902009-06-02 05:19:30 +000081 newmss = min(dst_mtu(skb_dst(skb)), in_mtu) - minlen;
Patrick McHardycdd289a2007-02-07 15:09:46 -080082 } 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
Benjamin LaHaise17008062007-12-17 22:27:36 -080093 /* 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)
Patrick McHardycdd289a2007-02-07 15:09:46 -080098 return 0;
99
100 opt[i+2] = (newmss & 0xff00) >> 8;
Jan Engelhardt7c4e36b2007-07-07 22:19:08 -0700101 opt[i+3] = newmss & 0x00ff;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800102
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100103 inet_proto_csum_replace2(&tcph->check, skb,
104 htons(oldmss), htons(newmss),
105 0);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800106 return 0;
107 }
108 }
109
Simon Arlott10a19932010-02-02 15:33:38 +0100110 /* 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
Patrick McHardycdd289a2007-02-07 15:09:46 -0800116 /*
117 * MSS Option not found ?! add it..
118 */
Herbert Xu3db05fe2007-10-15 00:53:15 -0700119 if (skb_tailroom(skb) < TCPOLEN_MSS) {
120 if (pskb_expand_head(skb, 0,
121 TCPOLEN_MSS - skb_tailroom(skb),
Herbert Xu2ca7b0a2007-10-14 00:39:55 -0700122 GFP_ATOMIC))
Patrick McHardycdd289a2007-02-07 15:09:46 -0800123 return -1;
Herbert Xu3db05fe2007-10-15 00:53:15 -0700124 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800125 }
126
Herbert Xu3db05fe2007-10-15 00:53:15 -0700127 skb_put(skb, TCPOLEN_MSS);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800128
129 opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
130 memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr));
131
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100132 inet_proto_csum_replace2(&tcph->check, skb,
133 htons(tcplen), htons(tcplen + TCPOLEN_MSS), 1);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800134 opt[0] = TCPOPT_MSS;
135 opt[1] = TCPOLEN_MSS;
136 opt[2] = (newmss & 0xff00) >> 8;
Jan Engelhardt7c4e36b2007-07-07 22:19:08 -0700137 opt[3] = newmss & 0x00ff;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800138
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100139 inet_proto_csum_replace4(&tcph->check, skb, 0, *((__be32 *)opt), 0);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800140
141 oldval = ((__be16 *)tcph)[6];
142 tcph->doff += TCPOLEN_MSS/4;
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100143 inet_proto_csum_replace2(&tcph->check, skb,
144 oldval, ((__be16 *)tcph)[6], 0);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800145 return TCPOLEN_MSS;
146}
147
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700148static u_int32_t tcpmss_reverse_mtu(const struct sk_buff *skb,
149 unsigned int family)
Jan Engelhardt37c08382008-01-31 04:06:10 -0800150{
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700151 struct flowi fl = {};
Jan Engelhardt37c08382008-01-31 04:06:10 -0800152 const struct nf_afinfo *ai;
153 struct rtable *rt = NULL;
154 u_int32_t mtu = ~0U;
155
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700156 if (family == PF_INET)
157 fl.fl4_dst = ip_hdr(skb)->saddr;
158 else
159 fl.fl6_dst = ipv6_hdr(skb)->saddr;
160
Jan Engelhardt37c08382008-01-31 04:06:10 -0800161 rcu_read_lock();
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700162 ai = nf_get_afinfo(family);
Jan Engelhardt37c08382008-01-31 04:06:10 -0800163 if (ai != NULL)
164 ai->route((struct dst_entry **)&rt, &fl);
165 rcu_read_unlock();
166
167 if (rt != NULL) {
Changli Gaod8d1f302010-06-10 23:31:35 -0700168 mtu = dst_mtu(&rt->dst);
169 dst_release(&rt->dst);
Jan Engelhardt37c08382008-01-31 04:06:10 -0800170 }
171 return mtu;
172}
173
Patrick McHardycdd289a2007-02-07 15:09:46 -0800174static unsigned int
Jan Engelhardt4b560b42009-07-05 19:43:26 +0200175tcpmss_tg4(struct sk_buff *skb, const struct xt_action_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800176{
Herbert Xu3db05fe2007-10-15 00:53:15 -0700177 struct iphdr *iph = ip_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800178 __be16 newlen;
179 int ret;
180
Jan Engelhardt7eb35582008-10-08 11:35:19 +0200181 ret = tcpmss_mangle_packet(skb, par->targinfo,
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700182 tcpmss_reverse_mtu(skb, PF_INET),
Jan Engelhardt37c08382008-01-31 04:06:10 -0800183 iph->ihl * 4,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800184 sizeof(*iph) + sizeof(struct tcphdr));
185 if (ret < 0)
186 return NF_DROP;
187 if (ret > 0) {
Herbert Xu3db05fe2007-10-15 00:53:15 -0700188 iph = ip_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800189 newlen = htons(ntohs(iph->tot_len) + ret);
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100190 csum_replace2(&iph->check, iph->tot_len, newlen);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800191 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
Jan Engelhardt4b560b42009-07-05 19:43:26 +0200198tcpmss_tg6(struct sk_buff *skb, const struct xt_action_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800199{
Herbert Xu3db05fe2007-10-15 00:53:15 -0700200 struct ipv6hdr *ipv6h = ipv6_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800201 u8 nexthdr;
202 int tcphoff;
203 int ret;
204
205 nexthdr = ipv6h->nexthdr;
Herbert Xu3db05fe2007-10-15 00:53:15 -0700206 tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr);
Patrick McHardy9dc05642007-11-30 23:58:03 +1100207 if (tcphoff < 0)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800208 return NF_DROP;
Jan Engelhardt7eb35582008-10-08 11:35:19 +0200209 ret = tcpmss_mangle_packet(skb, par->targinfo,
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700210 tcpmss_reverse_mtu(skb, PF_INET6),
Jan Engelhardt37c08382008-01-31 04:06:10 -0800211 tcphoff,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800212 sizeof(*ipv6h) + sizeof(struct tcphdr));
213 if (ret < 0)
214 return NF_DROP;
215 if (ret > 0) {
Herbert Xu3db05fe2007-10-15 00:53:15 -0700216 ipv6h = ipv6_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800217 ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
218 }
219 return XT_CONTINUE;
220}
221#endif
222
Patrick McHardycdd289a2007-02-07 15:09:46 -0800223/* Must specify -p tcp --syn */
Jan Engelhardte1931b72007-07-07 22:16:26 -0700224static inline bool find_syn_match(const struct xt_entry_match *m)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800225{
226 const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
227
228 if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
Changli Gaoa3433f32010-06-12 14:01:43 +0000229 tcpinfo->flg_cmp & TCPHDR_SYN &&
Patrick McHardycdd289a2007-02-07 15:09:46 -0800230 !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
Jan Engelhardte1931b72007-07-07 22:16:26 -0700231 return true;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800232
Jan Engelhardte1931b72007-07-07 22:16:26 -0700233 return false;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800234}
235
Jan Engelhardt135367b2010-03-19 17:16:42 +0100236static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800237{
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200238 const struct xt_tcpmss_info *info = par->targinfo;
239 const struct ipt_entry *e = par->entryinfo;
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100240 const struct xt_entry_match *ematch;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800241
242 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200243 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
Patrick McHardy6e23ae22007-11-19 18:53:30 -0800244 (1 << NF_INET_LOCAL_OUT) |
245 (1 << NF_INET_POST_ROUTING))) != 0) {
Jan Engelhardt8bee4ba2010-03-17 16:04:40 +0100246 pr_info("path-MTU clamping only supported in "
247 "FORWARD, OUTPUT and POSTROUTING hooks\n");
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100248 return -EINVAL;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800249 }
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100250 xt_ematch_foreach(ematch, e)
251 if (find_syn_match(ematch))
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100252 return 0;
Jan Engelhardt8bee4ba2010-03-17 16:04:40 +0100253 pr_info("Only works on TCP SYN packets\n");
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100254 return -EINVAL;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800255}
256
257#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
Jan Engelhardt135367b2010-03-19 17:16:42 +0100258static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800259{
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200260 const struct xt_tcpmss_info *info = par->targinfo;
261 const struct ip6t_entry *e = par->entryinfo;
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100262 const struct xt_entry_match *ematch;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800263
264 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200265 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
Patrick McHardy6e23ae22007-11-19 18:53:30 -0800266 (1 << NF_INET_LOCAL_OUT) |
267 (1 << NF_INET_POST_ROUTING))) != 0) {
Jan Engelhardt8bee4ba2010-03-17 16:04:40 +0100268 pr_info("path-MTU clamping only supported in "
269 "FORWARD, OUTPUT and POSTROUTING hooks\n");
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100270 return -EINVAL;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800271 }
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100272 xt_ematch_foreach(ematch, e)
273 if (find_syn_match(ematch))
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100274 return 0;
Jan Engelhardt8bee4ba2010-03-17 16:04:40 +0100275 pr_info("Only works on TCP SYN packets\n");
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100276 return -EINVAL;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800277}
278#endif
279
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800280static struct xt_target tcpmss_tg_reg[] __read_mostly = {
Patrick McHardycdd289a2007-02-07 15:09:46 -0800281 {
Jan Engelhardtee999d82008-10-08 11:35:01 +0200282 .family = NFPROTO_IPV4,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800283 .name = "TCPMSS",
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800284 .checkentry = tcpmss_tg4_check,
285 .target = tcpmss_tg4,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800286 .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 {
Jan Engelhardtee999d82008-10-08 11:35:01 +0200292 .family = NFPROTO_IPV6,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800293 .name = "TCPMSS",
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800294 .checkentry = tcpmss_tg6_check,
295 .target = tcpmss_tg6,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800296 .targetsize = sizeof(struct xt_tcpmss_info),
297 .proto = IPPROTO_TCP,
298 .me = THIS_MODULE,
299 },
300#endif
301};
302
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800303static int __init tcpmss_tg_init(void)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800304{
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800305 return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
Patrick McHardycdd289a2007-02-07 15:09:46 -0800306}
307
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800308static void __exit tcpmss_tg_exit(void)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800309{
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800310 xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
Patrick McHardycdd289a2007-02-07 15:09:46 -0800311}
312
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800313module_init(tcpmss_tg_init);
314module_exit(tcpmss_tg_exit);