Commit f88d8ea6 authored by David Ahern's avatar David Ahern Committed by David S. Miller

ipv6: Plumb support for nexthop object in a fib6_info

Add struct nexthop and nh_list list_head to fib6_info. nh_list is the
fib6_info side of the nexthop <-> fib_info relationship. Since a fib6_info
referencing a nexthop object can not have 'sibling' entries (the old way
of doing multipath routes), the nh_list is a union with fib6_siblings.

Add f6i_list list_head to 'struct nexthop' to track fib6_info entries
using a nexthop instance. Update __remove_nexthop_fib to walk f6_list
and delete fib entries using the nexthop.

Add a few nexthop helpers for use when a nexthop is added to fib6_info:
- nexthop_fib6_nh - return first fib6_nh in a nexthop object
- fib6_info_nh_dev moved to nexthop.h and updated to use nexthop_fib6_nh
  if the fib6_info references a nexthop object
- nexthop_path_fib6_result - similar to ipv4, select a path within a
  multipath nexthop object. If the nexthop is a blackhole, set
  fib6_result type to RTN_BLACKHOLE, and set the REJECT flag

Update the fib6_info references to check for nh and take a different path
as needed:
- rt6_qualify_for_ecmp - if a fib entry uses a nexthop object it can NOT
  be coalesced with other fib entries into a multipath route
- rt6_duplicate_nexthop - use nexthop_cmp if either fib6_info references
  a nexthop
- addrconf (host routes), RA's and info entries (anything configured via
  ndisc) does not use nexthop objects
- fib6_info_destroy_rcu - put reference to nexthop object
- fib6_purge_rt - drop fib6_info from f6i_list
- fib6_select_path - update to use the new nexthop_path_fib6_result when
  fib entry uses a nexthop object
- rt6_device_match - update to catch use of nexthop object as a blackhole
  and set fib6_type and flags.
- ip6_route_info_create - don't add space for fib6_nh if fib entry is
  going to reference a nexthop object, take a reference to nexthop object,
  disallow use of source routing
- rt6_nlmsg_size - add space for RTA_NH_ID
- add rt6_fill_node_nexthop to add nexthop data on a dump

As with ipv4, most of the changes push existing code into the else branch
of whether the fib entry uses a nexthop object.

