cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

nft_exthdr.c (17669B)


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