blob: 7011c71646f0266eb75c856bc49fea7b5030bd52 [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,
Phil Oester70d19f82013-06-12 10:44:51 +020048 const struct xt_action_param *par,
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{
Phil Oester70d19f82013-06-12 10:44:51 +020053 const struct xt_tcpmss_info *info = par->targinfo;
Patrick McHardycdd289a2007-02-07 15:09:46 -080054 struct tcphdr *tcph;
55 unsigned int tcplen, i;
56 __be16 oldval;
57 u16 newmss;
58 u8 *opt;
59
Phil Oesterb3969662013-06-12 10:58:20 +020060 /* This is a fragment, no TCP header is available */
61 if (par->fragoff != 0)
62 return XT_CONTINUE;
63
Herbert Xu3db05fe2007-10-15 00:53:15 -070064 if (!skb_make_writable(skb, skb->len))
Patrick McHardycdd289a2007-02-07 15:09:46 -080065 return -1;
66
Herbert Xu3db05fe2007-10-15 00:53:15 -070067 tcplen = skb->len - tcphoff;
68 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
Patrick McHardycdd289a2007-02-07 15:09:46 -080069
Simon Arlott10a19932010-02-02 15:33:38 +010070 /* Header cannot be larger than the packet */
71 if (tcplen < tcph->doff*4)
Patrick McHardycdd289a2007-02-07 15:09:46 -080072 return -1;
Patrick McHardycdd289a2007-02-07 15:09:46 -080073
74 if (info->mss == XT_TCPMSS_CLAMP_PMTU) {
Eric Dumazetadf30902009-06-02 05:19:30 +000075 if (dst_mtu(skb_dst(skb)) <= minlen) {
Joe Perchese87cc472012-05-13 21:56:26 +000076 net_err_ratelimited("unknown or invalid path-MTU (%u)\n",
77 dst_mtu(skb_dst(skb)));
Patrick McHardycdd289a2007-02-07 15:09:46 -080078 return -1;
79 }
Jan Engelhardt37c08382008-01-31 04:06:10 -080080 if (in_mtu <= minlen) {
Joe Perchese87cc472012-05-13 21:56:26 +000081 net_err_ratelimited("unknown or invalid path-MTU (%u)\n",
82 in_mtu);
Jan Engelhardt37c08382008-01-31 04:06:10 -080083 return -1;
84 }
Eric Dumazetadf30902009-06-02 05:19:30 +000085 newmss = min(dst_mtu(skb_dst(skb)), in_mtu) - minlen;
Patrick McHardycdd289a2007-02-07 15:09:46 -080086 } else
87 newmss = info->mss;
88
89 opt = (u_int8_t *)tcph;
90 for (i = sizeof(struct tcphdr); i < tcph->doff*4; i += optlen(opt, i)) {
91 if (opt[i] == TCPOPT_MSS && tcph->doff*4 - i >= TCPOLEN_MSS &&
92 opt[i+1] == TCPOLEN_MSS) {
93 u_int16_t oldmss;
94
95 oldmss = (opt[i+2] << 8) | opt[i+3];
96
Benjamin LaHaise17008062007-12-17 22:27:36 -080097 /* Never increase MSS, even when setting it, as
98 * doing so results in problems for hosts that rely
99 * on MSS being set correctly.
100 */
101 if (oldmss <= newmss)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800102 return 0;
103
104 opt[i+2] = (newmss & 0xff00) >> 8;
Jan Engelhardt7c4e36b2007-07-07 22:19:08 -0700105 opt[i+3] = newmss & 0x00ff;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800106
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100107 inet_proto_csum_replace2(&tcph->check, skb,
108 htons(oldmss), htons(newmss),
109 0);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800110 return 0;
111 }
112 }
113
Simon Arlott10a19932010-02-02 15:33:38 +0100114 /* There is data after the header so the option can't be added
115 without moving it, and doing so may make the SYN packet
116 itself too large. Accept the packet unmodified instead. */
117 if (tcplen > tcph->doff*4)
118 return 0;
119
Patrick McHardycdd289a2007-02-07 15:09:46 -0800120 /*
121 * MSS Option not found ?! add it..
122 */
Herbert Xu3db05fe2007-10-15 00:53:15 -0700123 if (skb_tailroom(skb) < TCPOLEN_MSS) {
124 if (pskb_expand_head(skb, 0,
125 TCPOLEN_MSS - skb_tailroom(skb),
Herbert Xu2ca7b0a2007-10-14 00:39:55 -0700126 GFP_ATOMIC))
Patrick McHardycdd289a2007-02-07 15:09:46 -0800127 return -1;
Herbert Xu3db05fe2007-10-15 00:53:15 -0700128 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800129 }
130
Herbert Xu3db05fe2007-10-15 00:53:15 -0700131 skb_put(skb, TCPOLEN_MSS);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800132
Phil Oester70d19f82013-06-12 10:44:51 +0200133 /*
134 * IPv4: RFC 1122 states "If an MSS option is not received at
135 * connection setup, TCP MUST assume a default send MSS of 536".
136 * IPv6: RFC 2460 states IPv6 has a minimum MTU of 1280 and a minimum
137 * length IPv6 header of 60, ergo the default MSS value is 1220
138 * Since no MSS was provided, we must use the default values
Phil Oester409b5452013-06-04 05:09:27 +0000139 */
Phil Oester70d19f82013-06-12 10:44:51 +0200140 if (par->family == NFPROTO_IPV4)
141 newmss = min(newmss, (u16)536);
142 else
143 newmss = min(newmss, (u16)1220);
Phil Oester409b5452013-06-04 05:09:27 +0000144
Patrick McHardycdd289a2007-02-07 15:09:46 -0800145 opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
146 memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr));
147
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100148 inet_proto_csum_replace2(&tcph->check, skb,
149 htons(tcplen), htons(tcplen + TCPOLEN_MSS), 1);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800150 opt[0] = TCPOPT_MSS;
151 opt[1] = TCPOLEN_MSS;
152 opt[2] = (newmss & 0xff00) >> 8;
Jan Engelhardt7c4e36b2007-07-07 22:19:08 -0700153 opt[3] = newmss & 0x00ff;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800154
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100155 inet_proto_csum_replace4(&tcph->check, skb, 0, *((__be32 *)opt), 0);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800156
157 oldval = ((__be16 *)tcph)[6];
158 tcph->doff += TCPOLEN_MSS/4;
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100159 inet_proto_csum_replace2(&tcph->check, skb,
160 oldval, ((__be16 *)tcph)[6], 0);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800161 return TCPOLEN_MSS;
162}
163
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700164static u_int32_t tcpmss_reverse_mtu(const struct sk_buff *skb,
165 unsigned int family)
Jan Engelhardt37c08382008-01-31 04:06:10 -0800166{
David S. Millera1bbb0e2011-03-12 02:16:48 -0500167 struct flowi fl;
Jan Engelhardt37c08382008-01-31 04:06:10 -0800168 const struct nf_afinfo *ai;
169 struct rtable *rt = NULL;
170 u_int32_t mtu = ~0U;
171
David S. Millera1bbb0e2011-03-12 02:16:48 -0500172 if (family == PF_INET) {
173 struct flowi4 *fl4 = &fl.u.ip4;
174 memset(fl4, 0, sizeof(*fl4));
175 fl4->daddr = ip_hdr(skb)->saddr;
176 } else {
177 struct flowi6 *fl6 = &fl.u.ip6;
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700178
David S. Millera1bbb0e2011-03-12 02:16:48 -0500179 memset(fl6, 0, sizeof(*fl6));
Alexey Dobriyan4e3fd7a2011-11-21 03:39:03 +0000180 fl6->daddr = ipv6_hdr(skb)->saddr;
David S. Millera1bbb0e2011-03-12 02:16:48 -0500181 }
Jan Engelhardt37c08382008-01-31 04:06:10 -0800182 rcu_read_lock();
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700183 ai = nf_get_afinfo(family);
Jan Engelhardt37c08382008-01-31 04:06:10 -0800184 if (ai != NULL)
Florian Westphal0fae2e72011-04-04 17:00:54 +0200185 ai->route(&init_net, (struct dst_entry **)&rt, &fl, false);
Jan Engelhardt37c08382008-01-31 04:06:10 -0800186 rcu_read_unlock();
187
188 if (rt != NULL) {
Changli Gaod8d1f302010-06-10 23:31:35 -0700189 mtu = dst_mtu(&rt->dst);
190 dst_release(&rt->dst);
Jan Engelhardt37c08382008-01-31 04:06:10 -0800191 }
192 return mtu;
193}
194
Patrick McHardycdd289a2007-02-07 15:09:46 -0800195static unsigned int
Jan Engelhardt4b560b42009-07-05 19:43:26 +0200196tcpmss_tg4(struct sk_buff *skb, const struct xt_action_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800197{
Herbert Xu3db05fe2007-10-15 00:53:15 -0700198 struct iphdr *iph = ip_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800199 __be16 newlen;
200 int ret;
201
Phil Oester70d19f82013-06-12 10:44:51 +0200202 ret = tcpmss_mangle_packet(skb, par,
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700203 tcpmss_reverse_mtu(skb, PF_INET),
Jan Engelhardt37c08382008-01-31 04:06:10 -0800204 iph->ihl * 4,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800205 sizeof(*iph) + sizeof(struct tcphdr));
206 if (ret < 0)
207 return NF_DROP;
208 if (ret > 0) {
Herbert Xu3db05fe2007-10-15 00:53:15 -0700209 iph = ip_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800210 newlen = htons(ntohs(iph->tot_len) + ret);
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100211 csum_replace2(&iph->check, iph->tot_len, newlen);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800212 iph->tot_len = newlen;
213 }
214 return XT_CONTINUE;
215}
216
Igor Maravićc0cd1152011-12-12 02:58:24 +0000217#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800218static unsigned int
Jan Engelhardt4b560b42009-07-05 19:43:26 +0200219tcpmss_tg6(struct sk_buff *skb, const struct xt_action_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800220{
Herbert Xu3db05fe2007-10-15 00:53:15 -0700221 struct ipv6hdr *ipv6h = ipv6_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800222 u8 nexthdr;
Jesse Gross75f28112011-11-30 17:05:51 -0800223 __be16 frag_off;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800224 int tcphoff;
225 int ret;
226
227 nexthdr = ipv6h->nexthdr;
Jesse Gross75f28112011-11-30 17:05:51 -0800228 tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr, &frag_off);
Patrick McHardy9dc05642007-11-30 23:58:03 +1100229 if (tcphoff < 0)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800230 return NF_DROP;
Phil Oester70d19f82013-06-12 10:44:51 +0200231 ret = tcpmss_mangle_packet(skb, par,
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700232 tcpmss_reverse_mtu(skb, PF_INET6),
Jan Engelhardt37c08382008-01-31 04:06:10 -0800233 tcphoff,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800234 sizeof(*ipv6h) + sizeof(struct tcphdr));
235 if (ret < 0)
236 return NF_DROP;
237 if (ret > 0) {
Herbert Xu3db05fe2007-10-15 00:53:15 -0700238 ipv6h = ipv6_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800239 ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
240 }
241 return XT_CONTINUE;
242}
243#endif
244
Patrick McHardycdd289a2007-02-07 15:09:46 -0800245/* Must specify -p tcp --syn */
Jan Engelhardte1931b72007-07-07 22:16:26 -0700246static inline bool find_syn_match(const struct xt_entry_match *m)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800247{
248 const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
249
250 if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
Changli Gaoa3433f32010-06-12 14:01:43 +0000251 tcpinfo->flg_cmp & TCPHDR_SYN &&
Patrick McHardycdd289a2007-02-07 15:09:46 -0800252 !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
Jan Engelhardte1931b72007-07-07 22:16:26 -0700253 return true;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800254
Jan Engelhardte1931b72007-07-07 22:16:26 -0700255 return false;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800256}
257
Jan Engelhardt135367b2010-03-19 17:16:42 +0100258static int tcpmss_tg4_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 ipt_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
Igor Maravićc0cd1152011-12-12 02:58:24 +0000279#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
Jan Engelhardt135367b2010-03-19 17:16:42 +0100280static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800281{
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200282 const struct xt_tcpmss_info *info = par->targinfo;
283 const struct ip6t_entry *e = par->entryinfo;
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100284 const struct xt_entry_match *ematch;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800285
286 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200287 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
Patrick McHardy6e23ae22007-11-19 18:53:30 -0800288 (1 << NF_INET_LOCAL_OUT) |
289 (1 << NF_INET_POST_ROUTING))) != 0) {
Jan Engelhardt8bee4ba2010-03-17 16:04:40 +0100290 pr_info("path-MTU clamping only supported in "
291 "FORWARD, OUTPUT and POSTROUTING hooks\n");
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100292 return -EINVAL;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800293 }
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100294 xt_ematch_foreach(ematch, e)
295 if (find_syn_match(ematch))
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100296 return 0;
Jan Engelhardt8bee4ba2010-03-17 16:04:40 +0100297 pr_info("Only works on TCP SYN packets\n");
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100298 return -EINVAL;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800299}
300#endif
301
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800302static struct xt_target tcpmss_tg_reg[] __read_mostly = {
Patrick McHardycdd289a2007-02-07 15:09:46 -0800303 {
Jan Engelhardtee999d82008-10-08 11:35:01 +0200304 .family = NFPROTO_IPV4,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800305 .name = "TCPMSS",
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800306 .checkentry = tcpmss_tg4_check,
307 .target = tcpmss_tg4,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800308 .targetsize = sizeof(struct xt_tcpmss_info),
309 .proto = IPPROTO_TCP,
310 .me = THIS_MODULE,
311 },
Igor Maravićc0cd1152011-12-12 02:58:24 +0000312#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800313 {
Jan Engelhardtee999d82008-10-08 11:35:01 +0200314 .family = NFPROTO_IPV6,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800315 .name = "TCPMSS",
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800316 .checkentry = tcpmss_tg6_check,
317 .target = tcpmss_tg6,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800318 .targetsize = sizeof(struct xt_tcpmss_info),
319 .proto = IPPROTO_TCP,
320 .me = THIS_MODULE,
321 },
322#endif
323};
324
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800325static int __init tcpmss_tg_init(void)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800326{
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800327 return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
Patrick McHardycdd289a2007-02-07 15:09:46 -0800328}
329
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800330static void __exit tcpmss_tg_exit(void)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800331{
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800332 xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
Patrick McHardycdd289a2007-02-07 15:09:46 -0800333}
334
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800335module_init(tcpmss_tg_init);
336module_exit(tcpmss_tg_exit);