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 
29 MODULE_LICENSE("GPL");
30 MODULE_AUTHOR("Marc Boucher <marc@mbsi.ca>");
31 MODULE_DESCRIPTION("Xtables: TCP Maximum Segment Size (MSS) adjustment");
32 MODULE_ALIAS("ipt_TCPMSS");
33 MODULE_ALIAS("ip6t_TCPMSS");
34 
35 static inline unsigned int
optlen(const u_int8_t * opt,unsigned int offset)36 optlen(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 
45 static int
tcpmss_mangle_packet(struct sk_buff * skb,const struct xt_tcpmss_info * info,unsigned int in_mtu,unsigned int tcphoff,unsigned int minlen)46 tcpmss_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 
tcpmss_reverse_mtu(const struct sk_buff * skb,unsigned int family)148 static 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 		struct flowi4 *fl4 = &fl.u.ip4;
158 		memset(fl4, 0, sizeof(*fl4));
159 		fl4->daddr = ip_hdr(skb)->saddr;
160 	} else {
161 		struct flowi6 *fl6 = &fl.u.ip6;
162 
163 		memset(fl6, 0, sizeof(*fl6));
164 		fl6->daddr = ipv6_hdr(skb)->saddr;
165 	}
166 	rcu_read_lock();
167 	ai = nf_get_afinfo(family);
168 	if (ai != NULL)
169 		ai->route(&init_net, (struct dst_entry **)&rt, &fl, false);
170 	rcu_read_unlock();
171 
172 	if (rt != NULL) {
173 		mtu = dst_mtu(&rt->dst);
174 		dst_release(&rt->dst);
175 	}
176 	return mtu;
177 }
178 
179 static unsigned int
tcpmss_tg4(struct sk_buff * skb,const struct xt_action_param * par)180 tcpmss_tg4(struct sk_buff *skb, const struct xt_action_param *par)
181 {
182 	struct iphdr *iph = ip_hdr(skb);
183 	__be16 newlen;
184 	int ret;
185 
186 	ret = tcpmss_mangle_packet(skb, par->targinfo,
187 				   tcpmss_reverse_mtu(skb, PF_INET),
188 				   iph->ihl * 4,
189 				   sizeof(*iph) + sizeof(struct tcphdr));
190 	if (ret < 0)
191 		return NF_DROP;
192 	if (ret > 0) {
193 		iph = ip_hdr(skb);
194 		newlen = htons(ntohs(iph->tot_len) + ret);
195 		csum_replace2(&iph->check, iph->tot_len, newlen);
196 		iph->tot_len = newlen;
197 	}
198 	return XT_CONTINUE;
199 }
200 
201 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
202 static unsigned int
tcpmss_tg6(struct sk_buff * skb,const struct xt_action_param * par)203 tcpmss_tg6(struct sk_buff *skb, const struct xt_action_param *par)
204 {
205 	struct ipv6hdr *ipv6h = ipv6_hdr(skb);
206 	u8 nexthdr;
207 	__be16 frag_off;
208 	int tcphoff;
209 	int ret;
210 
211 	nexthdr = ipv6h->nexthdr;
212 	tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr, &frag_off);
213 	if (tcphoff < 0)
214 		return NF_DROP;
215 	ret = tcpmss_mangle_packet(skb, par->targinfo,
216 				   tcpmss_reverse_mtu(skb, PF_INET6),
217 				   tcphoff,
218 				   sizeof(*ipv6h) + sizeof(struct tcphdr));
219 	if (ret < 0)
220 		return NF_DROP;
221 	if (ret > 0) {
222 		ipv6h = ipv6_hdr(skb);
223 		ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
224 	}
225 	return XT_CONTINUE;
226 }
227 #endif
228 
229 /* Must specify -p tcp --syn */
find_syn_match(const struct xt_entry_match * m)230 static inline bool find_syn_match(const struct xt_entry_match *m)
231 {
232 	const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
233 
234 	if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
235 	    tcpinfo->flg_cmp & TCPHDR_SYN &&
236 	    !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
237 		return true;
238 
239 	return false;
240 }
241 
tcpmss_tg4_check(const struct xt_tgchk_param * par)242 static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
243 {
244 	const struct xt_tcpmss_info *info = par->targinfo;
245 	const struct ipt_entry *e = par->entryinfo;
246 	const struct xt_entry_match *ematch;
247 
248 	if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
249 	    (par->hook_mask & ~((1 << NF_INET_FORWARD) |
250 			   (1 << NF_INET_LOCAL_OUT) |
251 			   (1 << NF_INET_POST_ROUTING))) != 0) {
252 		pr_info("path-MTU clamping only supported in "
253 			"FORWARD, OUTPUT and POSTROUTING hooks\n");
254 		return -EINVAL;
255 	}
256 	xt_ematch_foreach(ematch, e)
257 		if (find_syn_match(ematch))
258 			return 0;
259 	pr_info("Only works on TCP SYN packets\n");
260 	return -EINVAL;
261 }
262 
263 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
tcpmss_tg6_check(const struct xt_tgchk_param * par)264 static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
265 {
266 	const struct xt_tcpmss_info *info = par->targinfo;
267 	const struct ip6t_entry *e = par->entryinfo;
268 	const struct xt_entry_match *ematch;
269 
270 	if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
271 	    (par->hook_mask & ~((1 << NF_INET_FORWARD) |
272 			   (1 << NF_INET_LOCAL_OUT) |
273 			   (1 << NF_INET_POST_ROUTING))) != 0) {
274 		pr_info("path-MTU clamping only supported in "
275 			"FORWARD, OUTPUT and POSTROUTING hooks\n");
276 		return -EINVAL;
277 	}
278 	xt_ematch_foreach(ematch, e)
279 		if (find_syn_match(ematch))
280 			return 0;
281 	pr_info("Only works on TCP SYN packets\n");
282 	return -EINVAL;
283 }
284 #endif
285 
286 static struct xt_target tcpmss_tg_reg[] __read_mostly = {
287 	{
288 		.family		= NFPROTO_IPV4,
289 		.name		= "TCPMSS",
290 		.checkentry	= tcpmss_tg4_check,
291 		.target		= tcpmss_tg4,
292 		.targetsize	= sizeof(struct xt_tcpmss_info),
293 		.proto		= IPPROTO_TCP,
294 		.me		= THIS_MODULE,
295 	},
296 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
297 	{
298 		.family		= NFPROTO_IPV6,
299 		.name		= "TCPMSS",
300 		.checkentry	= tcpmss_tg6_check,
301 		.target		= tcpmss_tg6,
302 		.targetsize	= sizeof(struct xt_tcpmss_info),
303 		.proto		= IPPROTO_TCP,
304 		.me		= THIS_MODULE,
305 	},
306 #endif
307 };
308 
tcpmss_tg_init(void)309 static int __init tcpmss_tg_init(void)
310 {
311 	return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
312 }
313 
tcpmss_tg_exit(void)314 static void __exit tcpmss_tg_exit(void)
315 {
316 	xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
317 }
318 
319 module_init(tcpmss_tg_init);
320 module_exit(tcpmss_tg_exit);
321