1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2008 Patrick McHardy <kaber@trash.net> |
4 | * |
5 | * Development of this code funded by Astaro AG (http://www.astaro.com/) |
6 | */ |
7 | |
8 | #include <asm/unaligned.h> |
9 | #include <linux/kernel.h> |
10 | #include <linux/netlink.h> |
11 | #include <linux/netfilter.h> |
12 | #include <linux/netfilter/nf_tables.h> |
13 | #include <linux/dccp.h> |
14 | #include <linux/sctp.h> |
15 | #include <net/netfilter/nf_tables_core.h> |
16 | #include <net/netfilter/nf_tables.h> |
17 | #include <net/tcp.h> |
18 | |
19 | struct nft_exthdr { |
20 | u8 type; |
21 | u8 offset; |
22 | u8 len; |
23 | u8 op; |
24 | u8 dreg; |
25 | u8 sreg; |
26 | u8 flags; |
27 | }; |
28 | |
29 | static unsigned int optlen(const u8 *opt, unsigned int offset) |
30 | { |
31 | /* Beware zero-length options: make finite progress */ |
32 | if (opt[offset] <= TCPOPT_NOP || opt[offset + 1] == 0) |
33 | return 1; |
34 | else |
35 | return opt[offset + 1]; |
36 | } |
37 | |
38 | static int nft_skb_copy_to_reg(const struct sk_buff *skb, int offset, u32 *dest, unsigned int len) |
39 | { |
40 | if (len % NFT_REG32_SIZE) |
41 | dest[len / NFT_REG32_SIZE] = 0; |
42 | |
43 | return skb_copy_bits(skb, offset, to: dest, len); |
44 | } |
45 | |
46 | static void nft_exthdr_ipv6_eval(const struct nft_expr *expr, |
47 | struct nft_regs *regs, |
48 | const struct nft_pktinfo *pkt) |
49 | { |
50 | struct nft_exthdr *priv = nft_expr_priv(expr); |
51 | u32 *dest = ®s->data[priv->dreg]; |
52 | unsigned int offset = 0; |
53 | int err; |
54 | |
55 | if (pkt->skb->protocol != htons(ETH_P_IPV6)) |
56 | goto err; |
57 | |
58 | err = ipv6_find_hdr(skb: pkt->skb, offset: &offset, target: priv->type, NULL, NULL); |
59 | if (priv->flags & NFT_EXTHDR_F_PRESENT) { |
60 | nft_reg_store8(dreg: dest, val: err >= 0); |
61 | return; |
62 | } else if (err < 0) { |
63 | goto err; |
64 | } |
65 | offset += priv->offset; |
66 | |
67 | if (nft_skb_copy_to_reg(skb: pkt->skb, offset, dest, len: priv->len) < 0) |
68 | goto err; |
69 | return; |
70 | err: |
71 | regs->verdict.code = NFT_BREAK; |
72 | } |
73 | |
74 | /* find the offset to specified option. |
75 | * |
76 | * If target header is found, its offset is set in *offset and return option |
77 | * number. Otherwise, return negative error. |
78 | * |
79 | * If the first fragment doesn't contain the End of Options it is considered |
80 | * invalid. |
81 | */ |
82 | static int ipv4_find_option(struct net *net, struct sk_buff *skb, |
83 | unsigned int *offset, int target) |
84 | { |
85 | unsigned char optbuf[sizeof(struct ip_options) + 40]; |
86 | struct ip_options *opt = (struct ip_options *)optbuf; |
87 | struct iphdr *iph, _iph; |
88 | unsigned int start; |
89 | bool found = false; |
90 | __be32 info; |
91 | int optlen; |
92 | |
93 | iph = skb_header_pointer(skb, offset: 0, len: sizeof(_iph), buffer: &_iph); |
94 | if (!iph) |
95 | return -EBADMSG; |
96 | start = sizeof(struct iphdr); |
97 | |
98 | optlen = iph->ihl * 4 - (int)sizeof(struct iphdr); |
99 | if (optlen <= 0) |
100 | return -ENOENT; |
101 | |
102 | memset(opt, 0, sizeof(struct ip_options)); |
103 | /* Copy the options since __ip_options_compile() modifies |
104 | * the options. |
105 | */ |
106 | if (skb_copy_bits(skb, offset: start, to: opt->__data, len: optlen)) |
107 | return -EBADMSG; |
108 | opt->optlen = optlen; |
109 | |
110 | if (__ip_options_compile(net, opt, NULL, info: &info)) |
111 | return -EBADMSG; |
112 | |
113 | switch (target) { |
114 | case IPOPT_SSRR: |
115 | case IPOPT_LSRR: |
116 | if (!opt->srr) |
117 | break; |
118 | found = target == IPOPT_SSRR ? opt->is_strictroute : |
119 | !opt->is_strictroute; |
120 | if (found) |
121 | *offset = opt->srr + start; |
122 | break; |
123 | case IPOPT_RR: |
124 | if (!opt->rr) |
125 | break; |
126 | *offset = opt->rr + start; |
127 | found = true; |
128 | break; |
129 | case IPOPT_RA: |
130 | if (!opt->router_alert) |
131 | break; |
132 | *offset = opt->router_alert + start; |
133 | found = true; |
134 | break; |
135 | default: |
136 | return -EOPNOTSUPP; |
137 | } |
138 | return found ? target : -ENOENT; |
139 | } |
140 | |
141 | static void nft_exthdr_ipv4_eval(const struct nft_expr *expr, |
142 | struct nft_regs *regs, |
143 | const struct nft_pktinfo *pkt) |
144 | { |
145 | struct nft_exthdr *priv = nft_expr_priv(expr); |
146 | u32 *dest = ®s->data[priv->dreg]; |
147 | struct sk_buff *skb = pkt->skb; |
148 | unsigned int offset; |
149 | int err; |
150 | |
151 | if (skb->protocol != htons(ETH_P_IP)) |
152 | goto err; |
153 | |
154 | err = ipv4_find_option(net: nft_net(pkt), skb, offset: &offset, target: priv->type); |
155 | if (priv->flags & NFT_EXTHDR_F_PRESENT) { |
156 | nft_reg_store8(dreg: dest, val: err >= 0); |
157 | return; |
158 | } else if (err < 0) { |
159 | goto err; |
160 | } |
161 | offset += priv->offset; |
162 | |
163 | if (nft_skb_copy_to_reg(skb: pkt->skb, offset, dest, len: priv->len) < 0) |
164 | goto err; |
165 | return; |
166 | err: |
167 | regs->verdict.code = NFT_BREAK; |
168 | } |
169 | |
170 | static void * |
171 | (const struct nft_pktinfo *pkt, |
172 | unsigned int len, void *buffer, unsigned int *tcphdr_len) |
173 | { |
174 | struct tcphdr *tcph; |
175 | |
176 | if (pkt->tprot != IPPROTO_TCP || pkt->fragoff) |
177 | return NULL; |
178 | |
179 | tcph = skb_header_pointer(skb: pkt->skb, offset: nft_thoff(pkt), len: sizeof(*tcph), buffer); |
180 | if (!tcph) |
181 | return NULL; |
182 | |
183 | *tcphdr_len = __tcp_hdrlen(th: tcph); |
184 | if (*tcphdr_len < sizeof(*tcph) || *tcphdr_len > len) |
185 | return NULL; |
186 | |
187 | return skb_header_pointer(skb: pkt->skb, offset: nft_thoff(pkt), len: *tcphdr_len, buffer); |
188 | } |
189 | |
190 | static void nft_exthdr_tcp_eval(const struct nft_expr *expr, |
191 | struct nft_regs *regs, |
192 | const struct nft_pktinfo *pkt) |
193 | { |
194 | u8 buff[sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE]; |
195 | struct nft_exthdr *priv = nft_expr_priv(expr); |
196 | unsigned int i, optl, tcphdr_len, offset; |
197 | u32 *dest = ®s->data[priv->dreg]; |
198 | struct tcphdr *tcph; |
199 | u8 *opt; |
200 | |
201 | tcph = nft_tcp_header_pointer(pkt, len: sizeof(buff), buffer: buff, tcphdr_len: &tcphdr_len); |
202 | if (!tcph) |
203 | goto err; |
204 | |
205 | opt = (u8 *)tcph; |
206 | for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) { |
207 | optl = optlen(opt, offset: i); |
208 | |
209 | if (priv->type != opt[i]) |
210 | continue; |
211 | |
212 | if (i + optl > tcphdr_len || priv->len + priv->offset > optl) |
213 | goto err; |
214 | |
215 | offset = i + priv->offset; |
216 | if (priv->flags & NFT_EXTHDR_F_PRESENT) { |
217 | nft_reg_store8(dreg: dest, val: 1); |
218 | } else { |
219 | if (priv->len % NFT_REG32_SIZE) |
220 | dest[priv->len / NFT_REG32_SIZE] = 0; |
221 | memcpy(dest, opt + offset, priv->len); |
222 | } |
223 | |
224 | return; |
225 | } |
226 | |
227 | err: |
228 | if (priv->flags & NFT_EXTHDR_F_PRESENT) |
229 | *dest = 0; |
230 | else |
231 | regs->verdict.code = NFT_BREAK; |
232 | } |
233 | |
234 | static void nft_exthdr_tcp_set_eval(const struct nft_expr *expr, |
235 | struct nft_regs *regs, |
236 | const struct nft_pktinfo *pkt) |
237 | { |
238 | u8 buff[sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE]; |
239 | struct nft_exthdr *priv = nft_expr_priv(expr); |
240 | unsigned int i, optl, tcphdr_len, offset; |
241 | struct tcphdr *tcph; |
242 | u8 *opt; |
243 | |
244 | tcph = nft_tcp_header_pointer(pkt, len: sizeof(buff), buffer: buff, tcphdr_len: &tcphdr_len); |
245 | if (!tcph) |
246 | goto err; |
247 | |
248 | if (skb_ensure_writable(skb: pkt->skb, write_len: nft_thoff(pkt) + tcphdr_len)) |
249 | goto err; |
250 | |
251 | tcph = (struct tcphdr *)(pkt->skb->data + nft_thoff(pkt)); |
252 | opt = (u8 *)tcph; |
253 | |
254 | for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) { |
255 | union { |
256 | __be16 v16; |
257 | __be32 v32; |
258 | } old, new; |
259 | |
260 | optl = optlen(opt, offset: i); |
261 | |
262 | if (priv->type != opt[i]) |
263 | continue; |
264 | |
265 | if (i + optl > tcphdr_len || priv->len + priv->offset > optl) |
266 | goto err; |
267 | |
268 | offset = i + priv->offset; |
269 | |
270 | switch (priv->len) { |
271 | case 2: |
272 | old.v16 = (__force __be16)get_unaligned((u16 *)(opt + offset)); |
273 | new.v16 = (__force __be16)nft_reg_load16( |
274 | sreg: ®s->data[priv->sreg]); |
275 | |
276 | switch (priv->type) { |
277 | case TCPOPT_MSS: |
278 | /* increase can cause connection to stall */ |
279 | if (ntohs(old.v16) <= ntohs(new.v16)) |
280 | return; |
281 | break; |
282 | } |
283 | |
284 | if (old.v16 == new.v16) |
285 | return; |
286 | |
287 | put_unaligned(new.v16, (__be16*)(opt + offset)); |
288 | inet_proto_csum_replace2(sum: &tcph->check, skb: pkt->skb, |
289 | from: old.v16, to: new.v16, pseudohdr: false); |
290 | break; |
291 | case 4: |
292 | new.v32 = nft_reg_load_be32(sreg: ®s->data[priv->sreg]); |
293 | old.v32 = (__force __be32)get_unaligned((u32 *)(opt + offset)); |
294 | |
295 | if (old.v32 == new.v32) |
296 | return; |
297 | |
298 | put_unaligned(new.v32, (__be32*)(opt + offset)); |
299 | inet_proto_csum_replace4(sum: &tcph->check, skb: pkt->skb, |
300 | from: old.v32, to: new.v32, pseudohdr: false); |
301 | break; |
302 | default: |
303 | WARN_ON_ONCE(1); |
304 | break; |
305 | } |
306 | |
307 | return; |
308 | } |
309 | return; |
310 | err: |
311 | regs->verdict.code = NFT_BREAK; |
312 | } |
313 | |
314 | static void nft_exthdr_tcp_strip_eval(const struct nft_expr *expr, |
315 | struct nft_regs *regs, |
316 | const struct nft_pktinfo *pkt) |
317 | { |
318 | u8 buff[sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE]; |
319 | struct nft_exthdr *priv = nft_expr_priv(expr); |
320 | unsigned int i, tcphdr_len, optl; |
321 | struct tcphdr *tcph; |
322 | u8 *opt; |
323 | |
324 | tcph = nft_tcp_header_pointer(pkt, len: sizeof(buff), buffer: buff, tcphdr_len: &tcphdr_len); |
325 | if (!tcph) |
326 | goto err; |
327 | |
328 | if (skb_ensure_writable(skb: pkt->skb, write_len: nft_thoff(pkt) + tcphdr_len)) |
329 | goto drop; |
330 | |
331 | tcph = (struct tcphdr *)(pkt->skb->data + nft_thoff(pkt)); |
332 | opt = (u8 *)tcph; |
333 | |
334 | for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) { |
335 | unsigned int j; |
336 | |
337 | optl = optlen(opt, offset: i); |
338 | if (priv->type != opt[i]) |
339 | continue; |
340 | |
341 | if (i + optl > tcphdr_len) |
342 | goto drop; |
343 | |
344 | for (j = 0; j < optl; ++j) { |
345 | u16 n = TCPOPT_NOP; |
346 | u16 o = opt[i+j]; |
347 | |
348 | if ((i + j) % 2 == 0) { |
349 | o <<= 8; |
350 | n <<= 8; |
351 | } |
352 | inet_proto_csum_replace2(sum: &tcph->check, skb: pkt->skb, htons(o), |
353 | htons(n), pseudohdr: false); |
354 | } |
355 | memset(opt + i, TCPOPT_NOP, optl); |
356 | return; |
357 | } |
358 | |
359 | /* option not found, continue. This allows to do multiple |
360 | * option removals per rule. |
361 | */ |
362 | return; |
363 | err: |
364 | regs->verdict.code = NFT_BREAK; |
365 | return; |
366 | drop: |
367 | /* can't remove, no choice but to drop */ |
368 | regs->verdict.code = NF_DROP; |
369 | } |
370 | |
371 | static void nft_exthdr_sctp_eval(const struct nft_expr *expr, |
372 | struct nft_regs *regs, |
373 | const struct nft_pktinfo *pkt) |
374 | { |
375 | unsigned int offset = nft_thoff(pkt) + sizeof(struct sctphdr); |
376 | struct nft_exthdr *priv = nft_expr_priv(expr); |
377 | u32 *dest = ®s->data[priv->dreg]; |
378 | const struct sctp_chunkhdr *sch; |
379 | struct sctp_chunkhdr _sch; |
380 | |
381 | if (pkt->tprot != IPPROTO_SCTP) |
382 | goto err; |
383 | |
384 | do { |
385 | sch = skb_header_pointer(skb: pkt->skb, offset, len: sizeof(_sch), buffer: &_sch); |
386 | if (!sch || !sch->length) |
387 | break; |
388 | |
389 | if (sch->type == priv->type) { |
390 | if (priv->flags & NFT_EXTHDR_F_PRESENT) { |
391 | nft_reg_store8(dreg: dest, val: true); |
392 | return; |
393 | } |
394 | if (priv->offset + priv->len > ntohs(sch->length) || |
395 | offset + ntohs(sch->length) > pkt->skb->len) |
396 | break; |
397 | |
398 | if (nft_skb_copy_to_reg(skb: pkt->skb, offset: offset + priv->offset, |
399 | dest, len: priv->len) < 0) |
400 | break; |
401 | return; |
402 | } |
403 | offset += SCTP_PAD4(ntohs(sch->length)); |
404 | } while (offset < pkt->skb->len); |
405 | err: |
406 | if (priv->flags & NFT_EXTHDR_F_PRESENT) |
407 | nft_reg_store8(dreg: dest, val: false); |
408 | else |
409 | regs->verdict.code = NFT_BREAK; |
410 | } |
411 | |
412 | static void nft_exthdr_dccp_eval(const struct nft_expr *expr, |
413 | struct nft_regs *regs, |
414 | const struct nft_pktinfo *pkt) |
415 | { |
416 | struct nft_exthdr *priv = nft_expr_priv(expr); |
417 | unsigned int thoff, dataoff, optoff, optlen, i; |
418 | u32 *dest = ®s->data[priv->dreg]; |
419 | const struct dccp_hdr *dh; |
420 | struct dccp_hdr _dh; |
421 | |
422 | if (pkt->tprot != IPPROTO_DCCP || pkt->fragoff) |
423 | goto err; |
424 | |
425 | thoff = nft_thoff(pkt); |
426 | |
427 | dh = skb_header_pointer(skb: pkt->skb, offset: thoff, len: sizeof(_dh), buffer: &_dh); |
428 | if (!dh) |
429 | goto err; |
430 | |
431 | dataoff = dh->dccph_doff * sizeof(u32); |
432 | optoff = __dccp_hdr_len(dh); |
433 | if (dataoff <= optoff) |
434 | goto err; |
435 | |
436 | optlen = dataoff - optoff; |
437 | |
438 | for (i = 0; i < optlen; ) { |
439 | /* Options 0 (DCCPO_PADDING) - 31 (DCCPO_MAX_RESERVED) are 1B in |
440 | * the length; the remaining options are at least 2B long. In |
441 | * all cases, the first byte contains the option type. In |
442 | * multi-byte options, the second byte contains the option |
443 | * length, which must be at least two: 1 for the type plus 1 for |
444 | * the length plus 0-253 for any following option data. We |
445 | * aren't interested in the option data, only the type and the |
446 | * length, so we don't need to read more than two bytes at a |
447 | * time. |
448 | */ |
449 | unsigned int buflen = optlen - i; |
450 | u8 buf[2], *bufp; |
451 | u8 type, len; |
452 | |
453 | if (buflen > sizeof(buf)) |
454 | buflen = sizeof(buf); |
455 | |
456 | bufp = skb_header_pointer(skb: pkt->skb, offset: thoff + optoff + i, len: buflen, |
457 | buffer: &buf); |
458 | if (!bufp) |
459 | goto err; |
460 | |
461 | type = bufp[0]; |
462 | |
463 | if (type == priv->type) { |
464 | nft_reg_store8(dreg: dest, val: 1); |
465 | return; |
466 | } |
467 | |
468 | if (type <= DCCPO_MAX_RESERVED) { |
469 | i++; |
470 | continue; |
471 | } |
472 | |
473 | if (buflen < 2) |
474 | goto err; |
475 | |
476 | len = bufp[1]; |
477 | |
478 | if (len < 2) |
479 | goto err; |
480 | |
481 | i += len; |
482 | } |
483 | |
484 | err: |
485 | *dest = 0; |
486 | } |
487 | |
488 | static const struct nla_policy nft_exthdr_policy[NFTA_EXTHDR_MAX + 1] = { |
489 | [NFTA_EXTHDR_DREG] = { .type = NLA_U32 }, |
490 | [NFTA_EXTHDR_TYPE] = { .type = NLA_U8 }, |
491 | [NFTA_EXTHDR_OFFSET] = { .type = NLA_U32 }, |
492 | [NFTA_EXTHDR_LEN] = NLA_POLICY_MAX(NLA_BE32, 255), |
493 | [NFTA_EXTHDR_FLAGS] = { .type = NLA_U32 }, |
494 | [NFTA_EXTHDR_OP] = NLA_POLICY_MAX(NLA_BE32, 255), |
495 | [NFTA_EXTHDR_SREG] = { .type = NLA_U32 }, |
496 | }; |
497 | |
498 | static int nft_exthdr_init(const struct nft_ctx *ctx, |
499 | const struct nft_expr *expr, |
500 | const struct nlattr * const tb[]) |
501 | { |
502 | struct nft_exthdr *priv = nft_expr_priv(expr); |
503 | u32 offset, len, flags = 0, op = NFT_EXTHDR_OP_IPV6; |
504 | int err; |
505 | |
506 | if (!tb[NFTA_EXTHDR_DREG] || |
507 | !tb[NFTA_EXTHDR_TYPE] || |
508 | !tb[NFTA_EXTHDR_OFFSET] || |
509 | !tb[NFTA_EXTHDR_LEN]) |
510 | return -EINVAL; |
511 | |
512 | err = nft_parse_u32_check(attr: tb[NFTA_EXTHDR_OFFSET], U8_MAX, dest: &offset); |
513 | if (err < 0) |
514 | return err; |
515 | |
516 | err = nft_parse_u32_check(attr: tb[NFTA_EXTHDR_LEN], U8_MAX, dest: &len); |
517 | if (err < 0) |
518 | return err; |
519 | |
520 | if (tb[NFTA_EXTHDR_FLAGS]) { |
521 | err = nft_parse_u32_check(attr: tb[NFTA_EXTHDR_FLAGS], U8_MAX, dest: &flags); |
522 | if (err < 0) |
523 | return err; |
524 | |
525 | if (flags & ~NFT_EXTHDR_F_PRESENT) |
526 | return -EINVAL; |
527 | } |
528 | |
529 | if (tb[NFTA_EXTHDR_OP]) { |
530 | err = nft_parse_u32_check(attr: tb[NFTA_EXTHDR_OP], U8_MAX, dest: &op); |
531 | if (err < 0) |
532 | return err; |
533 | } |
534 | |
535 | priv->type = nla_get_u8(nla: tb[NFTA_EXTHDR_TYPE]); |
536 | priv->offset = offset; |
537 | priv->len = len; |
538 | priv->flags = flags; |
539 | priv->op = op; |
540 | |
541 | return nft_parse_register_store(ctx, attr: tb[NFTA_EXTHDR_DREG], |
542 | dreg: &priv->dreg, NULL, type: NFT_DATA_VALUE, |
543 | len: priv->len); |
544 | } |
545 | |
546 | static int nft_exthdr_tcp_set_init(const struct nft_ctx *ctx, |
547 | const struct nft_expr *expr, |
548 | const struct nlattr * const tb[]) |
549 | { |
550 | struct nft_exthdr *priv = nft_expr_priv(expr); |
551 | u32 offset, len, flags = 0, op = NFT_EXTHDR_OP_IPV6; |
552 | int err; |
553 | |
554 | if (!tb[NFTA_EXTHDR_SREG] || |
555 | !tb[NFTA_EXTHDR_TYPE] || |
556 | !tb[NFTA_EXTHDR_OFFSET] || |
557 | !tb[NFTA_EXTHDR_LEN]) |
558 | return -EINVAL; |
559 | |
560 | if (tb[NFTA_EXTHDR_DREG] || tb[NFTA_EXTHDR_FLAGS]) |
561 | return -EINVAL; |
562 | |
563 | err = nft_parse_u32_check(attr: tb[NFTA_EXTHDR_OFFSET], U8_MAX, dest: &offset); |
564 | if (err < 0) |
565 | return err; |
566 | |
567 | err = nft_parse_u32_check(attr: tb[NFTA_EXTHDR_LEN], U8_MAX, dest: &len); |
568 | if (err < 0) |
569 | return err; |
570 | |
571 | if (offset < 2) |
572 | return -EOPNOTSUPP; |
573 | |
574 | switch (len) { |
575 | case 2: break; |
576 | case 4: break; |
577 | default: |
578 | return -EOPNOTSUPP; |
579 | } |
580 | |
581 | err = nft_parse_u32_check(attr: tb[NFTA_EXTHDR_OP], U8_MAX, dest: &op); |
582 | if (err < 0) |
583 | return err; |
584 | |
585 | priv->type = nla_get_u8(nla: tb[NFTA_EXTHDR_TYPE]); |
586 | priv->offset = offset; |
587 | priv->len = len; |
588 | priv->flags = flags; |
589 | priv->op = op; |
590 | |
591 | return nft_parse_register_load(attr: tb[NFTA_EXTHDR_SREG], sreg: &priv->sreg, |
592 | len: priv->len); |
593 | } |
594 | |
595 | static int nft_exthdr_tcp_strip_init(const struct nft_ctx *ctx, |
596 | const struct nft_expr *expr, |
597 | const struct nlattr * const tb[]) |
598 | { |
599 | struct nft_exthdr *priv = nft_expr_priv(expr); |
600 | |
601 | if (tb[NFTA_EXTHDR_SREG] || |
602 | tb[NFTA_EXTHDR_DREG] || |
603 | tb[NFTA_EXTHDR_FLAGS] || |
604 | tb[NFTA_EXTHDR_OFFSET] || |
605 | tb[NFTA_EXTHDR_LEN]) |
606 | return -EINVAL; |
607 | |
608 | if (!tb[NFTA_EXTHDR_TYPE]) |
609 | return -EINVAL; |
610 | |
611 | priv->type = nla_get_u8(nla: tb[NFTA_EXTHDR_TYPE]); |
612 | priv->op = NFT_EXTHDR_OP_TCPOPT; |
613 | |
614 | return 0; |
615 | } |
616 | |
617 | static int nft_exthdr_ipv4_init(const struct nft_ctx *ctx, |
618 | const struct nft_expr *expr, |
619 | const struct nlattr * const tb[]) |
620 | { |
621 | struct nft_exthdr *priv = nft_expr_priv(expr); |
622 | int err = nft_exthdr_init(ctx, expr, tb); |
623 | |
624 | if (err < 0) |
625 | return err; |
626 | |
627 | switch (priv->type) { |
628 | case IPOPT_SSRR: |
629 | case IPOPT_LSRR: |
630 | case IPOPT_RR: |
631 | case IPOPT_RA: |
632 | break; |
633 | default: |
634 | return -EOPNOTSUPP; |
635 | } |
636 | return 0; |
637 | } |
638 | |
639 | static int nft_exthdr_dccp_init(const struct nft_ctx *ctx, |
640 | const struct nft_expr *expr, |
641 | const struct nlattr * const tb[]) |
642 | { |
643 | struct nft_exthdr *priv = nft_expr_priv(expr); |
644 | int err = nft_exthdr_init(ctx, expr, tb); |
645 | |
646 | if (err < 0) |
647 | return err; |
648 | |
649 | if (!(priv->flags & NFT_EXTHDR_F_PRESENT)) |
650 | return -EOPNOTSUPP; |
651 | |
652 | return 0; |
653 | } |
654 | |
655 | static int nft_exthdr_dump_common(struct sk_buff *skb, const struct nft_exthdr *priv) |
656 | { |
657 | if (nla_put_u8(skb, attrtype: NFTA_EXTHDR_TYPE, value: priv->type)) |
658 | goto nla_put_failure; |
659 | if (nla_put_be32(skb, attrtype: NFTA_EXTHDR_OFFSET, htonl(priv->offset))) |
660 | goto nla_put_failure; |
661 | if (nla_put_be32(skb, attrtype: NFTA_EXTHDR_LEN, htonl(priv->len))) |
662 | goto nla_put_failure; |
663 | if (nla_put_be32(skb, attrtype: NFTA_EXTHDR_FLAGS, htonl(priv->flags))) |
664 | goto nla_put_failure; |
665 | if (nla_put_be32(skb, attrtype: NFTA_EXTHDR_OP, htonl(priv->op))) |
666 | goto nla_put_failure; |
667 | return 0; |
668 | |
669 | nla_put_failure: |
670 | return -1; |
671 | } |
672 | |
673 | static int nft_exthdr_dump(struct sk_buff *skb, |
674 | const struct nft_expr *expr, bool reset) |
675 | { |
676 | const struct nft_exthdr *priv = nft_expr_priv(expr); |
677 | |
678 | if (nft_dump_register(skb, attr: NFTA_EXTHDR_DREG, reg: priv->dreg)) |
679 | return -1; |
680 | |
681 | return nft_exthdr_dump_common(skb, priv); |
682 | } |
683 | |
684 | static int nft_exthdr_dump_set(struct sk_buff *skb, |
685 | const struct nft_expr *expr, bool reset) |
686 | { |
687 | const struct nft_exthdr *priv = nft_expr_priv(expr); |
688 | |
689 | if (nft_dump_register(skb, attr: NFTA_EXTHDR_SREG, reg: priv->sreg)) |
690 | return -1; |
691 | |
692 | return nft_exthdr_dump_common(skb, priv); |
693 | } |
694 | |
695 | static int nft_exthdr_dump_strip(struct sk_buff *skb, |
696 | const struct nft_expr *expr, bool reset) |
697 | { |
698 | const struct nft_exthdr *priv = nft_expr_priv(expr); |
699 | |
700 | return nft_exthdr_dump_common(skb, priv); |
701 | } |
702 | |
703 | static bool nft_exthdr_reduce(struct nft_regs_track *track, |
704 | const struct nft_expr *expr) |
705 | { |
706 | const struct nft_exthdr *priv = nft_expr_priv(expr); |
707 | const struct nft_exthdr *exthdr; |
708 | |
709 | if (!nft_reg_track_cmp(track, expr, dreg: priv->dreg)) { |
710 | nft_reg_track_update(track, expr, dreg: priv->dreg, len: priv->len); |
711 | return false; |
712 | } |
713 | |
714 | exthdr = nft_expr_priv(expr: track->regs[priv->dreg].selector); |
715 | if (priv->type != exthdr->type || |
716 | priv->op != exthdr->op || |
717 | priv->flags != exthdr->flags || |
718 | priv->offset != exthdr->offset || |
719 | priv->len != exthdr->len) { |
720 | nft_reg_track_update(track, expr, dreg: priv->dreg, len: priv->len); |
721 | return false; |
722 | } |
723 | |
724 | if (!track->regs[priv->dreg].bitwise) |
725 | return true; |
726 | |
727 | return nft_expr_reduce_bitwise(track, expr); |
728 | } |
729 | |
730 | static const struct nft_expr_ops nft_exthdr_ipv6_ops = { |
731 | .type = &nft_exthdr_type, |
732 | .size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)), |
733 | .eval = nft_exthdr_ipv6_eval, |
734 | .init = nft_exthdr_init, |
735 | .dump = nft_exthdr_dump, |
736 | .reduce = nft_exthdr_reduce, |
737 | }; |
738 | |
739 | static const struct nft_expr_ops nft_exthdr_ipv4_ops = { |
740 | .type = &nft_exthdr_type, |
741 | .size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)), |
742 | .eval = nft_exthdr_ipv4_eval, |
743 | .init = nft_exthdr_ipv4_init, |
744 | .dump = nft_exthdr_dump, |
745 | .reduce = nft_exthdr_reduce, |
746 | }; |
747 | |
748 | static const struct nft_expr_ops nft_exthdr_tcp_ops = { |
749 | .type = &nft_exthdr_type, |
750 | .size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)), |
751 | .eval = nft_exthdr_tcp_eval, |
752 | .init = nft_exthdr_init, |
753 | .dump = nft_exthdr_dump, |
754 | .reduce = nft_exthdr_reduce, |
755 | }; |
756 | |
757 | static const struct nft_expr_ops nft_exthdr_tcp_set_ops = { |
758 | .type = &nft_exthdr_type, |
759 | .size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)), |
760 | .eval = nft_exthdr_tcp_set_eval, |
761 | .init = nft_exthdr_tcp_set_init, |
762 | .dump = nft_exthdr_dump_set, |
763 | .reduce = NFT_REDUCE_READONLY, |
764 | }; |
765 | |
766 | static const struct nft_expr_ops nft_exthdr_tcp_strip_ops = { |
767 | .type = &nft_exthdr_type, |
768 | .size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)), |
769 | .eval = nft_exthdr_tcp_strip_eval, |
770 | .init = nft_exthdr_tcp_strip_init, |
771 | .dump = nft_exthdr_dump_strip, |
772 | .reduce = NFT_REDUCE_READONLY, |
773 | }; |
774 | |
775 | static const struct nft_expr_ops nft_exthdr_sctp_ops = { |
776 | .type = &nft_exthdr_type, |
777 | .size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)), |
778 | .eval = nft_exthdr_sctp_eval, |
779 | .init = nft_exthdr_init, |
780 | .dump = nft_exthdr_dump, |
781 | .reduce = nft_exthdr_reduce, |
782 | }; |
783 | |
784 | static const struct nft_expr_ops nft_exthdr_dccp_ops = { |
785 | .type = &nft_exthdr_type, |
786 | .size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)), |
787 | .eval = nft_exthdr_dccp_eval, |
788 | .init = nft_exthdr_dccp_init, |
789 | .dump = nft_exthdr_dump, |
790 | .reduce = nft_exthdr_reduce, |
791 | }; |
792 | |
793 | static const struct nft_expr_ops * |
794 | nft_exthdr_select_ops(const struct nft_ctx *ctx, |
795 | const struct nlattr * const tb[]) |
796 | { |
797 | u32 op; |
798 | |
799 | if (!tb[NFTA_EXTHDR_OP]) |
800 | return &nft_exthdr_ipv6_ops; |
801 | |
802 | if (tb[NFTA_EXTHDR_SREG] && tb[NFTA_EXTHDR_DREG]) |
803 | return ERR_PTR(error: -EOPNOTSUPP); |
804 | |
805 | op = ntohl(nla_get_be32(tb[NFTA_EXTHDR_OP])); |
806 | switch (op) { |
807 | case NFT_EXTHDR_OP_TCPOPT: |
808 | if (tb[NFTA_EXTHDR_SREG]) |
809 | return &nft_exthdr_tcp_set_ops; |
810 | if (tb[NFTA_EXTHDR_DREG]) |
811 | return &nft_exthdr_tcp_ops; |
812 | return &nft_exthdr_tcp_strip_ops; |
813 | case NFT_EXTHDR_OP_IPV6: |
814 | if (tb[NFTA_EXTHDR_DREG]) |
815 | return &nft_exthdr_ipv6_ops; |
816 | break; |
817 | case NFT_EXTHDR_OP_IPV4: |
818 | if (ctx->family != NFPROTO_IPV6) { |
819 | if (tb[NFTA_EXTHDR_DREG]) |
820 | return &nft_exthdr_ipv4_ops; |
821 | } |
822 | break; |
823 | case NFT_EXTHDR_OP_SCTP: |
824 | if (tb[NFTA_EXTHDR_DREG]) |
825 | return &nft_exthdr_sctp_ops; |
826 | break; |
827 | case NFT_EXTHDR_OP_DCCP: |
828 | if (tb[NFTA_EXTHDR_DREG]) |
829 | return &nft_exthdr_dccp_ops; |
830 | break; |
831 | } |
832 | |
833 | return ERR_PTR(error: -EOPNOTSUPP); |
834 | } |
835 | |
836 | struct nft_expr_type nft_exthdr_type __read_mostly = { |
837 | .name = "exthdr" , |
838 | .select_ops = nft_exthdr_select_ops, |
839 | .policy = nft_exthdr_policy, |
840 | .maxattr = NFTA_EXTHDR_MAX, |
841 | .owner = THIS_MODULE, |
842 | }; |
843 | |