| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (c) 2013 Eric Leblond <eric@regit.org> |
| * |
| * Development of this code partly funded by OISF |
| * (http://www.openinfosecfoundation.org/) |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/netlink.h> |
| #include <linux/jhash.h> |
| #include <linux/netfilter.h> |
| #include <linux/netfilter/nf_tables.h> |
| #include <net/netfilter/nf_tables.h> |
| #include <net/netfilter/nf_queue.h> |
| |
| static u32 jhash_initval __read_mostly; |
| |
| struct nft_queue { |
| u8 sreg_qnum; |
| u16 queuenum; |
| u16 queues_total; |
| u16 flags; |
| }; |
| |
| static void nft_queue_eval(const struct nft_expr *expr, |
| struct nft_regs *regs, |
| const struct nft_pktinfo *pkt) |
| { |
| struct nft_queue *priv = nft_expr_priv(expr); |
| u32 queue = priv->queuenum; |
| u32 ret; |
| |
| if (priv->queues_total > 1) { |
| if (priv->flags & NFT_QUEUE_FLAG_CPU_FANOUT) { |
| int cpu = raw_smp_processor_id(); |
| |
| queue = priv->queuenum + cpu % priv->queues_total; |
| } else { |
| queue = nfqueue_hash(pkt->skb, queue, |
| priv->queues_total, nft_pf(pkt), |
| jhash_initval); |
| } |
| } |
| |
| ret = NF_QUEUE_NR(queue); |
| if (priv->flags & NFT_QUEUE_FLAG_BYPASS) |
| ret |= NF_VERDICT_FLAG_QUEUE_BYPASS; |
| |
| regs->verdict.code = ret; |
| } |
| |
| static void nft_queue_sreg_eval(const struct nft_expr *expr, |
| struct nft_regs *regs, |
| const struct nft_pktinfo *pkt) |
| { |
| struct nft_queue *priv = nft_expr_priv(expr); |
| u32 queue, ret; |
| |
| queue = regs->data[priv->sreg_qnum]; |
| |
| ret = NF_QUEUE_NR(queue); |
| if (priv->flags & NFT_QUEUE_FLAG_BYPASS) |
| ret |= NF_VERDICT_FLAG_QUEUE_BYPASS; |
| |
| regs->verdict.code = ret; |
| } |
| |
| static int nft_queue_validate(const struct nft_ctx *ctx, |
| const struct nft_expr *expr) |
| { |
| static const unsigned int supported_hooks = ((1 << NF_INET_PRE_ROUTING) | |
| (1 << NF_INET_LOCAL_IN) | |
| (1 << NF_INET_FORWARD) | |
| (1 << NF_INET_LOCAL_OUT) | |
| (1 << NF_INET_POST_ROUTING)); |
| |
| switch (ctx->family) { |
| case NFPROTO_IPV4: |
| case NFPROTO_IPV6: |
| case NFPROTO_INET: |
| case NFPROTO_BRIDGE: |
| break; |
| case NFPROTO_NETDEV: /* lacks okfn */ |
| fallthrough; |
| default: |
| return -EOPNOTSUPP; |
| } |
| |
| return nft_chain_validate_hooks(ctx->chain, supported_hooks); |
| } |
| |
| static const struct nla_policy nft_queue_policy[NFTA_QUEUE_MAX + 1] = { |
| [NFTA_QUEUE_NUM] = { .type = NLA_U16 }, |
| [NFTA_QUEUE_TOTAL] = { .type = NLA_U16 }, |
| [NFTA_QUEUE_FLAGS] = { .type = NLA_U16 }, |
| [NFTA_QUEUE_SREG_QNUM] = { .type = NLA_U32 }, |
| }; |
| |
| static int nft_queue_init(const struct nft_ctx *ctx, |
| const struct nft_expr *expr, |
| const struct nlattr * const tb[]) |
| { |
| struct nft_queue *priv = nft_expr_priv(expr); |
| u32 maxid; |
| |
| priv->queuenum = ntohs(nla_get_be16(tb[NFTA_QUEUE_NUM])); |
| |
| if (tb[NFTA_QUEUE_TOTAL]) |
| priv->queues_total = ntohs(nla_get_be16(tb[NFTA_QUEUE_TOTAL])); |
| else |
| priv->queues_total = 1; |
| |
| if (priv->queues_total == 0) |
| return -EINVAL; |
| |
| maxid = priv->queues_total - 1 + priv->queuenum; |
| if (maxid > U16_MAX) |
| return -ERANGE; |
| |
| if (tb[NFTA_QUEUE_FLAGS]) { |
| priv->flags = ntohs(nla_get_be16(tb[NFTA_QUEUE_FLAGS])); |
| if (priv->flags & ~NFT_QUEUE_FLAG_MASK) |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int nft_queue_sreg_init(const struct nft_ctx *ctx, |
| const struct nft_expr *expr, |
| const struct nlattr * const tb[]) |
| { |
| struct nft_queue *priv = nft_expr_priv(expr); |
| int err; |
| |
| err = nft_parse_register_load(ctx, tb[NFTA_QUEUE_SREG_QNUM], |
| &priv->sreg_qnum, sizeof(u32)); |
| if (err < 0) |
| return err; |
| |
| if (tb[NFTA_QUEUE_FLAGS]) { |
| priv->flags = ntohs(nla_get_be16(tb[NFTA_QUEUE_FLAGS])); |
| if (priv->flags & ~NFT_QUEUE_FLAG_MASK) |
| return -EINVAL; |
| if (priv->flags & NFT_QUEUE_FLAG_CPU_FANOUT) |
| return -EOPNOTSUPP; |
| } |
| |
| return 0; |
| } |
| |
| static int nft_queue_dump(struct sk_buff *skb, |
| const struct nft_expr *expr, bool reset) |
| { |
| const struct nft_queue *priv = nft_expr_priv(expr); |
| |
| if (nla_put_be16(skb, NFTA_QUEUE_NUM, htons(priv->queuenum)) || |
| nla_put_be16(skb, NFTA_QUEUE_TOTAL, htons(priv->queues_total)) || |
| nla_put_be16(skb, NFTA_QUEUE_FLAGS, htons(priv->flags))) |
| goto nla_put_failure; |
| |
| return 0; |
| |
| nla_put_failure: |
| return -1; |
| } |
| |
| static int |
| nft_queue_sreg_dump(struct sk_buff *skb, |
| const struct nft_expr *expr, bool reset) |
| { |
| const struct nft_queue *priv = nft_expr_priv(expr); |
| |
| if (nft_dump_register(skb, NFTA_QUEUE_SREG_QNUM, priv->sreg_qnum) || |
| nla_put_be16(skb, NFTA_QUEUE_FLAGS, htons(priv->flags))) |
| goto nla_put_failure; |
| |
| return 0; |
| |
| nla_put_failure: |
| return -1; |
| } |
| |
| static struct nft_expr_type nft_queue_type; |
| static const struct nft_expr_ops nft_queue_ops = { |
| .type = &nft_queue_type, |
| .size = NFT_EXPR_SIZE(sizeof(struct nft_queue)), |
| .eval = nft_queue_eval, |
| .init = nft_queue_init, |
| .dump = nft_queue_dump, |
| .validate = nft_queue_validate, |
| .reduce = NFT_REDUCE_READONLY, |
| }; |
| |
| static const struct nft_expr_ops nft_queue_sreg_ops = { |
| .type = &nft_queue_type, |
| .size = NFT_EXPR_SIZE(sizeof(struct nft_queue)), |
| .eval = nft_queue_sreg_eval, |
| .init = nft_queue_sreg_init, |
| .dump = nft_queue_sreg_dump, |
| .validate = nft_queue_validate, |
| .reduce = NFT_REDUCE_READONLY, |
| }; |
| |
| static const struct nft_expr_ops * |
| nft_queue_select_ops(const struct nft_ctx *ctx, |
| const struct nlattr * const tb[]) |
| { |
| if (tb[NFTA_QUEUE_NUM] && tb[NFTA_QUEUE_SREG_QNUM]) |
| return ERR_PTR(-EINVAL); |
| |
| init_hashrandom(&jhash_initval); |
| |
| if (tb[NFTA_QUEUE_NUM]) |
| return &nft_queue_ops; |
| |
| if (tb[NFTA_QUEUE_SREG_QNUM]) |
| return &nft_queue_sreg_ops; |
| |
| return ERR_PTR(-EINVAL); |
| } |
| |
| static struct nft_expr_type nft_queue_type __read_mostly = { |
| .name = "queue", |
| .select_ops = nft_queue_select_ops, |
| .policy = nft_queue_policy, |
| .maxattr = NFTA_QUEUE_MAX, |
| .owner = THIS_MODULE, |
| }; |
| |
| static int __init nft_queue_module_init(void) |
| { |
| return nft_register_expr(&nft_queue_type); |
| } |
| |
| static void __exit nft_queue_module_exit(void) |
| { |
| nft_unregister_expr(&nft_queue_type); |
| } |
| |
| module_init(nft_queue_module_init); |
| module_exit(nft_queue_module_exit); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Eric Leblond <eric@regit.org>"); |
| MODULE_ALIAS_NFT_EXPR("queue"); |
| MODULE_DESCRIPTION("Netfilter nftables queue module"); |