Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
[deliverable/linux.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);
This page took 0.038194 seconds and 6 git commands to generate.