netfilter: xtables: change targets to return error code
[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 */
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>
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 69 if (net_ratelimit())
ff67e4e4 70 pr_err("unknown or invalid path-MTU (%u)\n",
adf30907 71 dst_mtu(skb_dst(skb)));
cdd289a2
PM
72 return -1;
73 }
37c08387
JE
74 if (in_mtu <= minlen) {
75 if (net_ratelimit())
ff67e4e4
JE
76 pr_err("unknown or invalid path-MTU (%u)\n",
77 in_mtu);
37c08387
JE
78 return -1;
79 }
adf30907 80 newmss = min(dst_mtu(skb_dst(skb)), in_mtu) - minlen;
cdd289a2
PM
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
17008064
BL
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)
cdd289a2
PM
97 return 0;
98
99 opt[i+2] = (newmss & 0xff00) >> 8;
7c4e36bc 100 opt[i+3] = newmss & 0x00ff;
cdd289a2 101
be0ea7d5
PM
102 inet_proto_csum_replace2(&tcph->check, skb,
103 htons(oldmss), htons(newmss),
104 0);
cdd289a2
PM
105 return 0;
106 }
107 }
108
10a19939
SA
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
cdd289a2
PM
115 /*
116 * MSS Option not found ?! add it..
117 */
3db05fea
HX
118 if (skb_tailroom(skb) < TCPOLEN_MSS) {
119 if (pskb_expand_head(skb, 0,
120 TCPOLEN_MSS - skb_tailroom(skb),
2ca7b0ac 121 GFP_ATOMIC))
cdd289a2 122 return -1;
3db05fea 123 tcph = (struct tcphdr *)(skb_network_header(skb) + tcphoff);
cdd289a2
PM
124 }
125
3db05fea 126 skb_put(skb, TCPOLEN_MSS);
cdd289a2
PM
127
128 opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
129 memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr));
130
be0ea7d5
PM
131 inet_proto_csum_replace2(&tcph->check, skb,
132 htons(tcplen), htons(tcplen + TCPOLEN_MSS), 1);
cdd289a2
PM
133 opt[0] = TCPOPT_MSS;
134 opt[1] = TCPOLEN_MSS;
135 opt[2] = (newmss & 0xff00) >> 8;
7c4e36bc 136 opt[3] = newmss & 0x00ff;
cdd289a2 137
be0ea7d5 138 inet_proto_csum_replace4(&tcph->check, skb, 0, *((__be32 *)opt), 0);
cdd289a2
PM
139
140 oldval = ((__be16 *)tcph)[6];
141 tcph->doff += TCPOLEN_MSS/4;
be0ea7d5
PM
142 inet_proto_csum_replace2(&tcph->check, skb,
143 oldval, ((__be16 *)tcph)[6], 0);
cdd289a2
PM
144 return TCPOLEN_MSS;
145}
146
db1a75bd
JE
147static u_int32_t tcpmss_reverse_mtu(const struct sk_buff *skb,
148 unsigned int family)
37c08387 149{
db1a75bd 150 struct flowi fl = {};
37c08387
JE
151 const struct nf_afinfo *ai;
152 struct rtable *rt = NULL;
153 u_int32_t mtu = ~0U;
154
db1a75bd
JE
155 if (family == PF_INET)
156 fl.fl4_dst = ip_hdr(skb)->saddr;
157 else
158 fl.fl6_dst = ipv6_hdr(skb)->saddr;
159
37c08387 160 rcu_read_lock();
db1a75bd 161 ai = nf_get_afinfo(family);
37c08387
JE
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
cdd289a2 173static unsigned int
7eb35586 174tcpmss_tg4(struct sk_buff *skb, const struct xt_target_param *par)
cdd289a2 175{
3db05fea 176 struct iphdr *iph = ip_hdr(skb);
cdd289a2
PM
177 __be16 newlen;
178 int ret;
179
7eb35586 180 ret = tcpmss_mangle_packet(skb, par->targinfo,
db1a75bd 181 tcpmss_reverse_mtu(skb, PF_INET),
37c08387 182 iph->ihl * 4,
cdd289a2
PM
183 sizeof(*iph) + sizeof(struct tcphdr));
184 if (ret < 0)
185 return NF_DROP;
186 if (ret > 0) {
3db05fea 187 iph = ip_hdr(skb);
cdd289a2 188 newlen = htons(ntohs(iph->tot_len) + ret);
be0ea7d5 189 csum_replace2(&iph->check, iph->tot_len, newlen);
cdd289a2
PM
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)
196static unsigned int
7eb35586 197tcpmss_tg6(struct sk_buff *skb, const struct xt_target_param *par)
cdd289a2 198{
3db05fea 199 struct ipv6hdr *ipv6h = ipv6_hdr(skb);
cdd289a2
PM
200 u8 nexthdr;
201 int tcphoff;
202 int ret;
203
204 nexthdr = ipv6h->nexthdr;
3db05fea 205 tcphoff = ipv6_skip_exthdr(skb, sizeof(*ipv6h), &nexthdr);
9dc0564e 206 if (tcphoff < 0)
cdd289a2 207 return NF_DROP;
7eb35586 208 ret = tcpmss_mangle_packet(skb, par->targinfo,
db1a75bd 209 tcpmss_reverse_mtu(skb, PF_INET6),
37c08387 210 tcphoff,
cdd289a2
PM
211 sizeof(*ipv6h) + sizeof(struct tcphdr));
212 if (ret < 0)
213 return NF_DROP;
214 if (ret > 0) {
3db05fea 215 ipv6h = ipv6_hdr(skb);
cdd289a2
PM
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 */
e1931b78 225static inline bool find_syn_match(const struct xt_entry_match *m)
cdd289a2
PM
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))
e1931b78 232 return true;
cdd289a2 233
e1931b78 234 return false;
cdd289a2
PM
235}
236
135367b8 237static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
cdd289a2 238{
af5d6dc2
JE
239 const struct xt_tcpmss_info *info = par->targinfo;
240 const struct ipt_entry *e = par->entryinfo;
dcea992a 241 const struct xt_entry_match *ematch;
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) {
8bee4bad
JE
247 pr_info("path-MTU clamping only supported in "
248 "FORWARD, OUTPUT and POSTROUTING hooks\n");
d6b00a53 249 return -EINVAL;
cdd289a2 250 }
dcea992a
JE
251 xt_ematch_foreach(ematch, e)
252 if (find_syn_match(ematch))
d6b00a53 253 return 0;
8bee4bad 254 pr_info("Only works on TCP SYN packets\n");
d6b00a53 255 return -EINVAL;
cdd289a2
PM
256}
257
258#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
135367b8 259static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
cdd289a2 260{
af5d6dc2
JE
261 const struct xt_tcpmss_info *info = par->targinfo;
262 const struct ip6t_entry *e = par->entryinfo;
dcea992a 263 const struct xt_entry_match *ematch;
cdd289a2
PM
264
265 if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
af5d6dc2 266 (par->hook_mask & ~((1 << NF_INET_FORWARD) |
6e23ae2a
PM
267 (1 << NF_INET_LOCAL_OUT) |
268 (1 << NF_INET_POST_ROUTING))) != 0) {
8bee4bad
JE
269 pr_info("path-MTU clamping only supported in "
270 "FORWARD, OUTPUT and POSTROUTING hooks\n");
d6b00a53 271 return -EINVAL;
cdd289a2 272 }
dcea992a
JE
273 xt_ematch_foreach(ematch, e)
274 if (find_syn_match(ematch))
d6b00a53 275 return 0;
8bee4bad 276 pr_info("Only works on TCP SYN packets\n");
d6b00a53 277 return -EINVAL;
cdd289a2
PM
278}
279#endif
280
d3c5ee6d 281static struct xt_target tcpmss_tg_reg[] __read_mostly = {
cdd289a2 282 {
ee999d8b 283 .family = NFPROTO_IPV4,
cdd289a2 284 .name = "TCPMSS",
d3c5ee6d
JE
285 .checkentry = tcpmss_tg4_check,
286 .target = tcpmss_tg4,
cdd289a2
PM
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 {
ee999d8b 293 .family = NFPROTO_IPV6,
cdd289a2 294 .name = "TCPMSS",
d3c5ee6d
JE
295 .checkentry = tcpmss_tg6_check,
296 .target = tcpmss_tg6,
cdd289a2
PM
297 .targetsize = sizeof(struct xt_tcpmss_info),
298 .proto = IPPROTO_TCP,
299 .me = THIS_MODULE,
300 },
301#endif
302};
303
d3c5ee6d 304static int __init tcpmss_tg_init(void)
cdd289a2 305{
d3c5ee6d 306 return xt_register_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
cdd289a2
PM
307}
308
d3c5ee6d 309static void __exit tcpmss_tg_exit(void)
cdd289a2 310{
d3c5ee6d 311 xt_unregister_targets(tcpmss_tg_reg, ARRAY_SIZE(tcpmss_tg_reg));
cdd289a2
PM
312}
313
d3c5ee6d
JE
314module_init(tcpmss_tg_init);
315module_exit(tcpmss_tg_exit);
This page took 0.513188 seconds and 5 git commands to generate.