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