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