Merge branch 'x86-mm-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git...
[pandora-kernel.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                         net_err_ratelimited("unknown or invalid path-MTU (%u)\n",
71                                             dst_mtu(skb_dst(skb)));
72                         return -1;
73                 }
74                 if (in_mtu <= minlen) {
75                         net_err_ratelimited("unknown or invalid path-MTU (%u)\n",
76                                             in_mtu);
77                         return -1;
78                 }
79                 newmss = min(dst_mtu(skb_dst(skb)), in_mtu) - minlen;
80         } else
81                 newmss = info->mss;
82
83         opt = (u_int8_t *)tcph;
84         for (i = sizeof(struct tcphdr); i < tcph->doff*4; i += optlen(opt, i)) {
85                 if (opt[i] == TCPOPT_MSS && tcph->doff*4 - i >= TCPOLEN_MSS &&
86                     opt[i+1] == TCPOLEN_MSS) {
87                         u_int16_t oldmss;
88
89                         oldmss = (opt[i+2] << 8) | opt[i+3];
90
91                         /* Never increase MSS, even when setting it, as
92                          * doing so results in problems for hosts that rely
93                          * on MSS being set correctly.
94                          */
95                         if (oldmss <= newmss)
96                                 return 0;
97
98                         opt[i+2] = (newmss & 0xff00) >> 8;
99                         opt[i+3] = newmss & 0x00ff;
100
101                         inet_proto_csum_replace2(&tcph->check, skb,
102                                                  htons(oldmss), htons(newmss),
103                                                  0);
104                         return 0;
105                 }
106         }
107
108         /* There is data after the header so the option can't be added
109            without moving it, and doing so may make the SYN packet
110            itself too large. Accept the packet unmodified instead. */
111         if (tcplen > tcph->doff*4)
112                 return 0;
113
114         /*
115          * MSS Option not found ?! add it..
116          */
117         if (skb_tailroom(skb) < TCPOLEN_MSS) {
118                 if (pskb_expand_head(skb, 0,
119                                      TCPOLEN_MSS - skb_tailroom(skb),
120                                      GFP_ATOMIC))
121                         return -1;
122                 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
123         }
124
125         skb_put(skb, TCPOLEN_MSS);
126
127         opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
128         memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr));
129
130         inet_proto_csum_replace2(&tcph->check, skb,
131                                  htons(tcplen), htons(tcplen + TCPOLEN_MSS), 1);
132         opt[0] = TCPOPT_MSS;
133         opt[1] = TCPOLEN_MSS;
134         opt[2] = (newmss & 0xff00) >> 8;
135         opt[3] = newmss & 0x00ff;
136
137         inet_proto_csum_replace4(&tcph->check, skb, 0, *((__be32 *)opt), 0);
138
139         oldval = ((__be16 *)tcph)[6];
140         tcph->doff += TCPOLEN_MSS/4;
141         inet_proto_csum_replace2(&tcph->check, skb,
142                                  oldval, ((__be16 *)tcph)[6], 0);
143         return TCPOLEN_MSS;
144 }
145
146 static u_int32_t tcpmss_reverse_mtu(const struct sk_buff *skb,
147                                     unsigned int family)
148 {
149         struct flowi fl;
150         const struct nf_afinfo *ai;
151         struct rtable *rt = NULL;
152         u_int32_t mtu     = ~0U;
153
154         if (family == PF_INET) {
155                 struct flowi4 *fl4 = &fl.u.ip4;
156                 memset(fl4, 0, sizeof(*fl4));
157                 fl4->daddr = ip_hdr(skb)->saddr;
158         } else {
159                 struct flowi6 *fl6 = &fl.u.ip6;
160
161                 memset(fl6, 0, sizeof(*fl6));
162                 fl6->daddr = ipv6_hdr(skb)->saddr;
163         }
164         rcu_read_lock();
165         ai = nf_get_afinfo(family);
166         if (ai != NULL)
167                 ai->route(&init_net, (struct dst_entry **)&rt, &fl, false);
168         rcu_read_unlock();
169
170         if (rt != NULL) {
171                 mtu = dst_mtu(&rt->dst);
172                 dst_release(&rt->dst);
173         }
174         return mtu;
175 }
176
177 static unsigned int
178 tcpmss_tg4(struct sk_buff *skb, const struct xt_action_param *par)
179 {
180         struct iphdr *iph = ip_hdr(skb);
181         __be16 newlen;
182         int ret;
183
184         ret = tcpmss_mangle_packet(skb, par->targinfo,
185                                    tcpmss_reverse_mtu(skb, PF_INET),
186                                    iph->ihl * 4,
187                                    sizeof(*iph) + sizeof(struct tcphdr));
188         if (ret < 0)
189                 return NF_DROP;
190         if (ret > 0) {
191                 iph = ip_hdr(skb);
192                 newlen = htons(ntohs(iph->tot_len) + ret);
193                 csum_replace2(&iph->check, iph->tot_len, newlen);
194                 iph->tot_len = newlen;
195         }
196         return XT_CONTINUE;
197 }
198
199 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
200 static unsigned int
201 tcpmss_tg6(struct sk_buff *skb, const struct xt_action_param *par)
202 {
203         struct ipv6hdr *ipv6h = ipv6_hdr(skb);
204         u8 nexthdr;
205         __be16 frag_off;
206         int tcphoff;
207         int ret;
208
209         nexthdr = ipv6h->nexthdr;
210         tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr, &frag_off);
211         if (tcphoff < 0)
212                 return NF_DROP;
213         ret = tcpmss_mangle_packet(skb, par->targinfo,
214                                    tcpmss_reverse_mtu(skb, PF_INET6),
215                                    tcphoff,
216                                    sizeof(*ipv6h) + sizeof(struct tcphdr));
217         if (ret < 0)
218                 return NF_DROP;
219         if (ret > 0) {
220                 ipv6h = ipv6_hdr(skb);
221                 ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
222         }
223         return XT_CONTINUE;
224 }
225 #endif
226
227 /* Must specify -p tcp --syn */
228 static inline bool find_syn_match(const struct xt_entry_match *m)
229 {
230         const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
231
232         if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
233             tcpinfo->flg_cmp & TCPHDR_SYN &&
234             !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
235                 return true;
236
237         return false;
238 }
239
240 static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
241 {
242         const struct xt_tcpmss_info *info = par->targinfo;
243         const struct ipt_entry *e = par->entryinfo;
244         const struct xt_entry_match *ematch;
245
246         if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
247             (par->hook_mask & ~((1 << NF_INET_FORWARD) |
248                            (1 << NF_INET_LOCAL_OUT) |
249                            (1 << NF_INET_POST_ROUTING))) != 0) {
250                 pr_info("path-MTU clamping only supported in "
251                         "FORWARD, OUTPUT and POSTROUTING hooks\n");
252                 return -EINVAL;
253         }
254         xt_ematch_foreach(ematch, e)
255                 if (find_syn_match(ematch))
256                         return 0;
257         pr_info("Only works on TCP SYN packets\n");
258         return -EINVAL;
259 }
260
261 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
262 static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
263 {
264         const struct xt_tcpmss_info *info = par->targinfo;
265         const struct ip6t_entry *e = par->entryinfo;
266         const struct xt_entry_match *ematch;
267
268         if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
269             (par->hook_mask & ~((1 << NF_INET_FORWARD) |
270                            (1 << NF_INET_LOCAL_OUT) |
271                            (1 << NF_INET_POST_ROUTING))) != 0) {
272                 pr_info("path-MTU clamping only supported in "
273                         "FORWARD, OUTPUT and POSTROUTING hooks\n");
274                 return -EINVAL;
275         }
276         xt_ematch_foreach(ematch, e)
277                 if (find_syn_match(ematch))
278                         return 0;
279         pr_info("Only works on TCP SYN packets\n");
280         return -EINVAL;
281 }
282 #endif
283
284 static struct xt_target tcpmss_tg_reg[] __read_mostly = {
285         {
286                 .family         = NFPROTO_IPV4,
287                 .name           = "TCPMSS",
288                 .checkentry     = tcpmss_tg4_check,
289                 .target         = tcpmss_tg4,
290                 .targetsize     = sizeof(struct xt_tcpmss_info),
291                 .proto          = IPPROTO_TCP,
292                 .me             = THIS_MODULE,
293         },
294 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
295         {
296                 .family         = NFPROTO_IPV6,
297                 .name           = "TCPMSS",
298                 .checkentry     = tcpmss_tg6_check,
299                 .target         = tcpmss_tg6,
300                 .targetsize     = sizeof(struct xt_tcpmss_info),
301                 .proto          = IPPROTO_TCP,
302                 .me             = THIS_MODULE,
303         },
304 #endif
305 };
306
307 static int __init tcpmss_tg_init(void)
308 {
309         return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
310 }
311
312 static void __exit tcpmss_tg_exit(void)
313 {
314         xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
315 }
316
317 module_init(tcpmss_tg_init);
318 module_exit(tcpmss_tg_exit);