xref: /linux-6.15/net/netfilter/nft_queue.c (revision eaf9b2c8)
1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
20aff078dSEric Leblond /*
30aff078dSEric Leblond  * Copyright (c) 2013 Eric Leblond <[email protected]>
40aff078dSEric Leblond  *
50aff078dSEric Leblond  * Development of this code partly funded by OISF
60aff078dSEric Leblond  * (http://www.openinfosecfoundation.org/)
70aff078dSEric Leblond  */
80aff078dSEric Leblond 
90aff078dSEric Leblond #include <linux/kernel.h>
100aff078dSEric Leblond #include <linux/init.h>
110aff078dSEric Leblond #include <linux/module.h>
120aff078dSEric Leblond #include <linux/netlink.h>
130aff078dSEric Leblond #include <linux/jhash.h>
140aff078dSEric Leblond #include <linux/netfilter.h>
150aff078dSEric Leblond #include <linux/netfilter/nf_tables.h>
160aff078dSEric Leblond #include <net/netfilter/nf_tables.h>
170aff078dSEric Leblond #include <net/netfilter/nf_queue.h>
180aff078dSEric Leblond 
190aff078dSEric Leblond static u32 jhash_initval __read_mostly;
200aff078dSEric Leblond 
210aff078dSEric Leblond struct nft_queue {
224f16d25cSPablo Neira Ayuso 	u8	sreg_qnum;
230aff078dSEric Leblond 	u16	queuenum;
240aff078dSEric Leblond 	u16	queues_total;
250aff078dSEric Leblond 	u16	flags;
260aff078dSEric Leblond };
270aff078dSEric Leblond 
nft_queue_eval(const struct nft_expr * expr,struct nft_regs * regs,const struct nft_pktinfo * pkt)280aff078dSEric Leblond static void nft_queue_eval(const struct nft_expr *expr,
29a55e22e9SPatrick McHardy 			   struct nft_regs *regs,
300aff078dSEric Leblond 			   const struct nft_pktinfo *pkt)
310aff078dSEric Leblond {
320aff078dSEric Leblond 	struct nft_queue *priv = nft_expr_priv(expr);
330aff078dSEric Leblond 	u32 queue = priv->queuenum;
340aff078dSEric Leblond 	u32 ret;
350aff078dSEric Leblond 
360aff078dSEric Leblond 	if (priv->queues_total > 1) {
370aff078dSEric Leblond 		if (priv->flags & NFT_QUEUE_FLAG_CPU_FANOUT) {
38c2e756ffSPablo Neira Ayuso 			int cpu = raw_smp_processor_id();
390aff078dSEric Leblond 
400aff078dSEric Leblond 			queue = priv->queuenum + cpu % priv->queues_total;
410aff078dSEric Leblond 		} else {
420aff078dSEric Leblond 			queue = nfqueue_hash(pkt->skb, queue,
430e5a1c7eSPablo Neira Ayuso 					     priv->queues_total, nft_pf(pkt),
440aff078dSEric Leblond 					     jhash_initval);
450aff078dSEric Leblond 		}
460aff078dSEric Leblond 	}
470aff078dSEric Leblond 
480aff078dSEric Leblond 	ret = NF_QUEUE_NR(queue);
490aff078dSEric Leblond 	if (priv->flags & NFT_QUEUE_FLAG_BYPASS)
500aff078dSEric Leblond 		ret |= NF_VERDICT_FLAG_QUEUE_BYPASS;
510aff078dSEric Leblond 
52a55e22e9SPatrick McHardy 	regs->verdict.code = ret;
530aff078dSEric Leblond }
540aff078dSEric Leblond 
nft_queue_sreg_eval(const struct nft_expr * expr,struct nft_regs * regs,const struct nft_pktinfo * pkt)558061bb54SLiping Zhang static void nft_queue_sreg_eval(const struct nft_expr *expr,
568061bb54SLiping Zhang 				struct nft_regs *regs,
578061bb54SLiping Zhang 				const struct nft_pktinfo *pkt)
588061bb54SLiping Zhang {
598061bb54SLiping Zhang 	struct nft_queue *priv = nft_expr_priv(expr);
608061bb54SLiping Zhang 	u32 queue, ret;
618061bb54SLiping Zhang 
628061bb54SLiping Zhang 	queue = regs->data[priv->sreg_qnum];
638061bb54SLiping Zhang 
648061bb54SLiping Zhang 	ret = NF_QUEUE_NR(queue);
658061bb54SLiping Zhang 	if (priv->flags & NFT_QUEUE_FLAG_BYPASS)
668061bb54SLiping Zhang 		ret |= NF_VERDICT_FLAG_QUEUE_BYPASS;
678061bb54SLiping Zhang 
688061bb54SLiping Zhang 	regs->verdict.code = ret;
698061bb54SLiping Zhang }
708061bb54SLiping Zhang 
nft_queue_validate(const struct nft_ctx * ctx,const struct nft_expr * expr)7147f4f510SFlorian Westphal static int nft_queue_validate(const struct nft_ctx *ctx,
72*eaf9b2c8SFlorian Westphal 			      const struct nft_expr *expr)
7347f4f510SFlorian Westphal {
7447f4f510SFlorian Westphal 	static const unsigned int supported_hooks = ((1 << NF_INET_PRE_ROUTING) |
7547f4f510SFlorian Westphal 						     (1 << NF_INET_LOCAL_IN) |
7647f4f510SFlorian Westphal 						     (1 << NF_INET_FORWARD) |
7747f4f510SFlorian Westphal 						     (1 << NF_INET_LOCAL_OUT) |
7847f4f510SFlorian Westphal 						     (1 << NF_INET_POST_ROUTING));
7947f4f510SFlorian Westphal 
8047f4f510SFlorian Westphal 	switch (ctx->family) {
8147f4f510SFlorian Westphal 	case NFPROTO_IPV4:
8247f4f510SFlorian Westphal 	case NFPROTO_IPV6:
8347f4f510SFlorian Westphal 	case NFPROTO_INET:
8447f4f510SFlorian Westphal 	case NFPROTO_BRIDGE:
8547f4f510SFlorian Westphal 		break;
8647f4f510SFlorian Westphal 	case NFPROTO_NETDEV: /* lacks okfn */
8747f4f510SFlorian Westphal 		fallthrough;
8847f4f510SFlorian Westphal 	default:
8947f4f510SFlorian Westphal 		return -EOPNOTSUPP;
9047f4f510SFlorian Westphal 	}
9147f4f510SFlorian Westphal 
9247f4f510SFlorian Westphal 	return nft_chain_validate_hooks(ctx->chain, supported_hooks);
9347f4f510SFlorian Westphal }
9447f4f510SFlorian Westphal 
950aff078dSEric Leblond static const struct nla_policy nft_queue_policy[NFTA_QUEUE_MAX + 1] = {
960aff078dSEric Leblond 	[NFTA_QUEUE_NUM]	= { .type = NLA_U16 },
970aff078dSEric Leblond 	[NFTA_QUEUE_TOTAL]	= { .type = NLA_U16 },
980aff078dSEric Leblond 	[NFTA_QUEUE_FLAGS]	= { .type = NLA_U16 },
998061bb54SLiping Zhang 	[NFTA_QUEUE_SREG_QNUM]	= { .type = NLA_U32 },
1000aff078dSEric Leblond };
1010aff078dSEric Leblond 
nft_queue_init(const struct nft_ctx * ctx,const struct nft_expr * expr,const struct nlattr * const tb[])1020aff078dSEric Leblond static int nft_queue_init(const struct nft_ctx *ctx,
1030aff078dSEric Leblond 			  const struct nft_expr *expr,
1040aff078dSEric Leblond 			  const struct nlattr * const tb[])
1050aff078dSEric Leblond {
1060aff078dSEric Leblond 	struct nft_queue *priv = nft_expr_priv(expr);
107fe01111dSLiping Zhang 	u32 maxid;
1080aff078dSEric Leblond 
1090aff078dSEric Leblond 	priv->queuenum = ntohs(nla_get_be16(tb[NFTA_QUEUE_NUM]));
1100aff078dSEric Leblond 
1118061bb54SLiping Zhang 	if (tb[NFTA_QUEUE_TOTAL])
1120aff078dSEric Leblond 		priv->queues_total = ntohs(nla_get_be16(tb[NFTA_QUEUE_TOTAL]));
113fe01111dSLiping Zhang 	else
114fe01111dSLiping Zhang 		priv->queues_total = 1;
115fe01111dSLiping Zhang 
116fe01111dSLiping Zhang 	if (priv->queues_total == 0)
117fe01111dSLiping Zhang 		return -EINVAL;
118fe01111dSLiping Zhang 
119fe01111dSLiping Zhang 	maxid = priv->queues_total - 1 + priv->queuenum;
120fe01111dSLiping Zhang 	if (maxid > U16_MAX)
121fe01111dSLiping Zhang 		return -ERANGE;
122fe01111dSLiping Zhang 
1238061bb54SLiping Zhang 	if (tb[NFTA_QUEUE_FLAGS]) {
1240aff078dSEric Leblond 		priv->flags = ntohs(nla_get_be16(tb[NFTA_QUEUE_FLAGS]));
1250aff078dSEric Leblond 		if (priv->flags & ~NFT_QUEUE_FLAG_MASK)
1260aff078dSEric Leblond 			return -EINVAL;
1270aff078dSEric Leblond 	}
1280aff078dSEric Leblond 	return 0;
1290aff078dSEric Leblond }
1300aff078dSEric Leblond 
nft_queue_sreg_init(const struct nft_ctx * ctx,const struct nft_expr * expr,const struct nlattr * const tb[])1318061bb54SLiping Zhang static int nft_queue_sreg_init(const struct nft_ctx *ctx,
1328061bb54SLiping Zhang 			       const struct nft_expr *expr,
1338061bb54SLiping Zhang 			       const struct nlattr * const tb[])
1348061bb54SLiping Zhang {
1358061bb54SLiping Zhang 	struct nft_queue *priv = nft_expr_priv(expr);
1368061bb54SLiping Zhang 	int err;
1378061bb54SLiping Zhang 
1387ea0522eSFlorian Westphal 	err = nft_parse_register_load(ctx, tb[NFTA_QUEUE_SREG_QNUM],
1394f16d25cSPablo Neira Ayuso 				      &priv->sreg_qnum, sizeof(u32));
1408061bb54SLiping Zhang 	if (err < 0)
1418061bb54SLiping Zhang 		return err;
1428061bb54SLiping Zhang 
1438061bb54SLiping Zhang 	if (tb[NFTA_QUEUE_FLAGS]) {
1448061bb54SLiping Zhang 		priv->flags = ntohs(nla_get_be16(tb[NFTA_QUEUE_FLAGS]));
1458061bb54SLiping Zhang 		if (priv->flags & ~NFT_QUEUE_FLAG_MASK)
1468061bb54SLiping Zhang 			return -EINVAL;
1478061bb54SLiping Zhang 		if (priv->flags & NFT_QUEUE_FLAG_CPU_FANOUT)
1488061bb54SLiping Zhang 			return -EOPNOTSUPP;
1498061bb54SLiping Zhang 	}
1508061bb54SLiping Zhang 
1518061bb54SLiping Zhang 	return 0;
1528061bb54SLiping Zhang }
1538061bb54SLiping Zhang 
nft_queue_dump(struct sk_buff * skb,const struct nft_expr * expr,bool reset)1547d34aa3eSPhil Sutter static int nft_queue_dump(struct sk_buff *skb,
1557d34aa3eSPhil Sutter 			  const struct nft_expr *expr, bool reset)
1560aff078dSEric Leblond {
1570aff078dSEric Leblond 	const struct nft_queue *priv = nft_expr_priv(expr);
1580aff078dSEric Leblond 
1590aff078dSEric Leblond 	if (nla_put_be16(skb, NFTA_QUEUE_NUM, htons(priv->queuenum)) ||
1600aff078dSEric Leblond 	    nla_put_be16(skb, NFTA_QUEUE_TOTAL, htons(priv->queues_total)) ||
1610aff078dSEric Leblond 	    nla_put_be16(skb, NFTA_QUEUE_FLAGS, htons(priv->flags)))
1620aff078dSEric Leblond 		goto nla_put_failure;
1630aff078dSEric Leblond 
1640aff078dSEric Leblond 	return 0;
1650aff078dSEric Leblond 
1660aff078dSEric Leblond nla_put_failure:
1670aff078dSEric Leblond 	return -1;
1680aff078dSEric Leblond }
1690aff078dSEric Leblond 
1708061bb54SLiping Zhang static int
nft_queue_sreg_dump(struct sk_buff * skb,const struct nft_expr * expr,bool reset)1717d34aa3eSPhil Sutter nft_queue_sreg_dump(struct sk_buff *skb,
1727d34aa3eSPhil Sutter 		    const struct nft_expr *expr, bool reset)
1738061bb54SLiping Zhang {
1748061bb54SLiping Zhang 	const struct nft_queue *priv = nft_expr_priv(expr);
1758061bb54SLiping Zhang 
1768061bb54SLiping Zhang 	if (nft_dump_register(skb, NFTA_QUEUE_SREG_QNUM, priv->sreg_qnum) ||
1778061bb54SLiping Zhang 	    nla_put_be16(skb, NFTA_QUEUE_FLAGS, htons(priv->flags)))
1788061bb54SLiping Zhang 		goto nla_put_failure;
1798061bb54SLiping Zhang 
1808061bb54SLiping Zhang 	return 0;
1818061bb54SLiping Zhang 
1828061bb54SLiping Zhang nla_put_failure:
1838061bb54SLiping Zhang 	return -1;
1848061bb54SLiping Zhang }
1858061bb54SLiping Zhang 
1860aff078dSEric Leblond static struct nft_expr_type nft_queue_type;
1870aff078dSEric Leblond static const struct nft_expr_ops nft_queue_ops = {
1880aff078dSEric Leblond 	.type		= &nft_queue_type,
1890aff078dSEric Leblond 	.size		= NFT_EXPR_SIZE(sizeof(struct nft_queue)),
1900aff078dSEric Leblond 	.eval		= nft_queue_eval,
1910aff078dSEric Leblond 	.init		= nft_queue_init,
1920aff078dSEric Leblond 	.dump		= nft_queue_dump,
19347f4f510SFlorian Westphal 	.validate	= nft_queue_validate,
194b2d30654SPablo Neira Ayuso 	.reduce		= NFT_REDUCE_READONLY,
1950aff078dSEric Leblond };
1960aff078dSEric Leblond 
1978061bb54SLiping Zhang static const struct nft_expr_ops nft_queue_sreg_ops = {
1988061bb54SLiping Zhang 	.type		= &nft_queue_type,
1998061bb54SLiping Zhang 	.size		= NFT_EXPR_SIZE(sizeof(struct nft_queue)),
2008061bb54SLiping Zhang 	.eval		= nft_queue_sreg_eval,
2018061bb54SLiping Zhang 	.init		= nft_queue_sreg_init,
2028061bb54SLiping Zhang 	.dump		= nft_queue_sreg_dump,
20347f4f510SFlorian Westphal 	.validate	= nft_queue_validate,
204b2d30654SPablo Neira Ayuso 	.reduce		= NFT_REDUCE_READONLY,
2058061bb54SLiping Zhang };
2068061bb54SLiping Zhang 
2078061bb54SLiping Zhang static const struct nft_expr_ops *
nft_queue_select_ops(const struct nft_ctx * ctx,const struct nlattr * const tb[])2088061bb54SLiping Zhang nft_queue_select_ops(const struct nft_ctx *ctx,
2098061bb54SLiping Zhang 		     const struct nlattr * const tb[])
2108061bb54SLiping Zhang {
2118061bb54SLiping Zhang 	if (tb[NFTA_QUEUE_NUM] && tb[NFTA_QUEUE_SREG_QNUM])
2128061bb54SLiping Zhang 		return ERR_PTR(-EINVAL);
2138061bb54SLiping Zhang 
2148061bb54SLiping Zhang 	init_hashrandom(&jhash_initval);
2158061bb54SLiping Zhang 
2168061bb54SLiping Zhang 	if (tb[NFTA_QUEUE_NUM])
2178061bb54SLiping Zhang 		return &nft_queue_ops;
2188061bb54SLiping Zhang 
2198061bb54SLiping Zhang 	if (tb[NFTA_QUEUE_SREG_QNUM])
2208061bb54SLiping Zhang 		return &nft_queue_sreg_ops;
2218061bb54SLiping Zhang 
2228061bb54SLiping Zhang 	return ERR_PTR(-EINVAL);
2238061bb54SLiping Zhang }
2248061bb54SLiping Zhang 
2250aff078dSEric Leblond static struct nft_expr_type nft_queue_type __read_mostly = {
2260aff078dSEric Leblond 	.name		= "queue",
227d4ef3835SArushi Singhal 	.select_ops	= nft_queue_select_ops,
2280aff078dSEric Leblond 	.policy		= nft_queue_policy,
2290aff078dSEric Leblond 	.maxattr	= NFTA_QUEUE_MAX,
2300aff078dSEric Leblond 	.owner		= THIS_MODULE,
2310aff078dSEric Leblond };
2320aff078dSEric Leblond 
nft_queue_module_init(void)2330aff078dSEric Leblond static int __init nft_queue_module_init(void)
2340aff078dSEric Leblond {
2350aff078dSEric Leblond 	return nft_register_expr(&nft_queue_type);
2360aff078dSEric Leblond }
2370aff078dSEric Leblond 
nft_queue_module_exit(void)2380aff078dSEric Leblond static void __exit nft_queue_module_exit(void)
2390aff078dSEric Leblond {
2400aff078dSEric Leblond 	nft_unregister_expr(&nft_queue_type);
2410aff078dSEric Leblond }
2420aff078dSEric Leblond 
2430aff078dSEric Leblond module_init(nft_queue_module_init);
2440aff078dSEric Leblond module_exit(nft_queue_module_exit);
2450aff078dSEric Leblond 
2460aff078dSEric Leblond MODULE_LICENSE("GPL");
2470aff078dSEric Leblond MODULE_AUTHOR("Eric Leblond <[email protected]>");
2480aff078dSEric Leblond MODULE_ALIAS_NFT_EXPR("queue");
2494cacc395SRob Gill MODULE_DESCRIPTION("Netfilter nftables queue module");
250