]> bbs.cooldavid.org Git - net-next-2.6.git/blob - net/netfilter/xt_TCPMSS.c
70288dc31583b309926aaba71c5a523295b395b8
[net-next-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 #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/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                                 pr_err("unknown or invalid path-MTU (%u)\n",
71                                        dst_mtu(skb_dst(skb)));
72                         return -1;
73                 }
74                 if (in_mtu <= minlen) {
75                         if (net_ratelimit())
76                                 pr_err("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                 fl.fl4_dst = ip_hdr(skb)->saddr;
157         else
158                 fl.fl6_dst = ipv6_hdr(skb)->saddr;
159
160         rcu_read_lock();
161         ai = nf_get_afinfo(family);
162         if (ai != NULL)
163                 ai->route((struct dst_entry **)&rt, &fl);
164         rcu_read_unlock();
165
166         if (rt != NULL) {
167                 mtu = dst_mtu(&rt->u.dst);
168                 dst_release(&rt->u.dst);
169         }
170         return mtu;
171 }
172
173 static unsigned int
174 tcpmss_tg4(struct sk_buff *skb, const struct xt_target_param *par)
175 {
176         struct iphdr *iph = ip_hdr(skb);
177         __be16 newlen;
178         int ret;
179
180         ret = tcpmss_mangle_packet(skb, par->targinfo,
181                                    tcpmss_reverse_mtu(skb, PF_INET),
182                                    iph->ihl * 4,
183                                    sizeof(*iph) + sizeof(struct tcphdr));
184         if (ret < 0)
185                 return NF_DROP;
186         if (ret > 0) {
187                 iph = ip_hdr(skb);
188                 newlen = htons(ntohs(iph->tot_len) + ret);
189                 csum_replace2(&iph->check, iph->tot_len, newlen);
190                 iph->tot_len = newlen;
191         }
192         return XT_CONTINUE;
193 }
194
195 #if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
196 static unsigned int
197 tcpmss_tg6(struct sk_buff *skb, const struct xt_target_param *par)
198 {
199         struct ipv6hdr *ipv6h = ipv6_hdr(skb);
200         u8 nexthdr;
201         int tcphoff;
202         int ret;
203
204         nexthdr = ipv6h->nexthdr;
205         tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr);
206         if (tcphoff < 0)
207                 return NF_DROP;
208         ret = tcpmss_mangle_packet(skb, par->targinfo,
209                                    tcpmss_reverse_mtu(skb, PF_INET6),
210                                    tcphoff,
211                                    sizeof(*ipv6h) + sizeof(struct tcphdr));
212         if (ret < 0)
213                 return NF_DROP;
214         if (ret > 0) {
215                 ipv6h = ipv6_hdr(skb);
216                 ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
217         }
218         return XT_CONTINUE;
219 }
220 #endif
221
222 #define TH_SYN 0x02
223
224 /* Must specify -p tcp --syn */
225 static inline bool find_syn_match(const struct xt_entry_match *m)
226 {
227         const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
228
229         if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
230             tcpinfo->flg_cmp & TH_SYN &&
231             !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
232                 return true;
233
234         return false;
235 }
236
237 static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
238 {
239         const struct xt_tcpmss_info *info = par->targinfo;
240         const struct ipt_entry *e = par->entryinfo;
241         const struct xt_entry_match *ematch;
242
243         if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
244             (par->hook_mask & ~((1 << NF_INET_FORWARD) |
245                            (1 << NF_INET_LOCAL_OUT) |
246                            (1 << NF_INET_POST_ROUTING))) != 0) {
247                 pr_info("path-MTU clamping only supported in "
248                         "FORWARD, OUTPUT and POSTROUTING hooks\n");
249                 return false;
250         }
251         xt_ematch_foreach(ematch, e)
252                 if (find_syn_match(ematch))
253                         return true;
254         pr_info("Only works on TCP SYN packets\n");
255         return false;
256 }
257
258 #if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
259 static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
260 {
261         const struct xt_tcpmss_info *info = par->targinfo;
262         const struct ip6t_entry *e = par->entryinfo;
263         const struct xt_entry_match *ematch;
264
265         if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
266             (par->hook_mask & ~((1 << NF_INET_FORWARD) |
267                            (1 << NF_INET_LOCAL_OUT) |
268                            (1 << NF_INET_POST_ROUTING))) != 0) {
269                 pr_info("path-MTU clamping only supported in "
270                         "FORWARD, OUTPUT and POSTROUTING hooks\n");
271                 return false;
272         }
273         xt_ematch_foreach(ematch, e)
274                 if (find_syn_match(ematch))
275                         return true;
276         pr_info("Only works on TCP SYN packets\n");
277         return false;
278 }
279 #endif
280
281 static struct xt_target tcpmss_tg_reg[] __read_mostly = {
282         {
283                 .family         = NFPROTO_IPV4,
284                 .name           = "TCPMSS",
285                 .checkentry     = tcpmss_tg4_check,
286                 .target         = tcpmss_tg4,
287                 .targetsize     = sizeof(struct xt_tcpmss_info),
288                 .proto          = IPPROTO_TCP,
289                 .me             = THIS_MODULE,
290         },
291 #if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
292         {
293                 .family         = NFPROTO_IPV6,
294                 .name           = "TCPMSS",
295                 .checkentry     = tcpmss_tg6_check,
296                 .target         = tcpmss_tg6,
297                 .targetsize     = sizeof(struct xt_tcpmss_info),
298                 .proto          = IPPROTO_TCP,
299                 .me             = THIS_MODULE,
300         },
301 #endif
302 };
303
304 static int __init tcpmss_tg_init(void)
305 {
306         return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
307 }
308
309 static void __exit tcpmss_tg_exit(void)
310 {
311         xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
312 }
313
314 module_init(tcpmss_tg_init);
315 module_exit(tcpmss_tg_exit);