netfilter: xtables: optimize call flow around xt_entry_foreach
[deliverable/linux.git] / net / netfilter / xt_TCPMSS.c
CommitLineData
cdd289a2
PM
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>
37c08387
JE
16#include <net/dst.h>
17#include <net/flow.h>
cdd289a2 18#include <net/ipv6.h>
37c08387 19#include <net/route.h>
cdd289a2
PM
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
28MODULE_LICENSE("GPL");
29MODULE_AUTHOR("Marc Boucher <marc@mbsi.ca>");
2ae15b64 30MODULE_DESCRIPTION("Xtables: TCP Maximum Segment Size (MSS) adjustment");
cdd289a2
PM
31MODULE_ALIAS("ipt_TCPMSS");
32MODULE_ALIAS("ip6t_TCPMSS");
33
34static inline unsigned int
35optlen(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
44static int
3db05fea 45tcpmss_mangle_packet(struct sk_buff *skb,
cdd289a2 46 const struct xt_tcpmss_info *info,
37c08387 47 unsigned int in_mtu,
cdd289a2
PM
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
3db05fea 57 if (!skb_make_writable(skb, skb->len))
cdd289a2
PM
58 return -1;
59
3db05fea
HX
60 tcplen = skb->len - tcphoff;
61 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
cdd289a2 62
10a19939
SA
63 /* Header cannot be larger than the packet */
64 if (tcplen < tcph->doff*4)
cdd289a2 65 return -1;
cdd289a2
PM
66
67 if (info->mss == XT_TCPMSS_CLAMP_PMTU) {
adf30907 68 if (dst_mtu(skb_dst(skb)) <= minlen) {
cdd289a2
PM
69 if (net_ratelimit())
70 printk(KERN_ERR "xt_TCPMSS: "
71 "unknown or invalid path-MTU (%u)\n",
adf30907 72 dst_mtu(skb_dst(skb)));
cdd289a2
PM
73 return -1;
74 }
37c08387
JE
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 }
adf30907 81 newmss = min(dst_mtu(skb_dst(skb)), in_mtu) - minlen;
cdd289a2
PM
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
17008064
BL
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)
cdd289a2
PM
98 return 0;
99
100 opt[i+2] = (newmss & 0xff00) >> 8;
7c4e36bc 101 opt[i+3] = newmss & 0x00ff;
cdd289a2 102
be0ea7d5
PM
103 inet_proto_csum_replace2(&tcph->check, skb,
104 htons(oldmss), htons(newmss),
105 0);
cdd289a2
PM
106 return 0;
107 }
108 }
109
10a19939
SA
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
cdd289a2
PM
116 /*
117 * MSS Option not found ?! add it..
118 */
3db05fea
HX
119 if (skb_tailroom(skb) < TCPOLEN_MSS) {
120 if (pskb_expand_head(skb, 0,
121 TCPOLEN_MSS - skb_tailroom(skb),
2ca7b0ac 122 GFP_ATOMIC))
cdd289a2 123 return -1;
3db05fea 124 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
cdd289a2
PM
125 }
126
3db05fea 127 skb_put(skb, TCPOLEN_MSS);
cdd289a2
PM
128
129 opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
130 memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr));
131
be0ea7d5
PM
132 inet_proto_csum_replace2(&tcph->check, skb,
133 htons(tcplen), htons(tcplen + TCPOLEN_MSS), 1);
cdd289a2
PM
134 opt[0] = TCPOPT_MSS;
135 opt[1] = TCPOLEN_MSS;
136 opt[2] = (newmss & 0xff00) >> 8;
7c4e36bc 137 opt[3] = newmss & 0x00ff;
cdd289a2 138
be0ea7d5 139 inet_proto_csum_replace4(&tcph->check, skb, 0, *((__be32 *)opt), 0);
cdd289a2
PM
140
141 oldval = ((__be16 *)tcph)[6];
142 tcph->doff += TCPOLEN_MSS/4;
be0ea7d5
PM
143 inet_proto_csum_replace2(&tcph->check, skb,
144 oldval, ((__be16 *)tcph)[6], 0);
cdd289a2
PM
145 return TCPOLEN_MSS;
146}
147
db1a75bd
JE
148static u_int32_t tcpmss_reverse_mtu(const struct sk_buff *skb,
149 unsigned int family)
37c08387 150{
db1a75bd 151 struct flowi fl = {};
37c08387
JE
152 const struct nf_afinfo *ai;
153 struct rtable *rt = NULL;
154 u_int32_t mtu = ~0U;
155
db1a75bd
JE
156 if (family == PF_INET)
157 fl.fl4_dst = ip_hdr(skb)->saddr;
158 else
159 fl.fl6_dst = ipv6_hdr(skb)->saddr;
160
37c08387 161 rcu_read_lock();
db1a75bd 162 ai = nf_get_afinfo(family);
37c08387
JE
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
cdd289a2 174static unsigned int
7eb35586 175tcpmss_tg4(struct sk_buff *skb, const struct xt_target_param *par)
cdd289a2 176{
3db05fea 177 struct iphdr *iph = ip_hdr(skb);
cdd289a2
PM
178 __be16 newlen;
179 int ret;
180
7eb35586 181 ret = tcpmss_mangle_packet(skb, par->targinfo,
db1a75bd 182 tcpmss_reverse_mtu(skb, PF_INET),
37c08387 183 iph->ihl * 4,
cdd289a2
PM
184 sizeof(*iph) + sizeof(struct tcphdr));
185 if (ret < 0)
186 return NF_DROP;
187 if (ret > 0) {
3db05fea 188 iph = ip_hdr(skb);
cdd289a2 189 newlen = htons(ntohs(iph->tot_len) + ret);
be0ea7d5 190 csum_replace2(&iph->check, iph->tot_len, newlen);
cdd289a2
PM
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)
197static unsigned int
7eb35586 198tcpmss_tg6(struct sk_buff *skb, const struct xt_target_param *par)
cdd289a2 199{
3db05fea 200 struct ipv6hdr *ipv6h = ipv6_hdr(skb);
cdd289a2
PM
201 u8 nexthdr;
202 int tcphoff;
203 int ret;
204
205 nexthdr = ipv6h->nexthdr;
3db05fea 206 tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr);
9dc0564e 207 if (tcphoff < 0)
cdd289a2 208 return NF_DROP;
7eb35586 209 ret = tcpmss_mangle_packet(skb, par->targinfo,
db1a75bd 210 tcpmss_reverse_mtu(skb, PF_INET6),
37c08387 211 tcphoff,
cdd289a2
PM
212 sizeof(*ipv6h) + sizeof(struct tcphdr));
213 if (ret < 0)
214 return NF_DROP;
215 if (ret > 0) {
3db05fea 216 ipv6h = ipv6_hdr(skb);
cdd289a2
PM
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 */
e1931b78 226static inline bool find_syn_match(const struct xt_entry_match *m)
cdd289a2
PM
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))
e1931b78 233 return true;
cdd289a2 234
e1931b78 235 return false;
cdd289a2
PM
236}
237
af5d6dc2 238static bool tcpmss_tg4_check(const struct xt_tgchk_param *par)
cdd289a2 239{
af5d6dc2
JE
240 const struct xt_tcpmss_info *info = par->targinfo;
241 const struct ipt_entry *e = par->entryinfo;
cdd289a2
PM
242
243 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
af5d6dc2 244 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
6e23ae2a
PM
245 (1 << NF_INET_LOCAL_OUT) |
246 (1 << NF_INET_POST_ROUTING))) != 0) {
cdd289a2
PM
247 printk("xt_TCPMSS: path-MTU clamping only supported in "
248 "FORWARD, OUTPUT and POSTROUTING hooks\n");
e1931b78 249 return false;
cdd289a2
PM
250 }
251 if (IPT_MATCH_ITERATE(e, find_syn_match))
e1931b78 252 return true;
cdd289a2 253 printk("xt_TCPMSS: Only works on TCP SYN packets\n");
e1931b78 254 return false;
cdd289a2
PM
255}
256
257#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
af5d6dc2 258static bool tcpmss_tg6_check(const struct xt_tgchk_param *par)
cdd289a2 259{
af5d6dc2
JE
260 const struct xt_tcpmss_info *info = par->targinfo;
261 const struct ip6t_entry *e = par->entryinfo;
cdd289a2
PM
262
263 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
af5d6dc2 264 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
6e23ae2a
PM
265 (1 << NF_INET_LOCAL_OUT) |
266 (1 << NF_INET_POST_ROUTING))) != 0) {
cdd289a2
PM
267 printk("xt_TCPMSS: path-MTU clamping only supported in "
268 "FORWARD, OUTPUT and POSTROUTING hooks\n");
e1931b78 269 return false;
cdd289a2
PM
270 }
271 if (IP6T_MATCH_ITERATE(e, find_syn_match))
e1931b78 272 return true;
cdd289a2 273 printk("xt_TCPMSS: Only works on TCP SYN packets\n");
e1931b78 274 return false;
cdd289a2
PM
275}
276#endif
277
d3c5ee6d 278static struct xt_target tcpmss_tg_reg[] __read_mostly = {
cdd289a2 279 {
ee999d8b 280 .family = NFPROTO_IPV4,
cdd289a2 281 .name = "TCPMSS",
d3c5ee6d
JE
282 .checkentry = tcpmss_tg4_check,
283 .target = tcpmss_tg4,
cdd289a2
PM
284 .targetsize = sizeof(struct xt_tcpmss_info),
285 .proto = IPPROTO_TCP,
286 .me = THIS_MODULE,
287 },
288#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
289 {
ee999d8b 290 .family = NFPROTO_IPV6,
cdd289a2 291 .name = "TCPMSS",
d3c5ee6d
JE
292 .checkentry = tcpmss_tg6_check,
293 .target = tcpmss_tg6,
cdd289a2
PM
294 .targetsize = sizeof(struct xt_tcpmss_info),
295 .proto = IPPROTO_TCP,
296 .me = THIS_MODULE,
297 },
298#endif
299};
300
d3c5ee6d 301static int __init tcpmss_tg_init(void)
cdd289a2 302{
d3c5ee6d 303 return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
cdd289a2
PM
304}
305
d3c5ee6d 306static void __exit tcpmss_tg_exit(void)
cdd289a2 307{
d3c5ee6d 308 xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
cdd289a2
PM
309}
310
d3c5ee6d
JE
311module_init(tcpmss_tg_init);
312module_exit(tcpmss_tg_exit);
This page took 0.330847 seconds and 5 git commands to generate.