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