• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2008 Patrick McHardy <kaber@trash.net>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 as
6  * published by the Free Software Foundation.
7  *
8  * Development of this code funded by Astaro AG (http://www.astaro.com/)
9  */
10 
11 #include <asm/unaligned.h>
12 #include <linux/kernel.h>
13 #include <linux/init.h>
14 #include <linux/module.h>
15 #include <linux/netlink.h>
16 #include <linux/netfilter.h>
17 #include <linux/netfilter/nf_tables.h>
18 #include <net/netfilter/nf_tables.h>
19 #include <net/tcp.h>
20 
21 struct nft_exthdr {
22 	u8			type;
23 	u8			offset;
24 	u8			len;
25 	u8			op;
26 	enum nft_registers	dreg:8;
27 	enum nft_registers	sreg:8;
28 	u8			flags;
29 };
30 
optlen(const u8 * opt,unsigned int offset)31 static unsigned int optlen(const u8 *opt, unsigned int offset)
32 {
33 	/* Beware zero-length options: make finite progress */
34 	if (opt[offset] <= TCPOPT_NOP || opt[offset + 1] == 0)
35 		return 1;
36 	else
37 		return opt[offset + 1];
38 }
39 
nft_exthdr_ipv6_eval(const struct nft_expr * expr,struct nft_regs * regs,const struct nft_pktinfo * pkt)40 static void nft_exthdr_ipv6_eval(const struct nft_expr *expr,
41 				 struct nft_regs *regs,
42 				 const struct nft_pktinfo *pkt)
43 {
44 	struct nft_exthdr *priv = nft_expr_priv(expr);
45 	u32 *dest = &regs->data[priv->dreg];
46 	unsigned int offset = 0;
47 	int err;
48 
49 	err = ipv6_find_hdr(pkt->skb, &offset, priv->type, NULL, NULL);
50 	if (priv->flags & NFT_EXTHDR_F_PRESENT) {
51 		*dest = (err >= 0);
52 		return;
53 	} else if (err < 0) {
54 		goto err;
55 	}
56 	offset += priv->offset;
57 
58 	dest[priv->len / NFT_REG32_SIZE] = 0;
59 	if (skb_copy_bits(pkt->skb, offset, dest, priv->len) < 0)
60 		goto err;
61 	return;
62 err:
63 	regs->verdict.code = NFT_BREAK;
64 }
65 
66 static void *
nft_tcp_header_pointer(const struct nft_pktinfo * pkt,unsigned int len,void * buffer,unsigned int * tcphdr_len)67 nft_tcp_header_pointer(const struct nft_pktinfo *pkt,
68 		       unsigned int len, void *buffer, unsigned int *tcphdr_len)
69 {
70 	struct tcphdr *tcph;
71 
72 	if (!pkt->tprot_set || pkt->tprot != IPPROTO_TCP)
73 		return NULL;
74 
75 	tcph = skb_header_pointer(pkt->skb, pkt->xt.thoff, sizeof(*tcph), buffer);
76 	if (!tcph)
77 		return NULL;
78 
79 	*tcphdr_len = __tcp_hdrlen(tcph);
80 	if (*tcphdr_len < sizeof(*tcph) || *tcphdr_len > len)
81 		return NULL;
82 
83 	return skb_header_pointer(pkt->skb, pkt->xt.thoff, *tcphdr_len, buffer);
84 }
85 
nft_exthdr_tcp_eval(const struct nft_expr * expr,struct nft_regs * regs,const struct nft_pktinfo * pkt)86 static void nft_exthdr_tcp_eval(const struct nft_expr *expr,
87 				struct nft_regs *regs,
88 				const struct nft_pktinfo *pkt)
89 {
90 	u8 buff[sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE];
91 	struct nft_exthdr *priv = nft_expr_priv(expr);
92 	unsigned int i, optl, tcphdr_len, offset;
93 	u32 *dest = &regs->data[priv->dreg];
94 	struct tcphdr *tcph;
95 	u8 *opt;
96 
97 	tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff, &tcphdr_len);
98 	if (!tcph)
99 		goto err;
100 
101 	opt = (u8 *)tcph;
102 	for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) {
103 		optl = optlen(opt, i);
104 
105 		if (priv->type != opt[i])
106 			continue;
107 
108 		if (i + optl > tcphdr_len || priv->len + priv->offset > optl)
109 			goto err;
110 
111 		offset = i + priv->offset;
112 		if (priv->flags & NFT_EXTHDR_F_PRESENT) {
113 			*dest = 1;
114 		} else {
115 			dest[priv->len / NFT_REG32_SIZE] = 0;
116 			memcpy(dest, opt + offset, priv->len);
117 		}
118 
119 		return;
120 	}
121 
122 err:
123 	if (priv->flags & NFT_EXTHDR_F_PRESENT)
124 		*dest = 0;
125 	else
126 		regs->verdict.code = NFT_BREAK;
127 }
128 
nft_exthdr_tcp_set_eval(const struct nft_expr * expr,struct nft_regs * regs,const struct nft_pktinfo * pkt)129 static void nft_exthdr_tcp_set_eval(const struct nft_expr *expr,
130 				    struct nft_regs *regs,
131 				    const struct nft_pktinfo *pkt)
132 {
133 	u8 buff[sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE];
134 	struct nft_exthdr *priv = nft_expr_priv(expr);
135 	unsigned int i, optl, tcphdr_len, offset;
136 	struct tcphdr *tcph;
137 	u8 *opt;
138 	u32 src;
139 
140 	tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff, &tcphdr_len);
141 	if (!tcph)
142 		return;
143 
144 	opt = (u8 *)tcph;
145 	for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) {
146 		union {
147 			u8 octet;
148 			__be16 v16;
149 			__be32 v32;
150 		} old, new;
151 
152 		optl = optlen(opt, i);
153 
154 		if (priv->type != opt[i])
155 			continue;
156 
157 		if (i + optl > tcphdr_len || priv->len + priv->offset > optl)
158 			return;
159 
160 		if (!skb_make_writable(pkt->skb, pkt->xt.thoff + i + priv->len))
161 			return;
162 
163 		tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff,
164 					      &tcphdr_len);
165 		if (!tcph)
166 			return;
167 
168 		src = regs->data[priv->sreg];
169 		offset = i + priv->offset;
170 
171 		switch (priv->len) {
172 		case 2:
173 			old.v16 = get_unaligned((u16 *)(opt + offset));
174 			new.v16 = src;
175 
176 			switch (priv->type) {
177 			case TCPOPT_MSS:
178 				/* increase can cause connection to stall */
179 				if (ntohs(old.v16) <= ntohs(new.v16))
180 					return;
181 			break;
182 			}
183 
184 			if (old.v16 == new.v16)
185 				return;
186 
187 			put_unaligned(new.v16, (u16*)(opt + offset));
188 			inet_proto_csum_replace2(&tcph->check, pkt->skb,
189 						 old.v16, new.v16, false);
190 			break;
191 		case 4:
192 			new.v32 = src;
193 			old.v32 = get_unaligned((u32 *)(opt + offset));
194 
195 			if (old.v32 == new.v32)
196 				return;
197 
198 			put_unaligned(new.v32, (u32*)(opt + offset));
199 			inet_proto_csum_replace4(&tcph->check, pkt->skb,
200 						 old.v32, new.v32, false);
201 			break;
202 		default:
203 			WARN_ON_ONCE(1);
204 			break;
205 		}
206 
207 		return;
208 	}
209 }
210 
211 static const struct nla_policy nft_exthdr_policy[NFTA_EXTHDR_MAX + 1] = {
212 	[NFTA_EXTHDR_DREG]		= { .type = NLA_U32 },
213 	[NFTA_EXTHDR_TYPE]		= { .type = NLA_U8 },
214 	[NFTA_EXTHDR_OFFSET]		= { .type = NLA_U32 },
215 	[NFTA_EXTHDR_LEN]		= { .type = NLA_U32 },
216 	[NFTA_EXTHDR_FLAGS]		= { .type = NLA_U32 },
217 };
218 
nft_exthdr_init(const struct nft_ctx * ctx,const struct nft_expr * expr,const struct nlattr * const tb[])219 static int nft_exthdr_init(const struct nft_ctx *ctx,
220 			   const struct nft_expr *expr,
221 			   const struct nlattr * const tb[])
222 {
223 	struct nft_exthdr *priv = nft_expr_priv(expr);
224 	u32 offset, len, flags = 0, op = NFT_EXTHDR_OP_IPV6;
225 	int err;
226 
227 	if (!tb[NFTA_EXTHDR_DREG] ||
228 	    !tb[NFTA_EXTHDR_TYPE] ||
229 	    !tb[NFTA_EXTHDR_OFFSET] ||
230 	    !tb[NFTA_EXTHDR_LEN])
231 		return -EINVAL;
232 
233 	err = nft_parse_u32_check(tb[NFTA_EXTHDR_OFFSET], U8_MAX, &offset);
234 	if (err < 0)
235 		return err;
236 
237 	err = nft_parse_u32_check(tb[NFTA_EXTHDR_LEN], U8_MAX, &len);
238 	if (err < 0)
239 		return err;
240 
241 	if (tb[NFTA_EXTHDR_FLAGS]) {
242 		err = nft_parse_u32_check(tb[NFTA_EXTHDR_FLAGS], U8_MAX, &flags);
243 		if (err < 0)
244 			return err;
245 
246 		if (flags & ~NFT_EXTHDR_F_PRESENT)
247 			return -EINVAL;
248 	}
249 
250 	if (tb[NFTA_EXTHDR_OP]) {
251 		err = nft_parse_u32_check(tb[NFTA_EXTHDR_OP], U8_MAX, &op);
252 		if (err < 0)
253 			return err;
254 	}
255 
256 	priv->type   = nla_get_u8(tb[NFTA_EXTHDR_TYPE]);
257 	priv->offset = offset;
258 	priv->len    = len;
259 	priv->dreg   = nft_parse_register(tb[NFTA_EXTHDR_DREG]);
260 	priv->flags  = flags;
261 	priv->op     = op;
262 
263 	return nft_validate_register_store(ctx, priv->dreg, NULL,
264 					   NFT_DATA_VALUE, priv->len);
265 }
266 
nft_exthdr_tcp_set_init(const struct nft_ctx * ctx,const struct nft_expr * expr,const struct nlattr * const tb[])267 static int nft_exthdr_tcp_set_init(const struct nft_ctx *ctx,
268 				   const struct nft_expr *expr,
269 				   const struct nlattr * const tb[])
270 {
271 	struct nft_exthdr *priv = nft_expr_priv(expr);
272 	u32 offset, len, flags = 0, op = NFT_EXTHDR_OP_IPV6;
273 	int err;
274 
275 	if (!tb[NFTA_EXTHDR_SREG] ||
276 	    !tb[NFTA_EXTHDR_TYPE] ||
277 	    !tb[NFTA_EXTHDR_OFFSET] ||
278 	    !tb[NFTA_EXTHDR_LEN])
279 		return -EINVAL;
280 
281 	if (tb[NFTA_EXTHDR_DREG] || tb[NFTA_EXTHDR_FLAGS])
282 		return -EINVAL;
283 
284 	err = nft_parse_u32_check(tb[NFTA_EXTHDR_OFFSET], U8_MAX, &offset);
285 	if (err < 0)
286 		return err;
287 
288 	err = nft_parse_u32_check(tb[NFTA_EXTHDR_LEN], U8_MAX, &len);
289 	if (err < 0)
290 		return err;
291 
292 	if (offset < 2)
293 		return -EOPNOTSUPP;
294 
295 	switch (len) {
296 	case 2: break;
297 	case 4: break;
298 	default:
299 		return -EOPNOTSUPP;
300 	}
301 
302 	err = nft_parse_u32_check(tb[NFTA_EXTHDR_OP], U8_MAX, &op);
303 	if (err < 0)
304 		return err;
305 
306 	priv->type   = nla_get_u8(tb[NFTA_EXTHDR_TYPE]);
307 	priv->offset = offset;
308 	priv->len    = len;
309 	priv->sreg   = nft_parse_register(tb[NFTA_EXTHDR_SREG]);
310 	priv->flags  = flags;
311 	priv->op     = op;
312 
313 	return nft_validate_register_load(priv->sreg, priv->len);
314 }
315 
nft_exthdr_dump_common(struct sk_buff * skb,const struct nft_exthdr * priv)316 static int nft_exthdr_dump_common(struct sk_buff *skb, const struct nft_exthdr *priv)
317 {
318 	if (nla_put_u8(skb, NFTA_EXTHDR_TYPE, priv->type))
319 		goto nla_put_failure;
320 	if (nla_put_be32(skb, NFTA_EXTHDR_OFFSET, htonl(priv->offset)))
321 		goto nla_put_failure;
322 	if (nla_put_be32(skb, NFTA_EXTHDR_LEN, htonl(priv->len)))
323 		goto nla_put_failure;
324 	if (nla_put_be32(skb, NFTA_EXTHDR_FLAGS, htonl(priv->flags)))
325 		goto nla_put_failure;
326 	if (nla_put_be32(skb, NFTA_EXTHDR_OP, htonl(priv->op)))
327 		goto nla_put_failure;
328 	return 0;
329 
330 nla_put_failure:
331 	return -1;
332 }
333 
nft_exthdr_dump(struct sk_buff * skb,const struct nft_expr * expr)334 static int nft_exthdr_dump(struct sk_buff *skb, const struct nft_expr *expr)
335 {
336 	const struct nft_exthdr *priv = nft_expr_priv(expr);
337 
338 	if (nft_dump_register(skb, NFTA_EXTHDR_DREG, priv->dreg))
339 		return -1;
340 
341 	return nft_exthdr_dump_common(skb, priv);
342 }
343 
nft_exthdr_dump_set(struct sk_buff * skb,const struct nft_expr * expr)344 static int nft_exthdr_dump_set(struct sk_buff *skb, const struct nft_expr *expr)
345 {
346 	const struct nft_exthdr *priv = nft_expr_priv(expr);
347 
348 	if (nft_dump_register(skb, NFTA_EXTHDR_SREG, priv->sreg))
349 		return -1;
350 
351 	return nft_exthdr_dump_common(skb, priv);
352 }
353 
354 static struct nft_expr_type nft_exthdr_type;
355 static const struct nft_expr_ops nft_exthdr_ipv6_ops = {
356 	.type		= &nft_exthdr_type,
357 	.size		= NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
358 	.eval		= nft_exthdr_ipv6_eval,
359 	.init		= nft_exthdr_init,
360 	.dump		= nft_exthdr_dump,
361 };
362 
363 static const struct nft_expr_ops nft_exthdr_tcp_ops = {
364 	.type		= &nft_exthdr_type,
365 	.size		= NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
366 	.eval		= nft_exthdr_tcp_eval,
367 	.init		= nft_exthdr_init,
368 	.dump		= nft_exthdr_dump,
369 };
370 
371 static const struct nft_expr_ops nft_exthdr_tcp_set_ops = {
372 	.type		= &nft_exthdr_type,
373 	.size		= NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
374 	.eval		= nft_exthdr_tcp_set_eval,
375 	.init		= nft_exthdr_tcp_set_init,
376 	.dump		= nft_exthdr_dump_set,
377 };
378 
379 static const struct nft_expr_ops *
nft_exthdr_select_ops(const struct nft_ctx * ctx,const struct nlattr * const tb[])380 nft_exthdr_select_ops(const struct nft_ctx *ctx,
381 		      const struct nlattr * const tb[])
382 {
383 	u32 op;
384 
385 	if (!tb[NFTA_EXTHDR_OP])
386 		return &nft_exthdr_ipv6_ops;
387 
388 	if (tb[NFTA_EXTHDR_SREG] && tb[NFTA_EXTHDR_DREG])
389 		return ERR_PTR(-EOPNOTSUPP);
390 
391 	op = ntohl(nla_get_be32(tb[NFTA_EXTHDR_OP]));
392 	switch (op) {
393 	case NFT_EXTHDR_OP_TCPOPT:
394 		if (tb[NFTA_EXTHDR_SREG])
395 			return &nft_exthdr_tcp_set_ops;
396 		if (tb[NFTA_EXTHDR_DREG])
397 			return &nft_exthdr_tcp_ops;
398 		break;
399 	case NFT_EXTHDR_OP_IPV6:
400 		if (tb[NFTA_EXTHDR_DREG])
401 			return &nft_exthdr_ipv6_ops;
402 		break;
403 	}
404 
405 	return ERR_PTR(-EOPNOTSUPP);
406 }
407 
408 static struct nft_expr_type nft_exthdr_type __read_mostly = {
409 	.name		= "exthdr",
410 	.select_ops	= nft_exthdr_select_ops,
411 	.policy		= nft_exthdr_policy,
412 	.maxattr	= NFTA_EXTHDR_MAX,
413 	.owner		= THIS_MODULE,
414 };
415 
nft_exthdr_module_init(void)416 static int __init nft_exthdr_module_init(void)
417 {
418 	return nft_register_expr(&nft_exthdr_type);
419 }
420 
nft_exthdr_module_exit(void)421 static void __exit nft_exthdr_module_exit(void)
422 {
423 	nft_unregister_expr(&nft_exthdr_type);
424 }
425 
426 module_init(nft_exthdr_module_init);
427 module_exit(nft_exthdr_module_exit);
428 
429 MODULE_LICENSE("GPL");
430 MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
431 MODULE_ALIAS_NFT_EXPR("exthdr");
432