linux/net/ipv6/netfilter/nft_fib_ipv6.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2
   3#include <linux/kernel.h>
   4#include <linux/init.h>
   5#include <linux/module.h>
   6#include <linux/netlink.h>
   7#include <linux/netfilter.h>
   8#include <linux/netfilter/nf_tables.h>
   9#include <linux/netfilter_ipv6.h>
  10#include <net/netfilter/nf_tables_core.h>
  11#include <net/netfilter/nf_tables.h>
  12#include <net/netfilter/nft_fib.h>
  13
  14#include <net/ip6_fib.h>
  15#include <net/ip6_route.h>
  16
  17static int get_ifindex(const struct net_device *dev)
  18{
  19        return dev ? dev->ifindex : 0;
  20}
  21
  22static int nft_fib6_flowi_init(struct flowi6 *fl6, const struct nft_fib *priv,
  23                               const struct nft_pktinfo *pkt,
  24                               const struct net_device *dev,
  25                               struct ipv6hdr *iph)
  26{
  27        int lookup_flags = 0;
  28
  29        if (priv->flags & NFTA_FIB_F_DADDR) {
  30                fl6->daddr = iph->daddr;
  31                fl6->saddr = iph->saddr;
  32        } else {
  33                fl6->daddr = iph->saddr;
  34                fl6->saddr = iph->daddr;
  35        }
  36
  37        if (ipv6_addr_type(&fl6->daddr) & IPV6_ADDR_LINKLOCAL) {
  38                lookup_flags |= RT6_LOOKUP_F_IFACE;
  39                fl6->flowi6_oif = get_ifindex(dev ? dev : pkt->skb->dev);
  40        }
  41
  42        if (ipv6_addr_type(&fl6->saddr) & IPV6_ADDR_UNICAST)
  43                lookup_flags |= RT6_LOOKUP_F_HAS_SADDR;
  44
  45        if (priv->flags & NFTA_FIB_F_MARK)
  46                fl6->flowi6_mark = pkt->skb->mark;
  47
  48        fl6->flowlabel = (*(__be32 *)iph) & IPV6_FLOWINFO_MASK;
  49
  50        return lookup_flags;
  51}
  52
  53static u32 __nft_fib6_eval_type(const struct nft_fib *priv,
  54                                const struct nft_pktinfo *pkt,
  55                                struct ipv6hdr *iph)
  56{
  57        const struct net_device *dev = NULL;
  58        int route_err, addrtype;
  59        struct rt6_info *rt;
  60        struct flowi6 fl6 = {
  61                .flowi6_iif = LOOPBACK_IFINDEX,
  62                .flowi6_proto = pkt->tprot,
  63        };
  64        u32 ret = 0;
  65
  66        if (priv->flags & NFTA_FIB_F_IIF)
  67                dev = nft_in(pkt);
  68        else if (priv->flags & NFTA_FIB_F_OIF)
  69                dev = nft_out(pkt);
  70
  71        nft_fib6_flowi_init(&fl6, priv, pkt, dev, iph);
  72
  73        if (dev && nf_ipv6_chk_addr(nft_net(pkt), &fl6.daddr, dev, true))
  74                ret = RTN_LOCAL;
  75
  76        route_err = nf_ip6_route(nft_net(pkt), (struct dst_entry **)&rt,
  77                                 flowi6_to_flowi(&fl6), false);
  78        if (route_err)
  79                goto err;
  80
  81        if (rt->rt6i_flags & RTF_REJECT) {
  82                route_err = rt->dst.error;
  83                dst_release(&rt->dst);
  84                goto err;
  85        }
  86
  87        if (ipv6_anycast_destination((struct dst_entry *)rt, &fl6.daddr))
  88                ret = RTN_ANYCAST;
  89        else if (!dev && rt->rt6i_flags & RTF_LOCAL)
  90                ret = RTN_LOCAL;
  91
  92        dst_release(&rt->dst);
  93
  94        if (ret)
  95                return ret;
  96
  97        addrtype = ipv6_addr_type(&fl6.daddr);
  98
  99        if (addrtype & IPV6_ADDR_MULTICAST)
 100                return RTN_MULTICAST;
 101        if (addrtype & IPV6_ADDR_UNICAST)
 102                return RTN_UNICAST;
 103
 104        return RTN_UNSPEC;
 105 err:
 106        switch (route_err) {
 107        case -EINVAL:
 108                return RTN_BLACKHOLE;
 109        case -EACCES:
 110                return RTN_PROHIBIT;
 111        case -EAGAIN:
 112                return RTN_THROW;
 113        default:
 114                break;
 115        }
 116
 117        return RTN_UNREACHABLE;
 118}
 119
 120void nft_fib6_eval_type(const struct nft_expr *expr, struct nft_regs *regs,
 121                        const struct nft_pktinfo *pkt)
 122{
 123        const struct nft_fib *priv = nft_expr_priv(expr);
 124        int noff = skb_network_offset(pkt->skb);
 125        u32 *dest = &regs->data[priv->dreg];
 126        struct ipv6hdr *iph, _iph;
 127
 128        iph = skb_header_pointer(pkt->skb, noff, sizeof(_iph), &_iph);
 129        if (!iph) {
 130                regs->verdict.code = NFT_BREAK;
 131                return;
 132        }
 133
 134        *dest = __nft_fib6_eval_type(priv, pkt, iph);
 135}
 136EXPORT_SYMBOL_GPL(nft_fib6_eval_type);
 137
 138static bool nft_fib_v6_skip_icmpv6(const struct sk_buff *skb, u8 next, const struct ipv6hdr *iph)
 139{
 140        if (likely(next != IPPROTO_ICMPV6))
 141                return false;
 142
 143        if (ipv6_addr_type(&iph->saddr) != IPV6_ADDR_ANY)
 144                return false;
 145
 146        return ipv6_addr_type(&iph->daddr) & IPV6_ADDR_LINKLOCAL;
 147}
 148
 149void nft_fib6_eval(const struct nft_expr *expr, struct nft_regs *regs,
 150                   const struct nft_pktinfo *pkt)
 151{
 152        const struct nft_fib *priv = nft_expr_priv(expr);
 153        int noff = skb_network_offset(pkt->skb);
 154        const struct net_device *oif = NULL;
 155        u32 *dest = &regs->data[priv->dreg];
 156        struct ipv6hdr *iph, _iph;
 157        struct flowi6 fl6 = {
 158                .flowi6_iif = LOOPBACK_IFINDEX,
 159                .flowi6_proto = pkt->tprot,
 160        };
 161        struct rt6_info *rt;
 162        int lookup_flags;
 163
 164        if (priv->flags & NFTA_FIB_F_IIF)
 165                oif = nft_in(pkt);
 166        else if (priv->flags & NFTA_FIB_F_OIF)
 167                oif = nft_out(pkt);
 168
 169        iph = skb_header_pointer(pkt->skb, noff, sizeof(_iph), &_iph);
 170        if (!iph) {
 171                regs->verdict.code = NFT_BREAK;
 172                return;
 173        }
 174
 175        lookup_flags = nft_fib6_flowi_init(&fl6, priv, pkt, oif, iph);
 176
 177        if (nft_hook(pkt) == NF_INET_PRE_ROUTING ||
 178            nft_hook(pkt) == NF_INET_INGRESS) {
 179                if (nft_fib_is_loopback(pkt->skb, nft_in(pkt)) ||
 180                    nft_fib_v6_skip_icmpv6(pkt->skb, pkt->tprot, iph)) {
 181                        nft_fib_store_result(dest, priv, nft_in(pkt));
 182                        return;
 183                }
 184        }
 185
 186        *dest = 0;
 187        rt = (void *)ip6_route_lookup(nft_net(pkt), &fl6, pkt->skb,
 188                                      lookup_flags);
 189        if (rt->dst.error)
 190                goto put_rt_err;
 191
 192        /* Should not see RTF_LOCAL here */
 193        if (rt->rt6i_flags & (RTF_REJECT | RTF_ANYCAST | RTF_LOCAL))
 194                goto put_rt_err;
 195
 196        if (oif && oif != rt->rt6i_idev->dev)
 197                goto put_rt_err;
 198
 199        nft_fib_store_result(dest, priv, rt->rt6i_idev->dev);
 200 put_rt_err:
 201        ip6_rt_put(rt);
 202}
 203EXPORT_SYMBOL_GPL(nft_fib6_eval);
 204
 205static struct nft_expr_type nft_fib6_type;
 206
 207static const struct nft_expr_ops nft_fib6_type_ops = {
 208        .type           = &nft_fib6_type,
 209        .size           = NFT_EXPR_SIZE(sizeof(struct nft_fib)),
 210        .eval           = nft_fib6_eval_type,
 211        .init           = nft_fib_init,
 212        .dump           = nft_fib_dump,
 213        .validate       = nft_fib_validate,
 214        .reduce         = nft_fib_reduce,
 215};
 216
 217static const struct nft_expr_ops nft_fib6_ops = {
 218        .type           = &nft_fib6_type,
 219        .size           = NFT_EXPR_SIZE(sizeof(struct nft_fib)),
 220        .eval           = nft_fib6_eval,
 221        .init           = nft_fib_init,
 222        .dump           = nft_fib_dump,
 223        .validate       = nft_fib_validate,
 224        .reduce         = nft_fib_reduce,
 225};
 226
 227static const struct nft_expr_ops *
 228nft_fib6_select_ops(const struct nft_ctx *ctx,
 229                    const struct nlattr * const tb[])
 230{
 231        enum nft_fib_result result;
 232
 233        if (!tb[NFTA_FIB_RESULT])
 234                return ERR_PTR(-EINVAL);
 235
 236        result = ntohl(nla_get_be32(tb[NFTA_FIB_RESULT]));
 237
 238        switch (result) {
 239        case NFT_FIB_RESULT_OIF:
 240                return &nft_fib6_ops;
 241        case NFT_FIB_RESULT_OIFNAME:
 242                return &nft_fib6_ops;
 243        case NFT_FIB_RESULT_ADDRTYPE:
 244                return &nft_fib6_type_ops;
 245        default:
 246                return ERR_PTR(-EOPNOTSUPP);
 247        }
 248}
 249
 250static struct nft_expr_type nft_fib6_type __read_mostly = {
 251        .name           = "fib",
 252        .select_ops     = nft_fib6_select_ops,
 253        .policy         = nft_fib_policy,
 254        .maxattr        = NFTA_FIB_MAX,
 255        .family         = NFPROTO_IPV6,
 256        .owner          = THIS_MODULE,
 257};
 258
 259static int __init nft_fib6_module_init(void)
 260{
 261        return nft_register_expr(&nft_fib6_type);
 262}
 263
 264static void __exit nft_fib6_module_exit(void)
 265{
 266        nft_unregister_expr(&nft_fib6_type);
 267}
 268module_init(nft_fib6_module_init);
 269module_exit(nft_fib6_module_exit);
 270
 271MODULE_LICENSE("GPL");
 272MODULE_AUTHOR("Florian Westphal <fw@strlen.de>");
 273MODULE_ALIAS_NFT_AF_EXPR(10, "fib");
 274MODULE_DESCRIPTION("nftables fib / ipv6 route lookup support");
 275