blob: 385677b963d54142e2e773159ab6f22c5a84abbe [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>
14#include <linux/ipv6.h>
15#include <linux/tcp.h>
Jan Engelhardt37c08382008-01-31 04:06:10 -080016#include <net/dst.h>
17#include <net/flow.h>
Patrick McHardycdd289a2007-02-07 15:09:46 -080018#include <net/ipv6.h>
Jan Engelhardt37c08382008-01-31 04:06:10 -080019#include <net/route.h>
Patrick McHardycdd289a2007-02-07 15:09:46 -080020#include <net/tcp.h>
21
22#include <linux/netfilter_ipv4/ip_tables.h>
23#include <linux/netfilter_ipv6/ip6_tables.h>
24#include <linux/netfilter/x_tables.h>
25#include <linux/netfilter/xt_tcpudp.h>
26#include <linux/netfilter/xt_TCPMSS.h>
27
28MODULE_LICENSE("GPL");
29MODULE_AUTHOR("Marc Boucher <marc@mbsi.ca>");
Jan Engelhardt2ae15b62008-01-14 23:42:28 -080030MODULE_DESCRIPTION("Xtables: TCP Maximum Segment Size (MSS) adjustment");
Patrick McHardycdd289a2007-02-07 15:09:46 -080031MODULE_ALIAS("ipt_TCPMSS");
32MODULE_ALIAS("ip6t_TCPMSS");
33
34static inline unsigned int
35optlen(const u_int8_t *opt, unsigned int offset)
36{
37 /* Beware zero-length options: make finite progress */
38 if (opt[offset] <= TCPOPT_NOP || opt[offset+1] == 0)
39 return 1;
40 else
41 return opt[offset+1];
42}
43
44static int
Herbert Xu3db05fe2007-10-15 00:53:15 -070045tcpmss_mangle_packet(struct sk_buff *skb,
Patrick McHardycdd289a2007-02-07 15:09:46 -080046 const struct xt_tcpmss_info *info,
Jan Engelhardt37c08382008-01-31 04:06:10 -080047 unsigned int in_mtu,
Patrick McHardycdd289a2007-02-07 15:09:46 -080048 unsigned int tcphoff,
49 unsigned int minlen)
50{
51 struct tcphdr *tcph;
52 unsigned int tcplen, i;
53 __be16 oldval;
54 u16 newmss;
55 u8 *opt;
56
Herbert Xu3db05fe2007-10-15 00:53:15 -070057 if (!skb_make_writable(skb, skb->len))
Patrick McHardycdd289a2007-02-07 15:09:46 -080058 return -1;
59
Herbert Xu3db05fe2007-10-15 00:53:15 -070060 tcplen = skb->len - tcphoff;
61 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
Patrick McHardycdd289a2007-02-07 15:09:46 -080062
Simon Arlott10a19932010-02-02 15:33:38 +010063 /* Header cannot be larger than the packet */
64 if (tcplen < tcph->doff*4)
Patrick McHardycdd289a2007-02-07 15:09:46 -080065 return -1;
Patrick McHardycdd289a2007-02-07 15:09:46 -080066
67 if (info->mss == XT_TCPMSS_CLAMP_PMTU) {
Eric Dumazetadf30902009-06-02 05:19:30 +000068 if (dst_mtu(skb_dst(skb)) <= minlen) {
Patrick McHardycdd289a2007-02-07 15:09:46 -080069 if (net_ratelimit())
Jan Engelhardtff67e4e2010-03-19 21:08:16 +010070 pr_err("unknown or invalid path-MTU (%u)\n",
Eric Dumazetadf30902009-06-02 05:19:30 +000071 dst_mtu(skb_dst(skb)));
Patrick McHardycdd289a2007-02-07 15:09:46 -080072 return -1;
73 }
Jan Engelhardt37c08382008-01-31 04:06:10 -080074 if (in_mtu <= minlen) {
75 if (net_ratelimit())
Jan Engelhardtff67e4e2010-03-19 21:08:16 +010076 pr_err("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{
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700150 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
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700155 if (family == PF_INET)
156 fl.fl4_dst = ip_hdr(skb)->saddr;
157 else
158 fl.fl6_dst = ipv6_hdr(skb)->saddr;
159
Jan Engelhardt37c08382008-01-31 04:06:10 -0800160 rcu_read_lock();
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700161 ai = nf_get_afinfo(family);
Jan Engelhardt37c08382008-01-31 04:06:10 -0800162 if (ai != NULL)
163 ai->route((struct dst_entry **)&rt, &fl);
164 rcu_read_unlock();
165
166 if (rt != NULL) {
167 mtu = dst_mtu(&rt->u.dst);
168 dst_release(&rt->u.dst);
169 }
170 return mtu;
171}
172
Patrick McHardycdd289a2007-02-07 15:09:46 -0800173static unsigned int
Jan Engelhardt7eb35582008-10-08 11:35:19 +0200174tcpmss_tg4(struct sk_buff *skb, const struct xt_target_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800175{
Herbert Xu3db05fe2007-10-15 00:53:15 -0700176 struct iphdr *iph = ip_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800177 __be16 newlen;
178 int ret;
179
Jan Engelhardt7eb35582008-10-08 11:35:19 +0200180 ret = tcpmss_mangle_packet(skb, par->targinfo,
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700181 tcpmss_reverse_mtu(skb, PF_INET),
Jan Engelhardt37c08382008-01-31 04:06:10 -0800182 iph->ihl * 4,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800183 sizeof(*iph) + sizeof(struct tcphdr));
184 if (ret < 0)
185 return NF_DROP;
186 if (ret > 0) {
Herbert Xu3db05fe2007-10-15 00:53:15 -0700187 iph = ip_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800188 newlen = htons(ntohs(iph->tot_len) + ret);
Patrick McHardybe0ea7d2007-11-30 01:17:11 +1100189 csum_replace2(&iph->check, iph->tot_len, newlen);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800190 iph->tot_len = newlen;
191 }
192 return XT_CONTINUE;
193}
194
195#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
196static unsigned int
Jan Engelhardt7eb35582008-10-08 11:35:19 +0200197tcpmss_tg6(struct sk_buff *skb, const struct xt_target_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800198{
Herbert Xu3db05fe2007-10-15 00:53:15 -0700199 struct ipv6hdr *ipv6h = ipv6_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800200 u8 nexthdr;
201 int tcphoff;
202 int ret;
203
204 nexthdr = ipv6h->nexthdr;
Herbert Xu3db05fe2007-10-15 00:53:15 -0700205 tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr);
Patrick McHardy9dc05642007-11-30 23:58:03 +1100206 if (tcphoff < 0)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800207 return NF_DROP;
Jan Engelhardt7eb35582008-10-08 11:35:19 +0200208 ret = tcpmss_mangle_packet(skb, par->targinfo,
Jan Engelhardtdb1a75b2008-07-21 10:02:59 -0700209 tcpmss_reverse_mtu(skb, PF_INET6),
Jan Engelhardt37c08382008-01-31 04:06:10 -0800210 tcphoff,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800211 sizeof(*ipv6h) + sizeof(struct tcphdr));
212 if (ret < 0)
213 return NF_DROP;
214 if (ret > 0) {
Herbert Xu3db05fe2007-10-15 00:53:15 -0700215 ipv6h = ipv6_hdr(skb);
Patrick McHardycdd289a2007-02-07 15:09:46 -0800216 ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
217 }
218 return XT_CONTINUE;
219}
220#endif
221
222#define TH_SYN 0x02
223
224/* Must specify -p tcp --syn */
Jan Engelhardte1931b72007-07-07 22:16:26 -0700225static inline bool find_syn_match(const struct xt_entry_match *m)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800226{
227 const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
228
229 if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
230 tcpinfo->flg_cmp & TH_SYN &&
231 !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
Jan Engelhardte1931b72007-07-07 22:16:26 -0700232 return true;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800233
Jan Engelhardte1931b72007-07-07 22:16:26 -0700234 return false;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800235}
236
Jan Engelhardt135367b2010-03-19 17:16:42 +0100237static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800238{
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200239 const struct xt_tcpmss_info *info = par->targinfo;
240 const struct ipt_entry *e = par->entryinfo;
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100241 const struct xt_entry_match *ematch;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800242
243 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200244 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
Patrick McHardy6e23ae22007-11-19 18:53:30 -0800245 (1 << NF_INET_LOCAL_OUT) |
246 (1 << NF_INET_POST_ROUTING))) != 0) {
Jan Engelhardt8bee4ba2010-03-17 16:04:40 +0100247 pr_info("path-MTU clamping only supported in "
248 "FORWARD, OUTPUT and POSTROUTING hooks\n");
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100249 return -EINVAL;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800250 }
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100251 xt_ematch_foreach(ematch, e)
252 if (find_syn_match(ematch))
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100253 return 0;
Jan Engelhardt8bee4ba2010-03-17 16:04:40 +0100254 pr_info("Only works on TCP SYN packets\n");
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100255 return -EINVAL;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800256}
257
258#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
Jan Engelhardt135367b2010-03-19 17:16:42 +0100259static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800260{
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200261 const struct xt_tcpmss_info *info = par->targinfo;
262 const struct ip6t_entry *e = par->entryinfo;
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100263 const struct xt_entry_match *ematch;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800264
265 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
Jan Engelhardtaf5d6dc2008-10-08 11:35:19 +0200266 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
Patrick McHardy6e23ae22007-11-19 18:53:30 -0800267 (1 << NF_INET_LOCAL_OUT) |
268 (1 << NF_INET_POST_ROUTING))) != 0) {
Jan Engelhardt8bee4ba2010-03-17 16:04:40 +0100269 pr_info("path-MTU clamping only supported in "
270 "FORWARD, OUTPUT and POSTROUTING hooks\n");
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100271 return -EINVAL;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800272 }
Jan Engelhardtdcea9922010-02-24 18:34:48 +0100273 xt_ematch_foreach(ematch, e)
274 if (find_syn_match(ematch))
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100275 return 0;
Jan Engelhardt8bee4ba2010-03-17 16:04:40 +0100276 pr_info("Only works on TCP SYN packets\n");
Jan Engelhardtd6b00a52010-03-25 16:34:45 +0100277 return -EINVAL;
Patrick McHardycdd289a2007-02-07 15:09:46 -0800278}
279#endif
280
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800281static struct xt_target tcpmss_tg_reg[] __read_mostly = {
Patrick McHardycdd289a2007-02-07 15:09:46 -0800282 {
Jan Engelhardtee999d82008-10-08 11:35:01 +0200283 .family = NFPROTO_IPV4,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800284 .name = "TCPMSS",
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800285 .checkentry = tcpmss_tg4_check,
286 .target = tcpmss_tg4,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800287 .targetsize = sizeof(struct xt_tcpmss_info),
288 .proto = IPPROTO_TCP,
289 .me = THIS_MODULE,
290 },
291#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
292 {
Jan Engelhardtee999d82008-10-08 11:35:01 +0200293 .family = NFPROTO_IPV6,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800294 .name = "TCPMSS",
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800295 .checkentry = tcpmss_tg6_check,
296 .target = tcpmss_tg6,
Patrick McHardycdd289a2007-02-07 15:09:46 -0800297 .targetsize = sizeof(struct xt_tcpmss_info),
298 .proto = IPPROTO_TCP,
299 .me = THIS_MODULE,
300 },
301#endif
302};
303
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800304static int __init tcpmss_tg_init(void)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800305{
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800306 return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
Patrick McHardycdd289a2007-02-07 15:09:46 -0800307}
308
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800309static void __exit tcpmss_tg_exit(void)
Patrick McHardycdd289a2007-02-07 15:09:46 -0800310{
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800311 xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
Patrick McHardycdd289a2007-02-07 15:09:46 -0800312}
313
Jan Engelhardtd3c5ee62007-12-04 23:24:03 -0800314module_init(tcpmss_tg_init);
315module_exit(tcpmss_tg_exit);