alistair23-linux/net/ipv6/netfilter/nft_fib_ipv6.c
Pablo M. Bermudo Garay f347ec852c netfilter: nf_tables: fib: use skb_header_pointer
This is a preparatory patch for adding fib support to the netdev family.

The netdev family receives the packets from ingress hook. At this point
we have no guarantee that the ip header is linear. So this patch
replaces ip_hdr with skb_header_pointer in order to address that
possible situation.

Signed-off-by: Pablo M. Bermudo Garay <pablombg@gmail.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
2017-07-31 19:01:39 +02:00

288 lines
6.7 KiB
C

/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/netlink.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nf_tables.h>
#include <linux/netfilter_ipv6.h>
#include <net/netfilter/nf_tables_core.h>
#include <net/netfilter/nf_tables.h>
#include <net/netfilter/nft_fib.h>
#include <net/ip6_fib.h>
#include <net/ip6_route.h>
static int get_ifindex(const struct net_device *dev)
{
return dev ? dev->ifindex : 0;
}
static int nft_fib6_flowi_init(struct flowi6 *fl6, const struct nft_fib *priv,
const struct nft_pktinfo *pkt,
const struct net_device *dev,
struct ipv6hdr *iph)
{
int lookup_flags = 0;
if (priv->flags & NFTA_FIB_F_DADDR) {
fl6->daddr = iph->daddr;
fl6->saddr = iph->saddr;
} else {
fl6->daddr = iph->saddr;
fl6->saddr = iph->daddr;
}
if (ipv6_addr_type(&fl6->daddr) & IPV6_ADDR_LINKLOCAL) {
lookup_flags |= RT6_LOOKUP_F_IFACE;
fl6->flowi6_oif = get_ifindex(dev ? dev : pkt->skb->dev);
}
if (ipv6_addr_type(&fl6->saddr) & IPV6_ADDR_UNICAST)
lookup_flags |= RT6_LOOKUP_F_HAS_SADDR;
if (priv->flags & NFTA_FIB_F_MARK)
fl6->flowi6_mark = pkt->skb->mark;
fl6->flowlabel = (*(__be32 *)iph) & IPV6_FLOWINFO_MASK;
return lookup_flags;
}
static u32 __nft_fib6_eval_type(const struct nft_fib *priv,
const struct nft_pktinfo *pkt,
struct ipv6hdr *iph)
{
const struct net_device *dev = NULL;
const struct nf_ipv6_ops *v6ops;
const struct nf_afinfo *afinfo;
int route_err, addrtype;
struct rt6_info *rt;
struct flowi6 fl6 = {
.flowi6_iif = LOOPBACK_IFINDEX,
.flowi6_proto = pkt->tprot,
};
u32 ret = 0;
afinfo = nf_get_afinfo(NFPROTO_IPV6);
if (!afinfo)
return RTN_UNREACHABLE;
if (priv->flags & NFTA_FIB_F_IIF)
dev = nft_in(pkt);
else if (priv->flags & NFTA_FIB_F_OIF)
dev = nft_out(pkt);
nft_fib6_flowi_init(&fl6, priv, pkt, dev, iph);
v6ops = nf_get_ipv6_ops();
if (dev && v6ops && v6ops->chk_addr(nft_net(pkt), &fl6.daddr, dev, true))
ret = RTN_LOCAL;
route_err = afinfo->route(nft_net(pkt), (struct dst_entry **)&rt,
flowi6_to_flowi(&fl6), false);
if (route_err)
goto err;
if (rt->rt6i_flags & RTF_REJECT) {
route_err = rt->dst.error;
dst_release(&rt->dst);
goto err;
}
if (ipv6_anycast_destination((struct dst_entry *)rt, &fl6.daddr))
ret = RTN_ANYCAST;
else if (!dev && rt->rt6i_flags & RTF_LOCAL)
ret = RTN_LOCAL;
dst_release(&rt->dst);
if (ret)
return ret;
addrtype = ipv6_addr_type(&fl6.daddr);
if (addrtype & IPV6_ADDR_MULTICAST)
return RTN_MULTICAST;
if (addrtype & IPV6_ADDR_UNICAST)
return RTN_UNICAST;
return RTN_UNSPEC;
err:
switch (route_err) {
case -EINVAL:
return RTN_BLACKHOLE;
case -EACCES:
return RTN_PROHIBIT;
case -EAGAIN:
return RTN_THROW;
default:
break;
}
return RTN_UNREACHABLE;
}
void nft_fib6_eval_type(const struct nft_expr *expr, struct nft_regs *regs,
const struct nft_pktinfo *pkt)
{
const struct nft_fib *priv = nft_expr_priv(expr);
int noff = skb_network_offset(pkt->skb);
u32 *dest = &regs->data[priv->dreg];
struct ipv6hdr *iph, _iph;
iph = skb_header_pointer(pkt->skb, noff, sizeof(_iph), &_iph);
if (!iph) {
regs->verdict.code = NFT_BREAK;
return;
}
*dest = __nft_fib6_eval_type(priv, pkt, iph);
}
EXPORT_SYMBOL_GPL(nft_fib6_eval_type);
void nft_fib6_eval(const struct nft_expr *expr, struct nft_regs *regs,
const struct nft_pktinfo *pkt)
{
const struct nft_fib *priv = nft_expr_priv(expr);
int noff = skb_network_offset(pkt->skb);
const struct net_device *oif = NULL;
u32 *dest = &regs->data[priv->dreg];
struct ipv6hdr *iph, _iph;
struct flowi6 fl6 = {
.flowi6_iif = LOOPBACK_IFINDEX,
.flowi6_proto = pkt->tprot,
};
struct rt6_info *rt;
int lookup_flags;
if (priv->flags & NFTA_FIB_F_IIF)
oif = nft_in(pkt);
else if (priv->flags & NFTA_FIB_F_OIF)
oif = nft_out(pkt);
iph = skb_header_pointer(pkt->skb, noff, sizeof(_iph), &_iph);
if (!iph) {
regs->verdict.code = NFT_BREAK;
return;
}
lookup_flags = nft_fib6_flowi_init(&fl6, priv, pkt, oif, iph);
if (nft_hook(pkt) == NF_INET_PRE_ROUTING &&
nft_fib_is_loopback(pkt->skb, nft_in(pkt))) {
nft_fib_store_result(dest, priv, pkt,
nft_in(pkt)->ifindex);
return;
}
*dest = 0;
again:
rt = (void *)ip6_route_lookup(nft_net(pkt), &fl6, lookup_flags);
if (rt->dst.error)
goto put_rt_err;
/* Should not see RTF_LOCAL here */
if (rt->rt6i_flags & (RTF_REJECT | RTF_ANYCAST | RTF_LOCAL))
goto put_rt_err;
if (oif && oif != rt->rt6i_idev->dev) {
/* multipath route? Try again with F_IFACE */
if ((lookup_flags & RT6_LOOKUP_F_IFACE) == 0) {
lookup_flags |= RT6_LOOKUP_F_IFACE;
fl6.flowi6_oif = oif->ifindex;
ip6_rt_put(rt);
goto again;
}
}
switch (priv->result) {
case NFT_FIB_RESULT_OIF:
*dest = rt->rt6i_idev->dev->ifindex;
break;
case NFT_FIB_RESULT_OIFNAME:
strncpy((char *)dest, rt->rt6i_idev->dev->name, IFNAMSIZ);
break;
default:
WARN_ON_ONCE(1);
break;
}
put_rt_err:
ip6_rt_put(rt);
}
EXPORT_SYMBOL_GPL(nft_fib6_eval);
static struct nft_expr_type nft_fib6_type;
static const struct nft_expr_ops nft_fib6_type_ops = {
.type = &nft_fib6_type,
.size = NFT_EXPR_SIZE(sizeof(struct nft_fib)),
.eval = nft_fib6_eval_type,
.init = nft_fib_init,
.dump = nft_fib_dump,
.validate = nft_fib_validate,
};
static const struct nft_expr_ops nft_fib6_ops = {
.type = &nft_fib6_type,
.size = NFT_EXPR_SIZE(sizeof(struct nft_fib)),
.eval = nft_fib6_eval,
.init = nft_fib_init,
.dump = nft_fib_dump,
.validate = nft_fib_validate,
};
static const struct nft_expr_ops *
nft_fib6_select_ops(const struct nft_ctx *ctx,
const struct nlattr * const tb[])
{
enum nft_fib_result result;
if (!tb[NFTA_FIB_RESULT])
return ERR_PTR(-EINVAL);
result = ntohl(nla_get_be32(tb[NFTA_FIB_RESULT]));
switch (result) {
case NFT_FIB_RESULT_OIF:
return &nft_fib6_ops;
case NFT_FIB_RESULT_OIFNAME:
return &nft_fib6_ops;
case NFT_FIB_RESULT_ADDRTYPE:
return &nft_fib6_type_ops;
default:
return ERR_PTR(-EOPNOTSUPP);
}
}
static struct nft_expr_type nft_fib6_type __read_mostly = {
.name = "fib",
.select_ops = nft_fib6_select_ops,
.policy = nft_fib_policy,
.maxattr = NFTA_FIB_MAX,
.family = NFPROTO_IPV6,
.owner = THIS_MODULE,
};
static int __init nft_fib6_module_init(void)
{
return nft_register_expr(&nft_fib6_type);
}
static void __exit nft_fib6_module_exit(void)
{
nft_unregister_expr(&nft_fib6_type);
}
module_init(nft_fib6_module_init);
module_exit(nft_fib6_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Florian Westphal <fw@strlen.de>");
MODULE_ALIAS_NFT_AF_EXPR(10, "fib");