netfilter: xtables: change targets to return error code
[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 #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 -EINVAL;
250 }
251 xt_ematch_foreach(ematch, e)
252 if (find_syn_match(ematch))
253 return 0;
254 pr_info("Only works on TCP SYN packets\n");
255 return -EINVAL;
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 -EINVAL;
272 }
273 xt_ematch_foreach(ematch, e)
274 if (find_syn_match(ematch))
275 return 0;
276 pr_info("Only works on TCP SYN packets\n");
277 return -EINVAL;
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);
This page took 0.036792 seconds and 5 git commands to generate.