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