xref: /openbmc/linux/net/netfilter/xt_TCPMSS.c (revision c4c11dd1)
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  * Copyright (C) 2007 Patrick McHardy <kaber@trash.net>
6  *
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  */
11 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
12 #include <linux/module.h>
13 #include <linux/skbuff.h>
14 #include <linux/ip.h>
15 #include <linux/gfp.h>
16 #include <linux/ipv6.h>
17 #include <linux/tcp.h>
18 #include <net/dst.h>
19 #include <net/flow.h>
20 #include <net/ipv6.h>
21 #include <net/route.h>
22 #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 
30 MODULE_LICENSE("GPL");
31 MODULE_AUTHOR("Marc Boucher <marc@mbsi.ca>");
32 MODULE_DESCRIPTION("Xtables: TCP Maximum Segment Size (MSS) adjustment");
33 MODULE_ALIAS("ipt_TCPMSS");
34 MODULE_ALIAS("ip6t_TCPMSS");
35 
36 static inline unsigned int
37 optlen(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 
46 static int
47 tcpmss_mangle_packet(struct sk_buff *skb,
48 		     const struct xt_action_param *par,
49 		     unsigned int in_mtu,
50 		     unsigned int tcphoff,
51 		     unsigned int minlen)
52 {
53 	const struct xt_tcpmss_info *info = par->targinfo;
54 	struct tcphdr *tcph;
55 	unsigned int tcplen, i;
56 	__be16 oldval;
57 	u16 newmss;
58 	u8 *opt;
59 
60 	/* This is a fragment, no TCP header is available */
61 	if (par->fragoff != 0)
62 		return XT_CONTINUE;
63 
64 	if (!skb_make_writable(skb, skb->len))
65 		return -1;
66 
67 	tcplen = skb->len - tcphoff;
68 	tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
69 
70 	/* Header cannot be larger than the packet */
71 	if (tcplen < tcph->doff*4)
72 		return -1;
73 
74 	if (info->mss == XT_TCPMSS_CLAMP_PMTU) {
75 		if (dst_mtu(skb_dst(skb)) <= minlen) {
76 			net_err_ratelimited("unknown or invalid path-MTU (%u)\n",
77 					    dst_mtu(skb_dst(skb)));
78 			return -1;
79 		}
80 		if (in_mtu <= minlen) {
81 			net_err_ratelimited("unknown or invalid path-MTU (%u)\n",
82 					    in_mtu);
83 			return -1;
84 		}
85 		newmss = min(dst_mtu(skb_dst(skb)), in_mtu) - minlen;
86 	} 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 
97 			/* 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)
102 				return 0;
103 
104 			opt[i+2] = (newmss & 0xff00) >> 8;
105 			opt[i+3] = newmss & 0x00ff;
106 
107 			inet_proto_csum_replace2(&tcph->check, skb,
108 						 htons(oldmss), htons(newmss),
109 						 0);
110 			return 0;
111 		}
112 	}
113 
114 	/* 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 
120 	/*
121 	 * MSS Option not found ?! add it..
122 	 */
123 	if (skb_tailroom(skb) < TCPOLEN_MSS) {
124 		if (pskb_expand_head(skb, 0,
125 				     TCPOLEN_MSS - skb_tailroom(skb),
126 				     GFP_ATOMIC))
127 			return -1;
128 		tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
129 	}
130 
131 	skb_put(skb, TCPOLEN_MSS);
132 
133 	/*
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
139 	 */
140 	if (par->family == NFPROTO_IPV4)
141 		newmss = min(newmss, (u16)536);
142 	else
143 		newmss = min(newmss, (u16)1220);
144 
145 	opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
146 	memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr));
147 
148 	inet_proto_csum_replace2(&tcph->check, skb,
149 				 htons(tcplen), htons(tcplen + TCPOLEN_MSS), 1);
150 	opt[0] = TCPOPT_MSS;
151 	opt[1] = TCPOLEN_MSS;
152 	opt[2] = (newmss & 0xff00) >> 8;
153 	opt[3] = newmss & 0x00ff;
154 
155 	inet_proto_csum_replace4(&tcph->check, skb, 0, *((__be32 *)opt), 0);
156 
157 	oldval = ((__be16 *)tcph)[6];
158 	tcph->doff += TCPOLEN_MSS/4;
159 	inet_proto_csum_replace2(&tcph->check, skb,
160 				 oldval, ((__be16 *)tcph)[6], 0);
161 	return TCPOLEN_MSS;
162 }
163 
164 static u_int32_t tcpmss_reverse_mtu(const struct sk_buff *skb,
165 				    unsigned int family)
166 {
167 	struct flowi fl;
168 	const struct nf_afinfo *ai;
169 	struct rtable *rt = NULL;
170 	u_int32_t mtu     = ~0U;
171 
172 	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;
178 
179 		memset(fl6, 0, sizeof(*fl6));
180 		fl6->daddr = ipv6_hdr(skb)->saddr;
181 	}
182 	rcu_read_lock();
183 	ai = nf_get_afinfo(family);
184 	if (ai != NULL)
185 		ai->route(&init_net, (struct dst_entry **)&rt, &fl, false);
186 	rcu_read_unlock();
187 
188 	if (rt != NULL) {
189 		mtu = dst_mtu(&rt->dst);
190 		dst_release(&rt->dst);
191 	}
192 	return mtu;
193 }
194 
195 static unsigned int
196 tcpmss_tg4(struct sk_buff *skb, const struct xt_action_param *par)
197 {
198 	struct iphdr *iph = ip_hdr(skb);
199 	__be16 newlen;
200 	int ret;
201 
202 	ret = tcpmss_mangle_packet(skb, par,
203 				   tcpmss_reverse_mtu(skb, PF_INET),
204 				   iph->ihl * 4,
205 				   sizeof(*iph) + sizeof(struct tcphdr));
206 	if (ret < 0)
207 		return NF_DROP;
208 	if (ret > 0) {
209 		iph = ip_hdr(skb);
210 		newlen = htons(ntohs(iph->tot_len) + ret);
211 		csum_replace2(&iph->check, iph->tot_len, newlen);
212 		iph->tot_len = newlen;
213 	}
214 	return XT_CONTINUE;
215 }
216 
217 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
218 static unsigned int
219 tcpmss_tg6(struct sk_buff *skb, const struct xt_action_param *par)
220 {
221 	struct ipv6hdr *ipv6h = ipv6_hdr(skb);
222 	u8 nexthdr;
223 	__be16 frag_off;
224 	int tcphoff;
225 	int ret;
226 
227 	nexthdr = ipv6h->nexthdr;
228 	tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr, &frag_off);
229 	if (tcphoff < 0)
230 		return NF_DROP;
231 	ret = tcpmss_mangle_packet(skb, par,
232 				   tcpmss_reverse_mtu(skb, PF_INET6),
233 				   tcphoff,
234 				   sizeof(*ipv6h) + sizeof(struct tcphdr));
235 	if (ret < 0)
236 		return NF_DROP;
237 	if (ret > 0) {
238 		ipv6h = ipv6_hdr(skb);
239 		ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
240 	}
241 	return XT_CONTINUE;
242 }
243 #endif
244 
245 /* Must specify -p tcp --syn */
246 static inline bool find_syn_match(const struct xt_entry_match *m)
247 {
248 	const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
249 
250 	if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
251 	    tcpinfo->flg_cmp & TCPHDR_SYN &&
252 	    !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
253 		return true;
254 
255 	return false;
256 }
257 
258 static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
259 {
260 	const struct xt_tcpmss_info *info = par->targinfo;
261 	const struct ipt_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 
279 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
280 static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
281 {
282 	const struct xt_tcpmss_info *info = par->targinfo;
283 	const struct ip6t_entry *e = par->entryinfo;
284 	const struct xt_entry_match *ematch;
285 
286 	if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
287 	    (par->hook_mask & ~((1 << NF_INET_FORWARD) |
288 			   (1 << NF_INET_LOCAL_OUT) |
289 			   (1 << NF_INET_POST_ROUTING))) != 0) {
290 		pr_info("path-MTU clamping only supported in "
291 			"FORWARD, OUTPUT and POSTROUTING hooks\n");
292 		return -EINVAL;
293 	}
294 	xt_ematch_foreach(ematch, e)
295 		if (find_syn_match(ematch))
296 			return 0;
297 	pr_info("Only works on TCP SYN packets\n");
298 	return -EINVAL;
299 }
300 #endif
301 
302 static struct xt_target tcpmss_tg_reg[] __read_mostly = {
303 	{
304 		.family		= NFPROTO_IPV4,
305 		.name		= "TCPMSS",
306 		.checkentry	= tcpmss_tg4_check,
307 		.target		= tcpmss_tg4,
308 		.targetsize	= sizeof(struct xt_tcpmss_info),
309 		.proto		= IPPROTO_TCP,
310 		.me		= THIS_MODULE,
311 	},
312 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
313 	{
314 		.family		= NFPROTO_IPV6,
315 		.name		= "TCPMSS",
316 		.checkentry	= tcpmss_tg6_check,
317 		.target		= tcpmss_tg6,
318 		.targetsize	= sizeof(struct xt_tcpmss_info),
319 		.proto		= IPPROTO_TCP,
320 		.me		= THIS_MODULE,
321 	},
322 #endif
323 };
324 
325 static int __init tcpmss_tg_init(void)
326 {
327 	return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
328 }
329 
330 static void __exit tcpmss_tg_exit(void)
331 {
332 	xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
333 }
334 
335 module_init(tcpmss_tg_init);
336 module_exit(tcpmss_tg_exit);
337