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