linux/net/netfilter/nft_fwd_netdev.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright (c) 2015 Pablo Neira Ayuso <pablo@netfilter.org>
   4 */
   5
   6#include <linux/kernel.h>
   7#include <linux/init.h>
   8#include <linux/module.h>
   9#include <linux/netlink.h>
  10#include <linux/netfilter.h>
  11#include <linux/netfilter/nf_tables.h>
  12#include <linux/ip.h>
  13#include <linux/ipv6.h>
  14#include <net/netfilter/nf_tables.h>
  15#include <net/netfilter/nf_tables_offload.h>
  16#include <net/netfilter/nf_dup_netdev.h>
  17#include <net/neighbour.h>
  18#include <net/ip.h>
  19
  20struct nft_fwd_netdev {
  21        u8      sreg_dev;
  22};
  23
  24static void nft_fwd_netdev_eval(const struct nft_expr *expr,
  25                                struct nft_regs *regs,
  26                                const struct nft_pktinfo *pkt)
  27{
  28        struct nft_fwd_netdev *priv = nft_expr_priv(expr);
  29        int oif = regs->data[priv->sreg_dev];
  30
  31        /* This is used by ifb only. */
  32        skb_set_redirected(pkt->skb, true);
  33
  34        nf_fwd_netdev_egress(pkt, oif);
  35        regs->verdict.code = NF_STOLEN;
  36}
  37
  38static const struct nla_policy nft_fwd_netdev_policy[NFTA_FWD_MAX + 1] = {
  39        [NFTA_FWD_SREG_DEV]     = { .type = NLA_U32 },
  40        [NFTA_FWD_SREG_ADDR]    = { .type = NLA_U32 },
  41        [NFTA_FWD_NFPROTO]      = { .type = NLA_U32 },
  42};
  43
  44static int nft_fwd_netdev_init(const struct nft_ctx *ctx,
  45                               const struct nft_expr *expr,
  46                               const struct nlattr * const tb[])
  47{
  48        struct nft_fwd_netdev *priv = nft_expr_priv(expr);
  49
  50        if (tb[NFTA_FWD_SREG_DEV] == NULL)
  51                return -EINVAL;
  52
  53        return nft_parse_register_load(tb[NFTA_FWD_SREG_DEV], &priv->sreg_dev,
  54                                       sizeof(int));
  55}
  56
  57static int nft_fwd_netdev_dump(struct sk_buff *skb, const struct nft_expr *expr)
  58{
  59        struct nft_fwd_netdev *priv = nft_expr_priv(expr);
  60
  61        if (nft_dump_register(skb, NFTA_FWD_SREG_DEV, priv->sreg_dev))
  62                goto nla_put_failure;
  63
  64        return 0;
  65
  66nla_put_failure:
  67        return -1;
  68}
  69
  70static int nft_fwd_netdev_offload(struct nft_offload_ctx *ctx,
  71                                  struct nft_flow_rule *flow,
  72                                  const struct nft_expr *expr)
  73{
  74        const struct nft_fwd_netdev *priv = nft_expr_priv(expr);
  75        int oif = ctx->regs[priv->sreg_dev].data.data[0];
  76
  77        return nft_fwd_dup_netdev_offload(ctx, flow, FLOW_ACTION_REDIRECT, oif);
  78}
  79
  80struct nft_fwd_neigh {
  81        u8                      sreg_dev;
  82        u8                      sreg_addr;
  83        u8                      nfproto;
  84};
  85
  86static void nft_fwd_neigh_eval(const struct nft_expr *expr,
  87                              struct nft_regs *regs,
  88                              const struct nft_pktinfo *pkt)
  89{
  90        struct nft_fwd_neigh *priv = nft_expr_priv(expr);
  91        void *addr = &regs->data[priv->sreg_addr];
  92        int oif = regs->data[priv->sreg_dev];
  93        unsigned int verdict = NF_STOLEN;
  94        struct sk_buff *skb = pkt->skb;
  95        struct net_device *dev;
  96        int neigh_table;
  97
  98        switch (priv->nfproto) {
  99        case NFPROTO_IPV4: {
 100                struct iphdr *iph;
 101
 102                if (skb->protocol != htons(ETH_P_IP)) {
 103                        verdict = NFT_BREAK;
 104                        goto out;
 105                }
 106                if (skb_try_make_writable(skb, sizeof(*iph))) {
 107                        verdict = NF_DROP;
 108                        goto out;
 109                }
 110                iph = ip_hdr(skb);
 111                ip_decrease_ttl(iph);
 112                neigh_table = NEIGH_ARP_TABLE;
 113                break;
 114                }
 115        case NFPROTO_IPV6: {
 116                struct ipv6hdr *ip6h;
 117
 118                if (skb->protocol != htons(ETH_P_IPV6)) {
 119                        verdict = NFT_BREAK;
 120                        goto out;
 121                }
 122                if (skb_try_make_writable(skb, sizeof(*ip6h))) {
 123                        verdict = NF_DROP;
 124                        goto out;
 125                }
 126                ip6h = ipv6_hdr(skb);
 127                ip6h->hop_limit--;
 128                neigh_table = NEIGH_ND_TABLE;
 129                break;
 130                }
 131        default:
 132                verdict = NFT_BREAK;
 133                goto out;
 134        }
 135
 136        dev = dev_get_by_index_rcu(nft_net(pkt), oif);
 137        if (dev == NULL)
 138                return;
 139
 140        skb->dev = dev;
 141        skb->tstamp = 0;
 142        neigh_xmit(neigh_table, dev, addr, skb);
 143out:
 144        regs->verdict.code = verdict;
 145}
 146
 147static int nft_fwd_neigh_init(const struct nft_ctx *ctx,
 148                              const struct nft_expr *expr,
 149                              const struct nlattr * const tb[])
 150{
 151        struct nft_fwd_neigh *priv = nft_expr_priv(expr);
 152        unsigned int addr_len;
 153        int err;
 154
 155        if (!tb[NFTA_FWD_SREG_DEV] ||
 156            !tb[NFTA_FWD_SREG_ADDR] ||
 157            !tb[NFTA_FWD_NFPROTO])
 158                return -EINVAL;
 159
 160        priv->nfproto = ntohl(nla_get_be32(tb[NFTA_FWD_NFPROTO]));
 161
 162        switch (priv->nfproto) {
 163        case NFPROTO_IPV4:
 164                addr_len = sizeof(struct in_addr);
 165                break;
 166        case NFPROTO_IPV6:
 167                addr_len = sizeof(struct in6_addr);
 168                break;
 169        default:
 170                return -EOPNOTSUPP;
 171        }
 172
 173        err = nft_parse_register_load(tb[NFTA_FWD_SREG_DEV], &priv->sreg_dev,
 174                                      sizeof(int));
 175        if (err < 0)
 176                return err;
 177
 178        return nft_parse_register_load(tb[NFTA_FWD_SREG_ADDR], &priv->sreg_addr,
 179                                       addr_len);
 180}
 181
 182static int nft_fwd_neigh_dump(struct sk_buff *skb, const struct nft_expr *expr)
 183{
 184        struct nft_fwd_neigh *priv = nft_expr_priv(expr);
 185
 186        if (nft_dump_register(skb, NFTA_FWD_SREG_DEV, priv->sreg_dev) ||
 187            nft_dump_register(skb, NFTA_FWD_SREG_ADDR, priv->sreg_addr) ||
 188            nla_put_be32(skb, NFTA_FWD_NFPROTO, htonl(priv->nfproto)))
 189                goto nla_put_failure;
 190
 191        return 0;
 192
 193nla_put_failure:
 194        return -1;
 195}
 196
 197static int nft_fwd_validate(const struct nft_ctx *ctx,
 198                            const struct nft_expr *expr,
 199                            const struct nft_data **data)
 200{
 201        return nft_chain_validate_hooks(ctx->chain, (1 << NF_NETDEV_INGRESS));
 202}
 203
 204static struct nft_expr_type nft_fwd_netdev_type;
 205static const struct nft_expr_ops nft_fwd_neigh_netdev_ops = {
 206        .type           = &nft_fwd_netdev_type,
 207        .size           = NFT_EXPR_SIZE(sizeof(struct nft_fwd_neigh)),
 208        .eval           = nft_fwd_neigh_eval,
 209        .init           = nft_fwd_neigh_init,
 210        .dump           = nft_fwd_neigh_dump,
 211        .validate       = nft_fwd_validate,
 212};
 213
 214static const struct nft_expr_ops nft_fwd_netdev_ops = {
 215        .type           = &nft_fwd_netdev_type,
 216        .size           = NFT_EXPR_SIZE(sizeof(struct nft_fwd_netdev)),
 217        .eval           = nft_fwd_netdev_eval,
 218        .init           = nft_fwd_netdev_init,
 219        .dump           = nft_fwd_netdev_dump,
 220        .validate       = nft_fwd_validate,
 221        .offload        = nft_fwd_netdev_offload,
 222};
 223
 224static const struct nft_expr_ops *
 225nft_fwd_select_ops(const struct nft_ctx *ctx,
 226                   const struct nlattr * const tb[])
 227{
 228        if (tb[NFTA_FWD_SREG_ADDR])
 229                return &nft_fwd_neigh_netdev_ops;
 230        if (tb[NFTA_FWD_SREG_DEV])
 231                return &nft_fwd_netdev_ops;
 232
 233        return ERR_PTR(-EOPNOTSUPP);
 234}
 235
 236static struct nft_expr_type nft_fwd_netdev_type __read_mostly = {
 237        .family         = NFPROTO_NETDEV,
 238        .name           = "fwd",
 239        .select_ops     = nft_fwd_select_ops,
 240        .policy         = nft_fwd_netdev_policy,
 241        .maxattr        = NFTA_FWD_MAX,
 242        .owner          = THIS_MODULE,
 243};
 244
 245static int __init nft_fwd_netdev_module_init(void)
 246{
 247        return nft_register_expr(&nft_fwd_netdev_type);
 248}
 249
 250static void __exit nft_fwd_netdev_module_exit(void)
 251{
 252        nft_unregister_expr(&nft_fwd_netdev_type);
 253}
 254
 255module_init(nft_fwd_netdev_module_init);
 256module_exit(nft_fwd_netdev_module_exit);
 257
 258MODULE_LICENSE("GPL");
 259MODULE_AUTHOR("Pablo Neira Ayuso <pablo@netfilter.org>");
 260MODULE_ALIAS_NFT_AF_EXPR(5, "fwd");
 261