Merge branch 'for-3.3/core' of git://git.kernel.dk/linux-block
[deliverable/linux.git] / net / netfilter / xt_TCPMSS.c
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
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
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
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
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
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 */
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
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)
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
309 static int __init tcpmss_tg_init(void)
310 {
311 return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
312 }
313
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);
This page took 0.037623 seconds and 5 git commands to generate.