Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
[GitHub/mt8127/android_kernel_alcatel_ttab.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 * 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_tcpmss_info *info,
49 unsigned int in_mtu,
50 unsigned int tcphoff,
51 unsigned int minlen)
52 {
53 struct tcphdr *tcph;
54 unsigned int tcplen, i;
55 __be16 oldval;
56 u16 newmss;
57 u8 *opt;
58
59 if (!skb_make_writable(skb, skb->len))
60 return -1;
61
62 tcplen = skb->len - tcphoff;
63 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
64
65 /* Header cannot be larger than the packet */
66 if (tcplen < tcph->doff*4)
67 return -1;
68
69 if (info->mss == XT_TCPMSS_CLAMP_PMTU) {
70 if (dst_mtu(skb_dst(skb)) <= minlen) {
71 net_err_ratelimited("unknown or invalid path-MTU (%u)\n",
72 dst_mtu(skb_dst(skb)));
73 return -1;
74 }
75 if (in_mtu <= minlen) {
76 net_err_ratelimited("unknown or invalid path-MTU (%u)\n",
77 in_mtu);
78 return -1;
79 }
80 newmss = min(dst_mtu(skb_dst(skb)), in_mtu) - minlen;
81 } 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
92 /* 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)
97 return 0;
98
99 opt[i+2] = (newmss & 0xff00) >> 8;
100 opt[i+3] = newmss & 0x00ff;
101
102 inet_proto_csum_replace2(&tcph->check, skb,
103 htons(oldmss), htons(newmss),
104 0);
105 return 0;
106 }
107 }
108
109 /* 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
115 /*
116 * MSS Option not found ?! add it..
117 */
118 if (skb_tailroom(skb) < TCPOLEN_MSS) {
119 if (pskb_expand_head(skb, 0,
120 TCPOLEN_MSS - skb_tailroom(skb),
121 GFP_ATOMIC))
122 return -1;
123 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
124 }
125
126 skb_put(skb, TCPOLEN_MSS);
127
128 /* RFC 879 states that the default MSS is 536 without specific
129 * knowledge that the destination host is prepared to accept larger.
130 * Since no MSS was provided, we MUST NOT set a value > 536.
131 */
132 newmss = min(newmss, (u16)536);
133
134 opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
135 memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr));
136
137 inet_proto_csum_replace2(&tcph->check, skb,
138 htons(tcplen), htons(tcplen + TCPOLEN_MSS), 1);
139 opt[0] = TCPOPT_MSS;
140 opt[1] = TCPOLEN_MSS;
141 opt[2] = (newmss & 0xff00) >> 8;
142 opt[3] = newmss & 0x00ff;
143
144 inet_proto_csum_replace4(&tcph->check, skb, 0, *((__be32 *)opt), 0);
145
146 oldval = ((__be16 *)tcph)[6];
147 tcph->doff += TCPOLEN_MSS/4;
148 inet_proto_csum_replace2(&tcph->check, skb,
149 oldval, ((__be16 *)tcph)[6], 0);
150 return TCPOLEN_MSS;
151 }
152
153 static u_int32_t tcpmss_reverse_mtu(const struct sk_buff *skb,
154 unsigned int family)
155 {
156 struct flowi fl;
157 const struct nf_afinfo *ai;
158 struct rtable *rt = NULL;
159 u_int32_t mtu = ~0U;
160
161 if (family == PF_INET) {
162 struct flowi4 *fl4 = &fl.u.ip4;
163 memset(fl4, 0, sizeof(*fl4));
164 fl4->daddr = ip_hdr(skb)->saddr;
165 } else {
166 struct flowi6 *fl6 = &fl.u.ip6;
167
168 memset(fl6, 0, sizeof(*fl6));
169 fl6->daddr = ipv6_hdr(skb)->saddr;
170 }
171 rcu_read_lock();
172 ai = nf_get_afinfo(family);
173 if (ai != NULL)
174 ai->route(&init_net, (struct dst_entry **)&rt, &fl, false);
175 rcu_read_unlock();
176
177 if (rt != NULL) {
178 mtu = dst_mtu(&rt->dst);
179 dst_release(&rt->dst);
180 }
181 return mtu;
182 }
183
184 static unsigned int
185 tcpmss_tg4(struct sk_buff *skb, const struct xt_action_param *par)
186 {
187 struct iphdr *iph = ip_hdr(skb);
188 __be16 newlen;
189 int ret;
190
191 ret = tcpmss_mangle_packet(skb, par->targinfo,
192 tcpmss_reverse_mtu(skb, PF_INET),
193 iph->ihl * 4,
194 sizeof(*iph) + sizeof(struct tcphdr));
195 if (ret < 0)
196 return NF_DROP;
197 if (ret > 0) {
198 iph = ip_hdr(skb);
199 newlen = htons(ntohs(iph->tot_len) + ret);
200 csum_replace2(&iph->check, iph->tot_len, newlen);
201 iph->tot_len = newlen;
202 }
203 return XT_CONTINUE;
204 }
205
206 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
207 static unsigned int
208 tcpmss_tg6(struct sk_buff *skb, const struct xt_action_param *par)
209 {
210 struct ipv6hdr *ipv6h = ipv6_hdr(skb);
211 u8 nexthdr;
212 __be16 frag_off;
213 int tcphoff;
214 int ret;
215
216 nexthdr = ipv6h->nexthdr;
217 tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr, &frag_off);
218 if (tcphoff < 0)
219 return NF_DROP;
220 ret = tcpmss_mangle_packet(skb, par->targinfo,
221 tcpmss_reverse_mtu(skb, PF_INET6),
222 tcphoff,
223 sizeof(*ipv6h) + sizeof(struct tcphdr));
224 if (ret < 0)
225 return NF_DROP;
226 if (ret > 0) {
227 ipv6h = ipv6_hdr(skb);
228 ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
229 }
230 return XT_CONTINUE;
231 }
232 #endif
233
234 /* Must specify -p tcp --syn */
235 static inline bool find_syn_match(const struct xt_entry_match *m)
236 {
237 const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
238
239 if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
240 tcpinfo->flg_cmp & TCPHDR_SYN &&
241 !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
242 return true;
243
244 return false;
245 }
246
247 static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
248 {
249 const struct xt_tcpmss_info *info = par->targinfo;
250 const struct ipt_entry *e = par->entryinfo;
251 const struct xt_entry_match *ematch;
252
253 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
254 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
255 (1 << NF_INET_LOCAL_OUT) |
256 (1 << NF_INET_POST_ROUTING))) != 0) {
257 pr_info("path-MTU clamping only supported in "
258 "FORWARD, OUTPUT and POSTROUTING hooks\n");
259 return -EINVAL;
260 }
261 xt_ematch_foreach(ematch, e)
262 if (find_syn_match(ematch))
263 return 0;
264 pr_info("Only works on TCP SYN packets\n");
265 return -EINVAL;
266 }
267
268 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
269 static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
270 {
271 const struct xt_tcpmss_info *info = par->targinfo;
272 const struct ip6t_entry *e = par->entryinfo;
273 const struct xt_entry_match *ematch;
274
275 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
276 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
277 (1 << NF_INET_LOCAL_OUT) |
278 (1 << NF_INET_POST_ROUTING))) != 0) {
279 pr_info("path-MTU clamping only supported in "
280 "FORWARD, OUTPUT and POSTROUTING hooks\n");
281 return -EINVAL;
282 }
283 xt_ematch_foreach(ematch, e)
284 if (find_syn_match(ematch))
285 return 0;
286 pr_info("Only works on TCP SYN packets\n");
287 return -EINVAL;
288 }
289 #endif
290
291 static struct xt_target tcpmss_tg_reg[] __read_mostly = {
292 {
293 .family = NFPROTO_IPV4,
294 .name = "TCPMSS",
295 .checkentry = tcpmss_tg4_check,
296 .target = tcpmss_tg4,
297 .targetsize = sizeof(struct xt_tcpmss_info),
298 .proto = IPPROTO_TCP,
299 .me = THIS_MODULE,
300 },
301 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
302 {
303 .family = NFPROTO_IPV6,
304 .name = "TCPMSS",
305 .checkentry = tcpmss_tg6_check,
306 .target = tcpmss_tg6,
307 .targetsize = sizeof(struct xt_tcpmss_info),
308 .proto = IPPROTO_TCP,
309 .me = THIS_MODULE,
310 },
311 #endif
312 };
313
314 static int __init tcpmss_tg_init(void)
315 {
316 return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
317 }
318
319 static void __exit tcpmss_tg_exit(void)
320 {
321 xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
322 }
323
324 module_init(tcpmss_tg_init);
325 module_exit(tcpmss_tg_exit);