• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /asuswrt-rt-n18u-9.0.0.4.380.2695/release/src-rt-6.x.4708/linux/linux-2.6.36/net/netfilter/
1/*
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#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
11#include <linux/module.h>
12#include <linux/skbuff.h>
13#include <linux/ip.h>
14#include <linux/gfp.h>
15#include <linux/ipv6.h>
16#include <linux/tcp.h>
17#include <net/dst.h>
18#include <net/flow.h>
19#include <net/ipv6.h>
20#include <net/route.h>
21#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>");
31MODULE_DESCRIPTION("Xtables: TCP Maximum Segment Size (MSS) adjustment");
32MODULE_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
46tcpmss_mangle_packet(struct sk_buff *skb,
47		     const struct xt_tcpmss_info *info,
48		     unsigned int in_mtu,
49		     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
58	if (!skb_make_writable(skb, skb->len))
59		return -1;
60
61	tcplen = skb->len - tcphoff;
62	tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
63
64	/* Header cannot be larger than the packet */
65	if (tcplen < tcph->doff*4)
66		return -1;
67
68	if (info->mss == XT_TCPMSS_CLAMP_PMTU) {
69		if (dst_mtu(skb_dst(skb)) <= minlen) {
70			if (net_ratelimit())
71				pr_err("unknown or invalid path-MTU (%u)\n",
72				       dst_mtu(skb_dst(skb)));
73			return -1;
74		}
75		if (in_mtu <= minlen) {
76			if (net_ratelimit())
77				pr_err("unknown or invalid path-MTU (%u)\n",
78				       in_mtu);
79			return -1;
80		}
81		newmss = min(dst_mtu(skb_dst(skb)), in_mtu) - minlen;
82	} 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
93			/* 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)
98				return 0;
99
100			opt[i+2] = (newmss & 0xff00) >> 8;
101			opt[i+3] = newmss & 0x00ff;
102
103			inet_proto_csum_replace2(&tcph->check, skb,
104						 htons(oldmss), htons(newmss),
105						 0);
106			return 0;
107		}
108	}
109
110	/* 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
116	/*
117	 * MSS Option not found ?! add it..
118	 */
119	if (skb_tailroom(skb) < TCPOLEN_MSS) {
120		if (pskb_expand_head(skb, 0,
121				     TCPOLEN_MSS - skb_tailroom(skb),
122				     GFP_ATOMIC))
123			return -1;
124		tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
125	}
126
127	skb_put(skb, TCPOLEN_MSS);
128
129	opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
130	memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr));
131
132	inet_proto_csum_replace2(&tcph->check, skb,
133				 htons(tcplen), htons(tcplen + TCPOLEN_MSS), 1);
134	opt[0] = TCPOPT_MSS;
135	opt[1] = TCPOLEN_MSS;
136	opt[2] = (newmss & 0xff00) >> 8;
137	opt[3] = newmss & 0x00ff;
138
139	inet_proto_csum_replace4(&tcph->check, skb, 0, *((__be32 *)opt), 0);
140
141	oldval = ((__be16 *)tcph)[6];
142	tcph->doff += TCPOLEN_MSS/4;
143	inet_proto_csum_replace2(&tcph->check, skb,
144				 oldval, ((__be16 *)tcph)[6], 0);
145	return TCPOLEN_MSS;
146}
147
148static u_int32_t tcpmss_reverse_mtu(const struct sk_buff *skb,
149				    unsigned int family)
150{
151	struct flowi fl = {};
152	const struct nf_afinfo *ai;
153	struct rtable *rt = NULL;
154	u_int32_t mtu     = ~0U;
155
156	if (family == PF_INET)
157		fl.fl4_dst = ip_hdr(skb)->saddr;
158	else
159		fl.fl6_dst = ipv6_hdr(skb)->saddr;
160
161	rcu_read_lock();
162	ai = nf_get_afinfo(family);
163	if (ai != NULL)
164		ai->route((struct dst_entry **)&rt, &fl);
165	rcu_read_unlock();
166
167	if (rt != NULL) {
168		mtu = dst_mtu(&rt->dst);
169		dst_release(&rt->dst);
170	}
171	return mtu;
172}
173
174static unsigned int
175tcpmss_tg4(struct sk_buff *skb, const struct xt_action_param *par)
176{
177	struct iphdr *iph = ip_hdr(skb);
178	__be16 newlen;
179	int ret;
180
181	ret = tcpmss_mangle_packet(skb, par->targinfo,
182				   tcpmss_reverse_mtu(skb, PF_INET),
183				   iph->ihl * 4,
184				   sizeof(*iph) + sizeof(struct tcphdr));
185	if (ret < 0)
186		return NF_DROP;
187	if (ret > 0) {
188		iph = ip_hdr(skb);
189		newlen = htons(ntohs(iph->tot_len) + ret);
190		csum_replace2(&iph->check, iph->tot_len, newlen);
191		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
198tcpmss_tg6(struct sk_buff *skb, const struct xt_action_param *par)
199{
200	struct ipv6hdr *ipv6h = ipv6_hdr(skb);
201	u8 nexthdr;
202	int tcphoff;
203	int ret;
204
205	nexthdr = ipv6h->nexthdr;
206	tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr);
207	if (tcphoff < 0)
208		return NF_DROP;
209	ret = tcpmss_mangle_packet(skb, par->targinfo,
210				   tcpmss_reverse_mtu(skb, PF_INET6),
211				   tcphoff,
212				   sizeof(*ipv6h) + sizeof(struct tcphdr));
213	if (ret < 0)
214		return NF_DROP;
215	if (ret > 0) {
216		ipv6h = ipv6_hdr(skb);
217		ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
218	}
219	return XT_CONTINUE;
220}
221#endif
222
223/* Must specify -p tcp --syn */
224static inline bool find_syn_match(const struct xt_entry_match *m)
225{
226	const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
227
228	if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
229	    tcpinfo->flg_cmp & TCPHDR_SYN &&
230	    !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
231		return true;
232
233	return false;
234}
235
236static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
237{
238	const struct xt_tcpmss_info *info = par->targinfo;
239	const struct ipt_entry *e = par->entryinfo;
240	const struct xt_entry_match *ematch;
241
242	if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
243	    (par->hook_mask & ~((1 << NF_INET_FORWARD) |
244			   (1 << NF_INET_LOCAL_OUT) |
245			   (1 << NF_INET_POST_ROUTING))) != 0) {
246		pr_info("path-MTU clamping only supported in "
247			"FORWARD, OUTPUT and POSTROUTING hooks\n");
248		return -EINVAL;
249	}
250	xt_ematch_foreach(ematch, e)
251		if (find_syn_match(ematch))
252			return 0;
253	pr_info("Only works on TCP SYN packets\n");
254	return -EINVAL;
255}
256
257#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
258static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
259{
260	const struct xt_tcpmss_info *info = par->targinfo;
261	const struct ip6t_entry *e = par->entryinfo;
262	const struct xt_entry_match *ematch;
263
264	if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
265	    (par->hook_mask & ~((1 << NF_INET_FORWARD) |
266			   (1 << NF_INET_LOCAL_OUT) |
267			   (1 << NF_INET_POST_ROUTING))) != 0) {
268		pr_info("path-MTU clamping only supported in "
269			"FORWARD, OUTPUT and POSTROUTING hooks\n");
270		return -EINVAL;
271	}
272	xt_ematch_foreach(ematch, e)
273		if (find_syn_match(ematch))
274			return 0;
275	pr_info("Only works on TCP SYN packets\n");
276	return -EINVAL;
277}
278#endif
279
280static struct xt_target tcpmss_tg_reg[] __read_mostly = {
281	{
282		.family		= NFPROTO_IPV4,
283		.name		= "TCPMSS",
284		.checkentry	= tcpmss_tg4_check,
285		.target		= tcpmss_tg4,
286		.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	{
292		.family		= NFPROTO_IPV6,
293		.name		= "TCPMSS",
294		.checkentry	= tcpmss_tg6_check,
295		.target		= tcpmss_tg6,
296		.targetsize	= sizeof(struct xt_tcpmss_info),
297		.proto		= IPPROTO_TCP,
298		.me		= THIS_MODULE,
299	},
300#endif
301};
302
303static int __init tcpmss_tg_init(void)
304{
305	return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
306}
307
308static void __exit tcpmss_tg_exit(void)
309{
310	xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
311}
312
313module_init(tcpmss_tg_init);
314module_exit(tcpmss_tg_exit);
315