Merge branch 'perf-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[firefly-linux-kernel-4.4.55.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         opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
129         memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr));
130
131         inet_proto_csum_replace2(&tcph->check, skb,
132                                  htons(tcplen), htons(tcplen + TCPOLEN_MSS), 1);
133         opt[0] = TCPOPT_MSS;
134         opt[1] = TCPOLEN_MSS;
135         opt[2] = (newmss & 0xff00) >> 8;
136         opt[3] = newmss & 0x00ff;
137
138         inet_proto_csum_replace4(&tcph->check, skb, 0, *((__be32 *)opt), 0);
139
140         oldval = ((__be16 *)tcph)[6];
141         tcph->doff += TCPOLEN_MSS/4;
142         inet_proto_csum_replace2(&tcph->check, skb,
143                                  oldval, ((__be16 *)tcph)[6], 0);
144         return TCPOLEN_MSS;
145 }
146
147 static u_int32_t tcpmss_reverse_mtu(const struct sk_buff *skb,
148                                     unsigned int family)
149 {
150         struct flowi fl;
151         const struct nf_afinfo *ai;
152         struct rtable *rt = NULL;
153         u_int32_t mtu     = ~0U;
154
155         if (family == PF_INET) {
156                 struct flowi4 *fl4 = &fl.u.ip4;
157                 memset(fl4, 0, sizeof(*fl4));
158                 fl4->daddr = ip_hdr(skb)->saddr;
159         } else {
160                 struct flowi6 *fl6 = &fl.u.ip6;
161
162                 memset(fl6, 0, sizeof(*fl6));
163                 fl6->daddr = ipv6_hdr(skb)->saddr;
164         }
165         rcu_read_lock();
166         ai = nf_get_afinfo(family);
167         if (ai != NULL)
168                 ai->route(&init_net, (struct dst_entry **)&rt, &fl, false);
169         rcu_read_unlock();
170
171         if (rt != NULL) {
172                 mtu = dst_mtu(&rt->dst);
173                 dst_release(&rt->dst);
174         }
175         return mtu;
176 }
177
178 static unsigned int
179 tcpmss_tg4(struct sk_buff *skb, const struct xt_action_param *par)
180 {
181         struct iphdr *iph = ip_hdr(skb);
182         __be16 newlen;
183         int ret;
184
185         ret = tcpmss_mangle_packet(skb, par->targinfo,
186                                    tcpmss_reverse_mtu(skb, PF_INET),
187                                    iph->ihl * 4,
188                                    sizeof(*iph) + sizeof(struct tcphdr));
189         if (ret < 0)
190                 return NF_DROP;
191         if (ret > 0) {
192                 iph = ip_hdr(skb);
193                 newlen = htons(ntohs(iph->tot_len) + ret);
194                 csum_replace2(&iph->check, iph->tot_len, newlen);
195                 iph->tot_len = newlen;
196         }
197         return XT_CONTINUE;
198 }
199
200 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
201 static unsigned int
202 tcpmss_tg6(struct sk_buff *skb, const struct xt_action_param *par)
203 {
204         struct ipv6hdr *ipv6h = ipv6_hdr(skb);
205         u8 nexthdr;
206         __be16 frag_off;
207         int tcphoff;
208         int ret;
209
210         nexthdr = ipv6h->nexthdr;
211         tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr, &frag_off);
212         if (tcphoff < 0)
213                 return NF_DROP;
214         ret = tcpmss_mangle_packet(skb, par->targinfo,
215                                    tcpmss_reverse_mtu(skb, PF_INET6),
216                                    tcphoff,
217                                    sizeof(*ipv6h) + sizeof(struct tcphdr));
218         if (ret < 0)
219                 return NF_DROP;
220         if (ret > 0) {
221                 ipv6h = ipv6_hdr(skb);
222                 ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
223         }
224         return XT_CONTINUE;
225 }
226 #endif
227
228 /* Must specify -p tcp --syn */
229 static inline bool find_syn_match(const struct xt_entry_match *m)
230 {
231         const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
232
233         if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
234             tcpinfo->flg_cmp & TCPHDR_SYN &&
235             !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
236                 return true;
237
238         return false;
239 }
240
241 static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
242 {
243         const struct xt_tcpmss_info *info = par->targinfo;
244         const struct ipt_entry *e = par->entryinfo;
245         const struct xt_entry_match *ematch;
246
247         if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
248             (par->hook_mask & ~((1 << NF_INET_FORWARD) |
249                            (1 << NF_INET_LOCAL_OUT) |
250                            (1 << NF_INET_POST_ROUTING))) != 0) {
251                 pr_info("path-MTU clamping only supported in "
252                         "FORWARD, OUTPUT and POSTROUTING hooks\n");
253                 return -EINVAL;
254         }
255         xt_ematch_foreach(ematch, e)
256                 if (find_syn_match(ematch))
257                         return 0;
258         pr_info("Only works on TCP SYN packets\n");
259         return -EINVAL;
260 }
261
262 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
263 static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
264 {
265         const struct xt_tcpmss_info *info = par->targinfo;
266         const struct ip6t_entry *e = par->entryinfo;
267         const struct xt_entry_match *ematch;
268
269         if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
270             (par->hook_mask & ~((1 << NF_INET_FORWARD) |
271                            (1 << NF_INET_LOCAL_OUT) |
272                            (1 << NF_INET_POST_ROUTING))) != 0) {
273                 pr_info("path-MTU clamping only supported in "
274                         "FORWARD, OUTPUT and POSTROUTING hooks\n");
275                 return -EINVAL;
276         }
277         xt_ematch_foreach(ematch, e)
278                 if (find_syn_match(ematch))
279                         return 0;
280         pr_info("Only works on TCP SYN packets\n");
281         return -EINVAL;
282 }
283 #endif
284
285 static struct xt_target tcpmss_tg_reg[] __read_mostly = {
286         {
287                 .family         = NFPROTO_IPV4,
288                 .name           = "TCPMSS",
289                 .checkentry     = tcpmss_tg4_check,
290                 .target         = tcpmss_tg4,
291                 .targetsize     = sizeof(struct xt_tcpmss_info),
292                 .proto          = IPPROTO_TCP,
293                 .me             = THIS_MODULE,
294         },
295 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
296         {
297                 .family         = NFPROTO_IPV6,
298                 .name           = "TCPMSS",
299                 .checkentry     = tcpmss_tg6_check,
300                 .target         = tcpmss_tg6,
301                 .targetsize     = sizeof(struct xt_tcpmss_info),
302                 .proto          = IPPROTO_TCP,
303                 .me             = THIS_MODULE,
304         },
305 #endif
306 };
307
308 static int __init tcpmss_tg_init(void)
309 {
310         return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
311 }
312
313 static void __exit tcpmss_tg_exit(void)
314 {
315         xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
316 }
317
318 module_init(tcpmss_tg_init);
319 module_exit(tcpmss_tg_exit);