Update the nexthop code to walk f6i_list on a nexthop deleted to remove
fib entries referencing it.
Signed-off-by: default avatarDavid Ahern <dsahern@gmail.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 4c7e8084
...@@ -146,7 +146,10 @@ struct fib6_info { ...@@ -146,7 +146,10 @@ struct fib6_info {
* destination, but not the same gateway. nsiblings is just a cache * destination, but not the same gateway. nsiblings is just a cache
* to speed up lookup. * to speed up lookup.
*/ */
union {
struct list_head fib6_siblings; struct list_head fib6_siblings;
struct list_head nh_list;
};
unsigned int fib6_nsiblings; unsigned int fib6_nsiblings;
refcount_t fib6_ref; refcount_t fib6_ref;
...@@ -170,6 +173,7 @@ struct fib6_info { ...@@ -170,6 +173,7 @@ struct fib6_info {
unused:3; unused:3;
struct rcu_head rcu; struct rcu_head rcu;
struct nexthop *nh;
struct fib6_nh fib6_nh[0]; struct fib6_nh fib6_nh[0];
}; };
...@@ -441,11 +445,6 @@ void rt6_get_prefsrc(const struct rt6_info *rt, struct in6_addr *addr) ...@@ -441,11 +445,6 @@ void rt6_get_prefsrc(const struct rt6_info *rt, struct in6_addr *addr)
rcu_read_unlock(); rcu_read_unlock();
} }
static inline struct net_device *fib6_info_nh_dev(const struct fib6_info *f6i)
{
return f6i->fib6_nh->fib_nh_dev;
}
int fib6_nh_init(struct net *net, struct fib6_nh *fib6_nh, int fib6_nh_init(struct net *net, struct fib6_nh *fib6_nh,
struct fib6_config *cfg, gfp_t gfp_flags, struct fib6_config *cfg, gfp_t gfp_flags,
struct netlink_ext_ack *extack); struct netlink_ext_ack *extack);
......
...@@ -27,6 +27,7 @@ struct route_info { ...@@ -27,6 +27,7 @@ struct route_info {
#include <linux/ip.h> #include <linux/ip.h>
#include <linux/ipv6.h> #include <linux/ipv6.h>
#include <linux/route.h> #include <linux/route.h>
#include <net/nexthop.h>
#define RT6_LOOKUP_F_IFACE 0x00000001 #define RT6_LOOKUP_F_IFACE 0x00000001
#define RT6_LOOKUP_F_REACHABLE 0x00000002 #define RT6_LOOKUP_F_REACHABLE 0x00000002
...@@ -66,10 +67,13 @@ static inline bool rt6_need_strict(const struct in6_addr *daddr) ...@@ -66,10 +67,13 @@ static inline bool rt6_need_strict(const struct in6_addr *daddr)
(IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL | IPV6_ADDR_LOOPBACK); (IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL | IPV6_ADDR_LOOPBACK);
} }
/* fib entries using a nexthop object can not be coalesced into
* a multipath route
*/
static inline bool rt6_qualify_for_ecmp(const struct fib6_info *f6i) static inline bool rt6_qualify_for_ecmp(const struct fib6_info *f6i)
{ {
/* the RTF_ADDRCONF flag filters out RA's */ /* the RTF_ADDRCONF flag filters out RA's */
return !(f6i->fib6_flags & RTF_ADDRCONF) && return !(f6i->fib6_flags & RTF_ADDRCONF) && !f6i->nh &&
f6i->fib6_nh->fib_nh_gw_family; f6i->fib6_nh->fib_nh_gw_family;
} }
...@@ -275,8 +279,13 @@ static inline struct in6_addr *rt6_nexthop(struct rt6_info *rt, ...@@ -275,8 +279,13 @@ static inline struct in6_addr *rt6_nexthop(struct rt6_info *rt,
static inline bool rt6_duplicate_nexthop(struct fib6_info *a, struct fib6_info *b) static inline bool rt6_duplicate_nexthop(struct fib6_info *a, struct fib6_info *b)
{ {
struct fib6_nh *nha = a->fib6_nh, *nhb = b->fib6_nh; struct fib6_nh *nha, *nhb;
if (a->nh || b->nh)
return nexthop_cmp(a->nh, b->nh);
nha = a->fib6_nh;
nhb = b->fib6_nh;
return nha->fib_nh_dev == nhb->fib_nh_dev && return nha->fib_nh_dev == nhb->fib_nh_dev &&
ipv6_addr_equal(&nha->fib_nh_gw6, &nhb->fib_nh_gw6) && ipv6_addr_equal(&nha->fib_nh_gw6, &nhb->fib_nh_gw6) &&
!lwtunnel_cmp_encap(nha->fib_nh_lws, nhb->fib_nh_lws); !lwtunnel_cmp_encap(nha->fib_nh_lws, nhb->fib_nh_lws);
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#define __LINUX_NEXTHOP_H #define __LINUX_NEXTHOP_H
#include <linux/netdevice.h> #include <linux/netdevice.h>
#include <linux/route.h>
#include <linux/types.h> #include <linux/types.h>
#include <net/ip_fib.h> #include <net/ip_fib.h>
#include <net/ip6_fib.h> #include <net/ip6_fib.h>
...@@ -78,6 +79,7 @@ struct nh_group { ...@@ -78,6 +79,7 @@ struct nh_group {
struct nexthop { struct nexthop {
struct rb_node rb_node; /* entry on netns rbtree */ struct rb_node rb_node; /* entry on netns rbtree */
struct list_head fi_list; /* v4 entries using nh */ struct list_head fi_list; /* v4 entries using nh */
struct list_head f6i_list; /* v6 entries using nh */
struct list_head grp_list; /* nh group entries using this nh */ struct list_head grp_list; /* nh group entries using this nh */
struct net *net; struct net *net;
...@@ -255,4 +257,52 @@ static inline struct fib_nh *fib_info_nh(struct fib_info *fi, int nhsel) ...@@ -255,4 +257,52 @@ static inline struct fib_nh *fib_info_nh(struct fib_info *fi, int nhsel)
return &fi->fib_nh[nhsel]; return &fi->fib_nh[nhsel];
} }
/*
* IPv6 variants
*/
int fib6_check_nexthop(struct nexthop *nh, struct fib6_config *cfg,
struct netlink_ext_ack *extack);
static inline struct fib6_nh *nexthop_fib6_nh(struct nexthop *nh)
{
struct nh_info *nhi;
if (nexthop_is_multipath(nh)) {
nh = nexthop_mpath_select(nh, 0);
if (!nh)
return NULL;
}
nhi = rcu_dereference_rtnl(nh->nh_info);
if (nhi->family == AF_INET6)
return &nhi->fib6_nh;
return NULL;
}
static inline struct net_device *fib6_info_nh_dev(struct fib6_info *f6i)
{
struct fib6_nh *fib6_nh;
fib6_nh = f6i->nh ? nexthop_fib6_nh(f6i->nh) : f6i->fib6_nh;
return fib6_nh->fib_nh_dev;
}
static inline void nexthop_path_fib6_result(struct fib6_result *res, int hash)
{
struct nexthop *nh = res->f6i->nh;
struct nh_info *nhi;
nh = nexthop_select_path(nh, hash);
nhi = rcu_dereference_rtnl(nh->nh_info);
if (nhi->reject_nh) {
res->fib6_type = RTN_BLACKHOLE;
res->fib6_flags |= RTF_REJECT;
res->nh = nexthop_fib6_nh(nh);
} else {
res->nh = &nhi->fib6_nh;
}
}
#endif #endif
...@@ -106,6 +106,7 @@ static struct nexthop *nexthop_alloc(void) ...@@ -106,6 +106,7 @@ static struct nexthop *nexthop_alloc(void)
nh = kzalloc(sizeof(struct nexthop), GFP_KERNEL); nh = kzalloc(sizeof(struct nexthop), GFP_KERNEL);
if (nh) { if (nh) {
INIT_LIST_HEAD(&nh->fi_list); INIT_LIST_HEAD(&nh->fi_list);
INIT_LIST_HEAD(&nh->f6i_list);
INIT_LIST_HEAD(&nh->grp_list); INIT_LIST_HEAD(&nh->grp_list);
} }
return nh; return nh;
...@@ -516,6 +517,41 @@ struct nexthop *nexthop_select_path(struct nexthop *nh, int hash) ...@@ -516,6 +517,41 @@ struct nexthop *nexthop_select_path(struct nexthop *nh, int hash)
} }
EXPORT_SYMBOL_GPL(nexthop_select_path); EXPORT_SYMBOL_GPL(nexthop_select_path);
int fib6_check_nexthop(struct nexthop *nh, struct fib6_config *cfg,
struct netlink_ext_ack *extack)
{
struct nh_info *nhi;
/* fib6_src is unique to a fib6_info and limits the ability to cache
* routes in fib6_nh within a nexthop that is potentially shared
* across multiple fib entries. If the config wants to use source
* routing it can not use nexthop objects. mlxsw also does not allow
* fib6_src on routes.
*/
if (!ipv6_addr_any(&cfg->fc_src)) {
NL_SET_ERR_MSG(extack, "IPv6 routes using source address can not use nexthop objects");
return -EINVAL;
}
if (nh->is_group) {
struct nh_group *nhg;
nhg = rtnl_dereference(nh->nh_grp);
if (nhg->has_v4)
goto no_v4_nh;
} else {
nhi = rtnl_dereference(nh->nh_info);
if (nhi->family == AF_INET)
goto no_v4_nh;
}
return 0;
no_v4_nh:
NL_SET_ERR_MSG(extack, "IPv6 routes can not use an IPv4 nexthop");
return -EINVAL;
}
EXPORT_SYMBOL_GPL(fib6_check_nexthop);
static int nexthop_check_scope(struct nexthop *nh, u8 scope, static int nexthop_check_scope(struct nexthop *nh, u8 scope,
struct netlink_ext_ack *extack) struct netlink_ext_ack *extack)
{ {
...@@ -658,6 +694,7 @@ static void remove_nexthop_group(struct nexthop *nh, struct nl_info *nlinfo) ...@@ -658,6 +694,7 @@ static void remove_nexthop_group(struct nexthop *nh, struct nl_info *nlinfo)
static void __remove_nexthop_fib(struct net *net, struct nexthop *nh) static void __remove_nexthop_fib(struct net *net, struct nexthop *nh)
{ {
struct fib6_info *f6i, *tmp;
bool do_flush = false; bool do_flush = false;
struct fib_info *fi; struct fib_info *fi;
...@@ -667,6 +704,13 @@ static void __remove_nexthop_fib(struct net *net, struct nexthop *nh) ...@@ -667,6 +704,13 @@ static void __remove_nexthop_fib(struct net *net, struct nexthop *nh)
} }
if (do_flush) if (do_flush)
fib_flush(net); fib_flush(net);
/* ip6_del_rt removes the entry from this list hence the _safe */
list_for_each_entry_safe(f6i, tmp, &nh->f6i_list, nh_list) {
/* __ip6_del_rt does a release, so do a hold here */
fib6_info_hold(f6i);
ipv6_stub->ip6_del_rt(net, f6i);
}
} }
static void __remove_nexthop(struct net *net, struct nexthop *nh, static void __remove_nexthop(struct net *net, struct nexthop *nh,
......
...@@ -2421,6 +2421,10 @@ static struct fib6_info *addrconf_get_prefix_route(const struct in6_addr *pfx, ...@@ -2421,6 +2421,10 @@ static struct fib6_info *addrconf_get_prefix_route(const struct in6_addr *pfx,
goto out; goto out;
for_each_fib6_node_rt_rcu(fn) { for_each_fib6_node_rt_rcu(fn) {
/* prefix routes only use builtin fib6_nh */
if (rt->nh)
continue;
if (rt->fib6_nh->fib_nh_dev->ifindex != dev->ifindex) if (rt->fib6_nh->fib_nh_dev->ifindex != dev->ifindex)
continue; continue;
if (no_gw && rt->fib6_nh->fib_nh_gw_family) if (no_gw && rt->fib6_nh->fib_nh_gw_family)
...@@ -6352,6 +6356,7 @@ void addrconf_disable_policy_idev(struct inet6_dev *idev, int val) ...@@ -6352,6 +6356,7 @@ void addrconf_disable_policy_idev(struct inet6_dev *idev, int val)
list_for_each_entry(ifa, &idev->addr_list, if_list) { list_for_each_entry(ifa, &idev->addr_list, if_list) {
spin_lock(&ifa->lock); spin_lock(&ifa->lock);
if (ifa->rt) { if (ifa->rt) {
/* host routes only use builtin fib6_nh */
struct fib6_nh *nh = ifa->rt->fib6_nh; struct fib6_nh *nh = ifa->rt->fib6_nh;
int cpu; int cpu;
......
...@@ -159,6 +159,7 @@ struct fib6_info *fib6_info_alloc(gfp_t gfp_flags, bool with_fib6_nh) ...@@ -159,6 +159,7 @@ struct fib6_info *fib6_info_alloc(gfp_t gfp_flags, bool with_fib6_nh)
if (!f6i) if (!f6i)
return NULL; return NULL;
/* fib6_siblings is a union with nh_list, so this initializes both */
INIT_LIST_HEAD(&f6i->fib6_siblings); INIT_LIST_HEAD(&f6i->fib6_siblings);
refcount_set(&f6i->fib6_ref, 1); refcount_set(&f6i->fib6_ref, 1);
...@@ -171,7 +172,11 @@ void fib6_info_destroy_rcu(struct rcu_head *head) ...@@ -171,7 +172,11 @@ void fib6_info_destroy_rcu(struct rcu_head *head)
WARN_ON(f6i->fib6_node); WARN_ON(f6i->fib6_node);
if (f6i->nh)
nexthop_put(f6i->nh);
else
fib6_nh_release(f6i->fib6_nh); fib6_nh_release(f6i->fib6_nh);
ip_fib_metrics_put(f6i->fib6_metrics); ip_fib_metrics_put(f6i->fib6_metrics);
kfree(f6i); kfree(f6i);
} }
...@@ -927,6 +932,9 @@ static void fib6_purge_rt(struct fib6_info *rt, struct fib6_node *fn, ...@@ -927,6 +932,9 @@ static void fib6_purge_rt(struct fib6_info *rt, struct fib6_node *fn,
fib6_drop_pcpu_from(rt, table); fib6_drop_pcpu_from(rt, table);
if (rt->nh && !list_empty(&rt->nh_list))
list_del_init(&rt->nh_list);
if (refcount_read(&rt->fib6_ref) != 1) { if (refcount_read(&rt->fib6_ref) != 1) {
/* This route is used as dummy address holder in some split /* This route is used as dummy address holder in some split
* nodes. It is not leaked, but it still holds other resources, * nodes. It is not leaked, but it still holds other resources,
...@@ -1334,6 +1342,8 @@ int fib6_add(struct fib6_node *root, struct fib6_info *rt, ...@@ -1334,6 +1342,8 @@ int fib6_add(struct fib6_node *root, struct fib6_info *rt,
err = fib6_add_rt2node(fn, rt, info, extack); err = fib6_add_rt2node(fn, rt, info, extack);
if (!err) { if (!err) {
if (rt->nh)
list_add(&rt->nh_list, &rt->nh->f6i_list);
__fib6_update_sernum_upto_root(rt, sernum); __fib6_update_sernum_upto_root(rt, sernum);
fib6_start_gc(info->nl_net, rt); fib6_start_gc(info->nl_net, rt);
} }
...@@ -2295,9 +2305,13 @@ static int ipv6_route_seq_show(struct seq_file *seq, void *v) ...@@ -2295,9 +2305,13 @@ static int ipv6_route_seq_show(struct seq_file *seq, void *v)
{ {
struct fib6_info *rt = v; struct fib6_info *rt = v;
struct ipv6_route_iter *iter = seq->private; struct ipv6_route_iter *iter = seq->private;
struct fib6_nh *fib6_nh = rt->fib6_nh;
unsigned int flags = rt->fib6_flags; unsigned int flags = rt->fib6_flags;
const struct net_device *dev; const struct net_device *dev;
if (rt->nh)
fib6_nh = nexthop_fib6_nh(rt->nh);
seq_printf(seq, "%pi6 %02x ", &rt->fib6_dst.addr, rt->fib6_dst.plen); seq_printf(seq, "%pi6 %02x ", &rt->fib6_dst.addr, rt->fib6_dst.plen);
#ifdef CONFIG_IPV6_SUBTREES #ifdef CONFIG_IPV6_SUBTREES
...@@ -2305,14 +2319,14 @@ static int ipv6_route_seq_show(struct seq_file *seq, void *v) ...@@ -2305,14 +2319,14 @@ static int ipv6_route_seq_show(struct seq_file *seq, void *v)
#else #else
seq_puts(seq, "00000000000000000000000000000000 00 "); seq_puts(seq, "00000000000000000000000000000000 00 ");
#endif #endif
if (rt->fib6_nh->fib_nh_gw_family) { if (fib6_nh->fib_nh_gw_family) {
flags |= RTF_GATEWAY; flags |= RTF_GATEWAY;
seq_printf(seq, "%pi6", &rt->fib6_nh->fib_nh_gw6); seq_printf(seq, "%pi6", &fib6_nh->fib_nh_gw6);
} else { } else {
seq_puts(seq, "00000000000000000000000000000000"); seq_puts(seq, "00000000000000000000000000000000");
} }
dev = rt->fib6_nh->fib_nh_dev; dev = fib6_nh->fib_nh_dev;
seq_printf(seq, " %08x %08x %08x %08x %8s\n", seq_printf(seq, " %08x %08x %08x %08x %8s\n",
rt->fib6_metric, refcount_read(&rt->fib6_ref), 0, rt->fib6_metric, refcount_read(&rt->fib6_ref), 0,
flags, dev ? dev->name : ""); flags, dev ? dev->name : "");
......
...@@ -1289,9 +1289,8 @@ static void ndisc_router_discovery(struct sk_buff *skb) ...@@ -1289,9 +1289,8 @@ static void ndisc_router_discovery(struct sk_buff *skb)
!in6_dev->cnf.accept_ra_rtr_pref) !in6_dev->cnf.accept_ra_rtr_pref)
pref = ICMPV6_ROUTER_PREF_MEDIUM; pref = ICMPV6_ROUTER_PREF_MEDIUM;
#endif #endif
/* routes added from RAs do not use nexthop objects */
rt = rt6_get_dflt_router(net, &ipv6_hdr(skb)->saddr, skb->dev); rt = rt6_get_dflt_router(net, &ipv6_hdr(skb)->saddr, skb->dev);
if (rt) { if (rt) {
neigh = ip6_neigh_lookup(&rt->fib6_nh->fib_nh_gw6, neigh = ip6_neigh_lookup(&rt->fib6_nh->fib_nh_gw6,
rt->fib6_nh->fib_nh_dev, NULL, rt->fib6_nh->fib_nh_dev, NULL,
......
...@@ -432,15 +432,21 @@ void fib6_select_path(const struct net *net, struct fib6_result *res, ...@@ -432,15 +432,21 @@ void fib6_select_path(const struct net *net, struct fib6_result *res,
struct fib6_info *sibling, *next_sibling; struct fib6_info *sibling, *next_sibling;
struct fib6_info *match = res->f6i; struct fib6_info *match = res->f6i;
if (!match->fib6_nsiblings || have_oif_match) if ((!match->fib6_nsiblings && !match->nh) || have_oif_match)
goto out; goto out;
/* We might have already computed the hash for ICMPv6 errors. In such /* We might have already computed the hash for ICMPv6 errors. In such
* case it will always be non-zero. Otherwise now is the time to do it. * case it will always be non-zero. Otherwise now is the time to do it.
*/ */
if (!fl6->mp_hash) if (!fl6->mp_hash &&
(!match->nh || nexthop_is_multipath(match->nh)))
fl6->mp_hash = rt6_multipath_hash(net, fl6, skb, NULL); fl6->mp_hash = rt6_multipath_hash(net, fl6, skb, NULL);
if (unlikely(match->nh)) {
nexthop_path_fib6_result(res, fl6->mp_hash);
return;
}
if (fl6->mp_hash <= atomic_read(&match->fib6_nh->fib_nh_upper_bound)) if (fl6->mp_hash <= atomic_read(&match->fib6_nh->fib_nh_upper_bound))
goto out; goto out;
...@@ -496,7 +502,13 @@ static void rt6_device_match(struct net *net, struct fib6_result *res, ...@@ -496,7 +502,13 @@ static void rt6_device_match(struct net *net, struct fib6_result *res,
struct fib6_nh *nh; struct fib6_nh *nh;
if (!oif && ipv6_addr_any(saddr)) { if (!oif && ipv6_addr_any(saddr)) {
if (unlikely(f6i->nh)) {
nh = nexthop_fib6_nh(f6i->nh);
if (nexthop_is_blackhole(f6i->nh))
goto out_blackhole;
} else {
nh = f6i->fib6_nh; nh = f6i->fib6_nh;
}
if (!(nh->fib_nh_flags & RTNH_F_DEAD)) if (!(nh->fib_nh_flags & RTNH_F_DEAD))
goto out; goto out;
} }
...@@ -515,7 +527,14 @@ static void rt6_device_match(struct net *net, struct fib6_result *res, ...@@ -515,7 +527,14 @@ static void rt6_device_match(struct net *net, struct fib6_result *res,
goto out; goto out;
} }
if (unlikely(f6i->nh)) {
nh = nexthop_fib6_nh(f6i->nh);
if (nexthop_is_blackhole(f6i->nh))
goto out_blackhole;
} else {
nh = f6i->fib6_nh; nh = f6i->fib6_nh;
}
if (nh->fib_nh_flags & RTNH_F_DEAD) { if (nh->fib_nh_flags & RTNH_F_DEAD) {
res->f6i = net->ipv6.fib6_null_entry; res->f6i = net->ipv6.fib6_null_entry;
nh = res->f6i->fib6_nh; nh = res->f6i->fib6_nh;
...@@ -524,6 +543,12 @@ static void rt6_device_match(struct net *net, struct fib6_result *res, ...@@ -524,6 +543,12 @@ static void rt6_device_match(struct net *net, struct fib6_result *res,
res->nh = nh; res->nh = nh;
res->fib6_type = res->f6i->fib6_type; res->fib6_type = res->f6i->fib6_type;
res->fib6_flags = res->f6i->fib6_flags; res->fib6_flags = res->f6i->fib6_flags;
return;
out_blackhole:
res->fib6_flags |= RTF_REJECT;
res->fib6_type = RTN_BLACKHOLE;
res->nh = nh;
} }
#ifdef CONFIG_IPV6_ROUTER_PREF #ifdef CONFIG_IPV6_ROUTER_PREF
...@@ -1117,6 +1142,8 @@ static struct rt6_info *ip6_pol_route_lookup(struct net *net, ...@@ -1117,6 +1142,8 @@ static struct rt6_info *ip6_pol_route_lookup(struct net *net,
rt = net->ipv6.ip6_null_entry; rt = net->ipv6.ip6_null_entry;
dst_hold(&rt->dst); dst_hold(&rt->dst);
goto out; goto out;
} else if (res.fib6_flags & RTF_REJECT) {
goto do_create;
} }
fib6_select_path(net, &res, fl6, fl6->flowi6_oif, fib6_select_path(net, &res, fl6, fl6->flowi6_oif,
...@@ -1128,6 +1155,7 @@ static struct rt6_info *ip6_pol_route_lookup(struct net *net, ...@@ -1128,6 +1155,7 @@ static struct rt6_info *ip6_pol_route_lookup(struct net *net,
if (ip6_hold_safe(net, &rt)) if (ip6_hold_safe(net, &rt))
dst_use_noref(&rt->dst, jiffies); dst_use_noref(&rt->dst, jiffies);
} else { } else {
do_create:
rt = ip6_create_rt_rcu(&res); rt = ip6_create_rt_rcu(&res);
} }
...@@ -3217,7 +3245,9 @@ static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg, ...@@ -3217,7 +3245,9 @@ static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg,
{ {
struct net *net = cfg->fc_nlinfo.nl_net; struct net *net = cfg->fc_nlinfo.nl_net;
struct fib6_info *rt = NULL; struct fib6_info *rt = NULL;
struct nexthop *nh = NULL;
struct fib6_table *table; struct fib6_table *table;
struct fib6_nh *fib6_nh;
int err = -EINVAL; int err = -EINVAL;
int addr_type; int addr_type;
...@@ -3270,7 +3300,7 @@ static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg, ...@@ -3270,7 +3300,7 @@ static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg,
goto out; goto out;
err = -ENOMEM; err = -ENOMEM;
rt = fib6_info_alloc(gfp_flags, true); rt = fib6_info_alloc(gfp_flags, !nh);
if (!rt) if (!rt)
goto out; goto out;
...@@ -3310,19 +3340,35 @@ static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg, ...@@ -3310,19 +3340,35 @@ static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg,
ipv6_addr_prefix(&rt->fib6_src.addr, &cfg->fc_src, cfg->fc_src_len); ipv6_addr_prefix(&rt->fib6_src.addr, &cfg->fc_src, cfg->fc_src_len);
rt->fib6_src.plen = cfg->fc_src_len; rt->fib6_src.plen = cfg->fc_src_len;
#endif #endif
if (nh) {
if (!nexthop_get(nh)) {
NL_SET_ERR_MSG(extack, "Nexthop has been deleted");
goto out;
}
if (rt->fib6_src.plen) {
NL_SET_ERR_MSG(extack, "Nexthops can not be used wtih source routing");
goto out;
}
rt->nh = nh;
fib6_nh = nexthop_fib6_nh(rt->nh);
} else {
err = fib6_nh_init(net, rt->fib6_nh, cfg, gfp_flags, extack); err = fib6_nh_init(net, rt->fib6_nh, cfg, gfp_flags, extack);
if (err) if (err)
goto out; goto out;
/* We cannot add true routes via loopback here, fib6_nh = rt->fib6_nh;
* they would result in kernel looping; promote them to reject routes
/* We cannot add true routes via loopback here, they would
* result in kernel looping; promote them to reject routes
*/ */
addr_type = ipv6_addr_type(&cfg->fc_dst); addr_type = ipv6_addr_type(&cfg->fc_dst);
if (fib6_is_reject(cfg->fc_flags, rt->fib6_nh->fib_nh_dev, addr_type)) if (fib6_is_reject(cfg->fc_flags, rt->fib6_nh->fib_nh_dev,
addr_type))
rt->fib6_flags = RTF_REJECT | RTF_NONEXTHOP; rt->fib6_flags = RTF_REJECT | RTF_NONEXTHOP;
}
if (!ipv6_addr_any(&cfg->fc_prefsrc)) { if (!ipv6_addr_any(&cfg->fc_prefsrc)) {
struct net_device *dev = fib6_info_nh_dev(rt); struct net_device *dev = fib6_nh->fib_nh_dev;
if (!ipv6_chk_addr(net, &cfg->fc_prefsrc, dev, 0)) { if (!ipv6_chk_addr(net, &cfg->fc_prefsrc, dev, 0)) {
NL_SET_ERR_MSG(extack, "Invalid source address"); NL_SET_ERR_MSG(extack, "Invalid source address");
...@@ -3678,6 +3724,9 @@ static struct fib6_info *rt6_get_route_info(struct net *net, ...@@ -3678,6 +3724,9 @@ static struct fib6_info *rt6_get_route_info(struct net *net,
goto out; goto out;
for_each_fib6_node_rt_rcu(fn) { for_each_fib6_node_rt_rcu(fn) {
/* these routes do not use nexthops */
if (rt->nh)
continue;
if (rt->fib6_nh->fib_nh_dev->ifindex != ifindex) if (rt->fib6_nh->fib_nh_dev->ifindex != ifindex)
continue; continue;
if (!(rt->fib6_flags & RTF_ROUTEINFO) || if (!(rt->fib6_flags & RTF_ROUTEINFO) ||
...@@ -3741,8 +3790,13 @@ struct fib6_info *rt6_get_dflt_router(struct net *net, ...@@ -3741,8 +3790,13 @@ struct fib6_info *rt6_get_dflt_router(struct net *net,
rcu_read_lock(); rcu_read_lock();
for_each_fib6_node_rt_rcu(&table->tb6_root) { for_each_fib6_node_rt_rcu(&table->tb6_root) {
struct fib6_nh *nh = rt->fib6_nh; struct fib6_nh *nh;
/* RA routes do not use nexthops */
if (rt->nh)
continue;
nh = rt->fib6_nh;
if (dev == nh->fib_nh_dev && if (dev == nh->fib_nh_dev &&
((rt->fib6_flags & (RTF_ADDRCONF | RTF_DEFAULT)) == (RTF_ADDRCONF | RTF_DEFAULT)) && ((rt->fib6_flags & (RTF_ADDRCONF | RTF_DEFAULT)) == (RTF_ADDRCONF | RTF_DEFAULT)) &&
ipv6_addr_equal(&nh->fib_nh_gw6, addr)) ipv6_addr_equal(&nh->fib_nh_gw6, addr))
...@@ -3993,7 +4047,8 @@ static int fib6_remove_prefsrc(struct fib6_info *rt, void *arg) ...@@ -3993,7 +4047,8 @@ static int fib6_remove_prefsrc(struct fib6_info *rt, void *arg)
struct net *net = ((struct arg_dev_net_ip *)arg)->net; struct net *net = ((struct arg_dev_net_ip *)arg)->net;
struct in6_addr *addr = ((struct arg_dev_net_ip *)arg)->addr; struct in6_addr *addr = ((struct arg_dev_net_ip *)arg)->addr;
if (((void *)rt->fib6_nh->fib_nh_dev == dev || !dev) && if (!rt->nh &&
((void *)rt->fib6_nh->fib_nh_dev == dev || !dev) &&
rt != net->ipv6.fib6_null_entry && rt != net->ipv6.fib6_null_entry &&
ipv6_addr_equal(addr, &rt->fib6_prefsrc.addr)) { ipv6_addr_equal(addr, &rt->fib6_prefsrc.addr)) {
spin_lock_bh(&rt6_exception_lock); spin_lock_bh(&rt6_exception_lock);
...@@ -4021,8 +4076,13 @@ void rt6_remove_prefsrc(struct inet6_ifaddr *ifp) ...@@ -4021,8 +4076,13 @@ void rt6_remove_prefsrc(struct inet6_ifaddr *ifp)
static int fib6_clean_tohost(struct fib6_info *rt, void *arg) static int fib6_clean_tohost(struct fib6_info *rt, void *arg)
{ {
struct in6_addr *gateway = (struct in6_addr *)arg; struct in6_addr *gateway = (struct in6_addr *)arg;
struct fib6_nh *nh = rt->fib6_nh; struct fib6_nh *nh;
/* RA routes do not use nexthops */
if (rt->nh)
return 0;
nh = rt->fib6_nh;
if (((rt->fib6_flags & RTF_RA_ROUTER) == RTF_RA_ROUTER) && if (((rt->fib6_flags & RTF_RA_ROUTER) == RTF_RA_ROUTER) &&
nh->fib_nh_gw_family && ipv6_addr_equal(gateway, &nh->fib_nh_gw6)) nh->fib_nh_gw_family && ipv6_addr_equal(gateway, &nh->fib_nh_gw6))
return -1; return -1;
...@@ -4069,6 +4129,7 @@ static struct fib6_info *rt6_multipath_first_sibling(const struct fib6_info *rt) ...@@ -4069,6 +4129,7 @@ static struct fib6_info *rt6_multipath_first_sibling(const struct fib6_info *rt)
return NULL; return NULL;
} }
/* only called for fib entries with builtin fib6_nh */
static bool rt6_is_dead(const struct fib6_info *rt) static bool rt6_is_dead(const struct fib6_info *rt)
{ {
if (rt->fib6_nh->fib_nh_flags & RTNH_F_DEAD || if (rt->fib6_nh->fib_nh_flags & RTNH_F_DEAD ||
...@@ -4147,7 +4208,7 @@ static int fib6_ifup(struct fib6_info *rt, void *p_arg) ...@@ -4147,7 +4208,7 @@ static int fib6_ifup(struct fib6_info *rt, void *p_arg)
const struct arg_netdev_event *arg = p_arg; const struct arg_netdev_event *arg = p_arg;
struct net *net = dev_net(arg->dev); struct net *net = dev_net(arg->dev);
if (rt != net->ipv6.fib6_null_entry && if (rt != net->ipv6.fib6_null_entry && !rt->nh &&
rt->fib6_nh->fib_nh_dev == arg->dev) { rt->fib6_nh->fib_nh_dev == arg->dev) {
rt->fib6_nh->fib_nh_flags &= ~arg->nh_flags; rt->fib6_nh->fib_nh_flags &= ~arg->nh_flags;
fib6_update_sernum_upto_root(net, rt); fib6_update_sernum_upto_root(net, rt);
...@@ -4172,6 +4233,7 @@ void rt6_sync_up(struct net_device *dev, unsigned char nh_flags) ...@@ -4172,6 +4233,7 @@ void rt6_sync_up(struct net_device *dev, unsigned char nh_flags)
fib6_clean_all(dev_net(dev), fib6_ifup, &arg); fib6_clean_all(dev_net(dev), fib6_ifup, &arg);
} }
/* only called for fib entries with inline fib6_nh */
static bool rt6_multipath_uses_dev(const struct fib6_info *rt, static bool rt6_multipath_uses_dev(const struct fib6_info *rt,
const struct net_device *dev) const struct net_device *dev)
{ {
...@@ -4232,7 +4294,7 @@ static int fib6_ifdown(struct fib6_info *rt, void *p_arg) ...@@ -4232,7 +4294,7 @@ static int fib6_ifdown(struct fib6_info *rt, void *p_arg)
const struct net_device *dev = arg->dev; const struct net_device *dev = arg->dev;
struct net *net = dev_net(dev); struct net *net = dev_net(dev);
if (rt == net->ipv6.fib6_null_entry) if (rt == net->ipv6.fib6_null_entry || rt->nh)
return 0; return 0;
switch (arg->event) { switch (arg->event) {
...@@ -4786,6 +4848,9 @@ static size_t rt6_nlmsg_size(struct fib6_info *rt) ...@@ -4786,6 +4848,9 @@ static size_t rt6_nlmsg_size(struct fib6_info *rt)
{ {
int nexthop_len = 0; int nexthop_len = 0;
if (rt->nh)
nexthop_len += nla_total_size(4); /* RTA_NH_ID */
if (rt->fib6_nsiblings) { if (rt->fib6_nsiblings) {
nexthop_len = nla_total_size(0) /* RTA_MULTIPATH */ nexthop_len = nla_total_size(0) /* RTA_MULTIPATH */
+ NLA_ALIGN(sizeof(struct rtnexthop)) + NLA_ALIGN(sizeof(struct rtnexthop))
...@@ -4812,6 +4877,35 @@ static size_t rt6_nlmsg_size(struct fib6_info *rt) ...@@ -4812,6 +4877,35 @@ static size_t rt6_nlmsg_size(struct fib6_info *rt)
+ nexthop_len; + nexthop_len;
} }
static int rt6_fill_node_nexthop(struct sk_buff *skb, struct nexthop *nh,
unsigned char *flags)
{
if (nexthop_is_multipath(nh)) {
struct nlattr *mp;
mp = nla_nest_start(skb, RTA_MULTIPATH);
if (!mp)
goto nla_put_failure;
if (nexthop_mpath_fill_node(skb, nh))
goto nla_put_failure;
nla_nest_end(skb, mp);
} else {
struct fib6_nh *fib6_nh;
fib6_nh = nexthop_fib6_nh(nh);
if (fib_nexthop_info(skb, &fib6_nh->nh_common,
flags, false) < 0)
goto nla_put_failure;
}
return 0;
nla_put_failure:
return -EMSGSIZE;
}
static int rt6_fill_node(struct net *net, struct sk_buff *skb, static int rt6_fill_node(struct net *net, struct sk_buff *skb,
struct fib6_info *rt, struct dst_entry *dst, struct fib6_info *rt, struct dst_entry *dst,
struct in6_addr *dest, struct in6_addr *src, struct in6_addr *dest, struct in6_addr *src,
...@@ -4821,6 +4915,7 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb, ...@@ -4821,6 +4915,7 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb,
struct rt6_info *rt6 = (struct rt6_info *)dst; struct rt6_info *rt6 = (struct rt6_info *)dst;
struct rt6key *rt6_dst, *rt6_src; struct rt6key *rt6_dst, *rt6_src;
u32 *pmetrics, table, rt6_flags; u32 *pmetrics, table, rt6_flags;
unsigned char nh_flags = 0;
struct nlmsghdr *nlh; struct nlmsghdr *nlh;
struct rtmsg *rtm; struct rtmsg *rtm;
long expires = 0; long expires = 0;
...@@ -4940,9 +5035,18 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb, ...@@ -4940,9 +5035,18 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb,
} }
nla_nest_end(skb, mp); nla_nest_end(skb, mp);
} else { } else if (rt->nh) {
unsigned char nh_flags = 0; if (nla_put_u32(skb, RTA_NH_ID, rt->nh->id))
goto nla_put_failure;
if (nexthop_is_blackhole(rt->nh))
rtm->rtm_type = RTN_BLACKHOLE;
if (rt6_fill_node_nexthop(skb, rt->nh, &nh_flags) < 0)
goto nla_put_failure;
rtm->rtm_flags |= nh_flags;
} else {
if (fib_nexthop_info(skb, &rt->fib6_nh->nh_common, if (fib_nexthop_info(skb, &rt->fib6_nh->nh_common,
&nh_flags, false) < 0) &nh_flags, false) < 0)
goto nla_put_failure; goto nla_put_failure;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment