]> bbs.cooldavid.org Git - net-next-2.6.git/blame - net/netfilter/xt_TCPMSS.c
net-next: remove useless union keyword
[net-next-2.6.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 */
8bee4bad 10#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
cdd289a2
PM
11#include <linux/module.h>
12#include <linux/skbuff.h>
13#include <linux/ip.h>
5a0e3ad6 14#include <linux/gfp.h>
cdd289a2
PM
15#include <linux/ipv6.h>
16#include <linux/tcp.h>
37c08387
JE
17#include <net/dst.h>
18#include <net/flow.h>
cdd289a2 19#include <net/ipv6.h>
37c08387 20#include <net/route.h>
cdd289a2
PM
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
29MODULE_LICENSE("GPL");
30MODULE_AUTHOR("Marc Boucher <marc@mbsi.ca>");
2ae15b64 31MODULE_DESCRIPTION("Xtables: TCP Maximum Segment Size (MSS) adjustment");
cdd289a2
PM
32MODULE_ALIAS("ipt_TCPMSS");
33MODULE_ALIAS("ip6t_TCPMSS");
34
35static inline unsigned int
36optlen(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
45static int
3db05fea 46tcpmss_mangle_packet(struct sk_buff *skb,
cdd289a2 47 const struct xt_tcpmss_info *info,
37c08387 48 unsigned int in_mtu,
cdd289a2
PM
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
3db05fea 58 if (!skb_make_writable(skb, skb->len))
cdd289a2
PM
59 return -1;
60
3db05fea
HX
61 tcplen = skb->len - tcphoff;
62 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
cdd289a2 63
10a19939
SA
64 /* Header cannot be larger than the packet */
65 if (tcplen < tcph->doff*4)
cdd289a2 66 return -1;
cdd289a2
PM
67
68 if (info->mss == XT_TCPMSS_CLAMP_PMTU) {
adf30907 69 if (dst_mtu(skb_dst(skb)) <= minlen) {
cdd289a2 70 if (net_ratelimit())
ff67e4e4 71 pr_err("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())
ff67e4e4
JE
77 pr_err("unknown or invalid path-MTU (%u)\n",
78 in_mtu);
37c08387
JE
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) {
d8d1f30b
CG
168 mtu = dst_mtu(&rt->dst);
169 dst_release(&rt->dst);
37c08387
JE
170 }
171 return mtu;
172}
173
cdd289a2 174static unsigned int
4b560b44 175tcpmss_tg4(struct sk_buff *skb, const struct xt_action_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
4b560b44 198tcpmss_tg6(struct sk_buff *skb, const struct xt_action_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
135367b8 238static int 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;
dcea992a 242 const struct xt_entry_match *ematch;
cdd289a2
PM
243
244 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
af5d6dc2 245 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
6e23ae2a
PM
246 (1 << NF_INET_LOCAL_OUT) |
247 (1 << NF_INET_POST_ROUTING))) != 0) {
8bee4bad
JE
248 pr_info("path-MTU clamping only supported in "
249 "FORWARD, OUTPUT and POSTROUTING hooks\n");
d6b00a53 250 return -EINVAL;
cdd289a2 251 }
dcea992a
JE
252 xt_ematch_foreach(ematch, e)
253 if (find_syn_match(ematch))
d6b00a53 254 return 0;
8bee4bad 255 pr_info("Only works on TCP SYN packets\n");
d6b00a53 256 return -EINVAL;
cdd289a2
PM
257}
258
259#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
135367b8 260static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
cdd289a2 261{
af5d6dc2
JE
262 const struct xt_tcpmss_info *info = par->targinfo;
263 const struct ip6t_entry *e = par->entryinfo;
dcea992a 264 const struct xt_entry_match *ematch;
cdd289a2
PM
265
266 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
af5d6dc2 267 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
6e23ae2a
PM
268 (1 << NF_INET_LOCAL_OUT) |
269 (1 << NF_INET_POST_ROUTING))) != 0) {
8bee4bad
JE
270 pr_info("path-MTU clamping only supported in "
271 "FORWARD, OUTPUT and POSTROUTING hooks\n");
d6b00a53 272 return -EINVAL;
cdd289a2 273 }
dcea992a
JE
274 xt_ematch_foreach(ematch, e)
275 if (find_syn_match(ematch))
d6b00a53 276 return 0;
8bee4bad 277 pr_info("Only works on TCP SYN packets\n");
d6b00a53 278 return -EINVAL;
cdd289a2
PM
279}
280#endif
281
d3c5ee6d 282static struct xt_target tcpmss_tg_reg[] __read_mostly = {
cdd289a2 283 {
ee999d8b 284 .family = NFPROTO_IPV4,
cdd289a2 285 .name = "TCPMSS",
d3c5ee6d
JE
286 .checkentry = tcpmss_tg4_check,
287 .target = tcpmss_tg4,
cdd289a2
PM
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 {
ee999d8b 294 .family = NFPROTO_IPV6,
cdd289a2 295 .name = "TCPMSS",
d3c5ee6d
JE
296 .checkentry = tcpmss_tg6_check,
297 .target = tcpmss_tg6,
cdd289a2
PM
298 .targetsize = sizeof(struct xt_tcpmss_info),
299 .proto = IPPROTO_TCP,
300 .me = THIS_MODULE,
301 },
302#endif
303};
304
d3c5ee6d 305static int __init tcpmss_tg_init(void)
cdd289a2 306{
d3c5ee6d 307 return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
cdd289a2
PM
308}
309
d3c5ee6d 310static void __exit tcpmss_tg_exit(void)
cdd289a2 311{
d3c5ee6d 312 xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
cdd289a2
PM
313}
314
d3c5ee6d
JE
315module_init(tcpmss_tg_init);
316module_exit(tcpmss_tg_exit);