blob: a75240f0d42b8da245035a8f8ee59b51895c445e [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>
Patrick McHardyf229f6c2013-04-06 15:24:29 +02005 * Copyright (C) 2007 Patrick McHardy <kaber@trash.net>
Patrick McHardycdd289a2007-02-07 15:09:46 -08006 *
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 */
Jan Engelhardt8bee4ba2010-03-17 16:04:40 +010011#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
Patrick McHardycdd289a2007-02-07 15:09:46 -080012#include <linux/module.h>
13#include <linux/skbuff.h>
14#include <linux/ip.h>
Tejun Heo5a0e3ad2010-03-24 17:04:11 +090015#include <linux/gfp.h>
Patrick McHardycdd289a2007-02-07 15:09:46 -080016#include <linux/ipv6.h>
17#include <linux/tcp.h>
Jan Engelhardt37c08382008-01-31 04:06:10 -080018#include <net/dst.h>
19#include <net/flow.h>
Patrick McHardycdd289a2007-02-07 15:09:46 -080020#include <net/ipv6.h>
Jan Engelhardt37c08382008-01-31 04:06:10 -080021#include <net/route.h>
Patrick McHardycdd289a2007-02-07 15:09:46 -080022#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>");
Jan Engelhardt2ae15b62008-01-14 23:42:28 -080032MODULE_DESCRIPTION("Xtables: TCP Maximum Segment Size (MSS) adjustment");
Patrick McHardycdd289a2007-02-07 15:09:46 -080033MODULE_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
Herbert Xu3db05fe2007-10-15 00:53:15 -070047tcpmss_mangle_packet(struct sk_buff *skb,
Patrick McHardycdd289a2007-02-07 15:09:46 -080048 const struct xt_tcpmss_info *info,
Jan Engelhardt37c08382008-01-31 04:06:10 -080049 unsigned int in_mtu,
Patrick McHardycdd289a2007-02-07 15:09:46 -080050 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
Herbert Xu3db05fe2007-10-15 00:53:15 -070059 if (!skb_make_writable(skb, skb->len))
Patrick McHardycdd289a2007-02-07 15:09:46 -080060 return -1;
61
Herbert Xu3db05fe2007-10-15 00:53:15 -070062 tcplen = skb->len - tcphoff;
63 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
Patrick McHardycdd289a2007-02-07 15:09:46 -080064
Simon Arlott10a19932010-02-02 15:33:38 +010065 /* Header cannot be larger than the packet */
66 if (tcplen < tcph->doff*4)
Patrick McHardycdd289a2007-02-07 15:09:46 -080067 return -1;
Patrick McHardycdd289a2007-02-07 15:09:46 -080068
69 if (info->mss == XT_TCPMSS_CLAMP_PMTU) {
Eric Dumazetadf30902009-06-02 05:19:30 +000070 if (dst_mtu(skb_dst(skb)) <= minlen) {
Joe Perchese87cc472012-05-13 21:56:26 +000071 net_err_ratelimited("unknown or invalid path-MTU (%u)\n",
72 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) {
Joe Perchese87cc472012-05-13 21:56:26 +000076 net_err_ratelimited("unknown or invalid path-MTU (%u)\n",
77 in_mtu);
Jan Engelhardt37c08382008-01-31 04:06:10 -080078 return -1;
79 }
Eric Dumazetadf30902009-06-02 05:19:30 +000080 newmss = min(dst_mtu(skb_dst(skb)), in_mtu) - minlen;
Patrick McHardycdd289a2007-02-07 15:09:46 -080081 } 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
Benjamin LaHaise17008062007-12-17 22:27:36 -080092 /* 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)
Patrick McHardycdd289a2007-02-07 15:09:46 -080097 return 0;
98
99 opt[i+2] = (newmss & 0xff00) >> 8;
Jan Engelhardt7c4e36b2007-07-07 22:19:08 -0700100 opt[i+3] = newmss & 0x00ff;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800101
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100102 inet_proto_csum_replace2(&tcph->check, skb,
103 htons(oldmss), htons(newmss),
104 0);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800105 return 0;
106 }
107 }
108
Simon Arlott10a19932010-02-02 15:33:38 +0100109 /* 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
Patrick McHardycdd289a2007-02-07 15:09:46 -0800115 /*
116 * MSS Option not found ?! add it..
117 */
Herbert Xu3db05fe2007-10-15 00:53:15 -0700118 if (skb_tailroom(skb) < TCPOLEN_MSS) {
119 if (pskb_expand_head(skb, 0,
120 TCPOLEN_MSS - skb_tailroom(skb),
Herbert Xu2ca7b0a2007-10-14 00:39:55 -0700121 GFP_ATOMIC))
Patrick McHardycdd289a2007-02-07 15:09:46 -0800122 return -1;
Herbert Xu3db05fe2007-10-15 00:53:15 -0700123 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800124 }
125
Herbert Xu3db05fe2007-10-15 00:53:15 -0700126 skb_put(skb, TCPOLEN_MSS);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800127
128 opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
129 memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr));
130
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100131 inet_proto_csum_replace2(&tcph->check, skb,
132 htons(tcplen), htons(tcplen + TCPOLEN_MSS), 1);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800133 opt[0] = TCPOPT_MSS;
134 opt[1] = TCPOLEN_MSS;
135 opt[2] = (newmss & 0xff00) >> 8;
Jan Engelhardt7c4e36b2007-07-07 22:19:08 -0700136 opt[3] = newmss & 0x00ff;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800137
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100138 inet_proto_csum_replace4(&tcph->check, skb, 0, *((__be32 *)opt), 0);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800139
140 oldval = ((__be16 *)tcph)[6];
141 tcph->doff += TCPOLEN_MSS/4;
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100142 inet_proto_csum_replace2(&tcph->check, skb,
143 oldval, ((__be16 *)tcph)[6], 0);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800144 return TCPOLEN_MSS;
145}
146
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700147static u_int32_t tcpmss_reverse_mtu(const struct sk_buff *skb,
148 unsigned int family)
Jan Engelhardt37c08382008-01-31 04:06:10 -0800149{
David S. Millera1bbb0e2011-03-12 02:16:48 -0500150 struct flowi fl;
Jan Engelhardt37c08382008-01-31 04:06:10 -0800151 const struct nf_afinfo *ai;
152 struct rtable *rt = NULL;
153 u_int32_t mtu = ~0U;
154
David S. Millera1bbb0e2011-03-12 02:16:48 -0500155 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;
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700161
David S. Millera1bbb0e2011-03-12 02:16:48 -0500162 memset(fl6, 0, sizeof(*fl6));
Alexey Dobriyan4e3fd7a2011-11-21 03:39:03 +0000163 fl6->daddr = ipv6_hdr(skb)->saddr;
David S. Millera1bbb0e2011-03-12 02:16:48 -0500164 }
Jan Engelhardt37c08382008-01-31 04:06:10 -0800165 rcu_read_lock();
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700166 ai = nf_get_afinfo(family);
Jan Engelhardt37c08382008-01-31 04:06:10 -0800167 if (ai != NULL)
Florian Westphal0fae2e72011-04-04 17:00:54 +0200168 ai->route(&init_net, (struct dst_entry **)&rt, &fl, false);
Jan Engelhardt37c08382008-01-31 04:06:10 -0800169 rcu_read_unlock();
170
171 if (rt != NULL) {
Changli Gaod8d1f302010-06-10 23:31:35 -0700172 mtu = dst_mtu(&rt->dst);
173 dst_release(&rt->dst);
Jan Engelhardt37c08382008-01-31 04:06:10 -0800174 }
175 return mtu;
176}
177
Patrick McHardycdd289a2007-02-07 15:09:46 -0800178static unsigned int
Jan Engelhardt4b560b42009-07-05 19:43:26 +0200179tcpmss_tg4(struct sk_buff *skb, const struct xt_action_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800180{
Herbert Xu3db05fe2007-10-15 00:53:15 -0700181 struct iphdr *iph = ip_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800182 __be16 newlen;
183 int ret;
184
Jan Engelhardt7eb35582008-10-08 11:35:19 +0200185 ret = tcpmss_mangle_packet(skb, par->targinfo,
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700186 tcpmss_reverse_mtu(skb, PF_INET),
Jan Engelhardt37c08382008-01-31 04:06:10 -0800187 iph->ihl * 4,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800188 sizeof(*iph) + sizeof(struct tcphdr));
189 if (ret < 0)
190 return NF_DROP;
191 if (ret > 0) {
Herbert Xu3db05fe2007-10-15 00:53:15 -0700192 iph = ip_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800193 newlen = htons(ntohs(iph->tot_len) + ret);
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100194 csum_replace2(&iph->check, iph->tot_len, newlen);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800195 iph->tot_len = newlen;
196 }
197 return XT_CONTINUE;
198}
199
Igor Maravićc0cd1152011-12-12 02:58:24 +0000200#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800201static unsigned int
Jan Engelhardt4b560b42009-07-05 19:43:26 +0200202tcpmss_tg6(struct sk_buff *skb, const struct xt_action_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800203{
Herbert Xu3db05fe2007-10-15 00:53:15 -0700204 struct ipv6hdr *ipv6h = ipv6_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800205 u8 nexthdr;
Jesse Gross75f28112011-11-30 17:05:51 -0800206 __be16 frag_off;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800207 int tcphoff;
208 int ret;
209
210 nexthdr = ipv6h->nexthdr;
Jesse Gross75f28112011-11-30 17:05:51 -0800211 tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr, &frag_off);
Patrick McHardy9dc05642007-11-30 23:58:03 +1100212 if (tcphoff < 0)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800213 return NF_DROP;
Jan Engelhardt7eb35582008-10-08 11:35:19 +0200214 ret = tcpmss_mangle_packet(skb, par->targinfo,
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700215 tcpmss_reverse_mtu(skb, PF_INET6),
Jan Engelhardt37c08382008-01-31 04:06:10 -0800216 tcphoff,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800217 sizeof(*ipv6h) + sizeof(struct tcphdr));
218 if (ret < 0)
219 return NF_DROP;
220 if (ret > 0) {
Herbert Xu3db05fe2007-10-15 00:53:15 -0700221 ipv6h = ipv6_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800222 ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
223 }
224 return XT_CONTINUE;
225}
226#endif
227
Patrick McHardycdd289a2007-02-07 15:09:46 -0800228/* Must specify -p tcp --syn */
Jan Engelhardte1931b72007-07-07 22:16:26 -0700229static inline bool find_syn_match(const struct xt_entry_match *m)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800230{
231 const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
232
233 if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
Changli Gaoa3433f32010-06-12 14:01:43 +0000234 tcpinfo->flg_cmp & TCPHDR_SYN &&
Patrick McHardycdd289a2007-02-07 15:09:46 -0800235 !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
Jan Engelhardte1931b72007-07-07 22:16:26 -0700236 return true;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800237
Jan Engelhardte1931b72007-07-07 22:16:26 -0700238 return false;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800239}
240
Jan Engelhardt135367b2010-03-19 17:16:42 +0100241static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800242{
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200243 const struct xt_tcpmss_info *info = par->targinfo;
244 const struct ipt_entry *e = par->entryinfo;
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100245 const struct xt_entry_match *ematch;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800246
247 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200248 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
Patrick McHardy6e23ae22007-11-19 18:53:30 -0800249 (1 << NF_INET_LOCAL_OUT) |
250 (1 << NF_INET_POST_ROUTING))) != 0) {
Jan Engelhardt8bee4ba2010-03-17 16:04:40 +0100251 pr_info("path-MTU clamping only supported in "
252 "FORWARD, OUTPUT and POSTROUTING hooks\n");
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100253 return -EINVAL;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800254 }
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100255 xt_ematch_foreach(ematch, e)
256 if (find_syn_match(ematch))
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100257 return 0;
Jan Engelhardt8bee4ba2010-03-17 16:04:40 +0100258 pr_info("Only works on TCP SYN packets\n");
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100259 return -EINVAL;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800260}
261
Igor Maravićc0cd1152011-12-12 02:58:24 +0000262#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
Jan Engelhardt135367b2010-03-19 17:16:42 +0100263static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800264{
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200265 const struct xt_tcpmss_info *info = par->targinfo;
266 const struct ip6t_entry *e = par->entryinfo;
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100267 const struct xt_entry_match *ematch;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800268
269 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200270 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
Patrick McHardy6e23ae22007-11-19 18:53:30 -0800271 (1 << NF_INET_LOCAL_OUT) |
272 (1 << NF_INET_POST_ROUTING))) != 0) {
Jan Engelhardt8bee4ba2010-03-17 16:04:40 +0100273 pr_info("path-MTU clamping only supported in "
274 "FORWARD, OUTPUT and POSTROUTING hooks\n");
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100275 return -EINVAL;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800276 }
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100277 xt_ematch_foreach(ematch, e)
278 if (find_syn_match(ematch))
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100279 return 0;
Jan Engelhardt8bee4ba2010-03-17 16:04:40 +0100280 pr_info("Only works on TCP SYN packets\n");
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100281 return -EINVAL;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800282}
283#endif
284
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800285static struct xt_target tcpmss_tg_reg[] __read_mostly = {
Patrick McHardycdd289a2007-02-07 15:09:46 -0800286 {
Jan Engelhardtee999d82008-10-08 11:35:01 +0200287 .family = NFPROTO_IPV4,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800288 .name = "TCPMSS",
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800289 .checkentry = tcpmss_tg4_check,
290 .target = tcpmss_tg4,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800291 .targetsize = sizeof(struct xt_tcpmss_info),
292 .proto = IPPROTO_TCP,
293 .me = THIS_MODULE,
294 },
Igor Maravićc0cd1152011-12-12 02:58:24 +0000295#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800296 {
Jan Engelhardtee999d82008-10-08 11:35:01 +0200297 .family = NFPROTO_IPV6,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800298 .name = "TCPMSS",
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800299 .checkentry = tcpmss_tg6_check,
300 .target = tcpmss_tg6,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800301 .targetsize = sizeof(struct xt_tcpmss_info),
302 .proto = IPPROTO_TCP,
303 .me = THIS_MODULE,
304 },
305#endif
306};
307
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800308static int __init tcpmss_tg_init(void)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800309{
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800310 return xt_register_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 -0800313static void __exit tcpmss_tg_exit(void)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800314{
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800315 xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
Patrick McHardycdd289a2007-02-07 15:09:46 -0800316}
317
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800318module_init(tcpmss_tg_init);
319module_exit(tcpmss_tg_exit);