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