ipv6: Plumb support for nexthop object in a fib6_info
authorDavid Ahern <dsahern@gmail.com>
Tue, 4 Jun 2019 03:19:52 +0000 (20:19 -0700)
committerDavid S. Miller <davem@davemloft.net>
Wed, 5 Jun 2019 02:26:50 +0000 (19:26 -0700)
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: David Ahern <dsahern@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/ip6_fib.h
include/net/ip6_route.h
include/net/nexthop.h
net/ipv4/nexthop.c
net/ipv6/addrconf.c
net/ipv6/ip6_fib.c
net/ipv6/ndisc.c
net/ipv6/route.c

index ebe5d65f97e0991e6735162821525aa8b1e50d65..1a8acd51b277907ca4ac5c4a33a9a2004ddf9c5b 100644 (file)
@@ -146,7 +146,10 @@ struct fib6_info {
         * destination, but not the same gateway. nsiblings is just a cache
         * to speed up lookup.
         */
-       struct list_head                fib6_siblings;
+       union {
+               struct list_head        fib6_siblings;
+               struct list_head        nh_list;
+       };
        unsigned int                    fib6_nsiblings;
 
        refcount_t                      fib6_ref;
@@ -170,6 +173,7 @@ struct fib6_info {
                                        unused:3;
 
        struct rcu_head                 rcu;
+       struct nexthop                  *nh;
        struct fib6_nh                  fib6_nh[0];
 };
 
@@ -441,11 +445,6 @@ void rt6_get_prefsrc(const struct rt6_info *rt, struct in6_addr *addr)
        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,
                 struct fib6_config *cfg, gfp_t gfp_flags,
                 struct netlink_ext_ack *extack);
index a6ce6ea856b948a30d029ed609e52ab45ae7ec10..7375a165fd98da708084892e7edc8cca5d169199 100644 (file)
@@ -27,6 +27,7 @@ struct route_info {
 #include <linux/ip.h>
 #include <linux/ipv6.h>
 #include <linux/route.h>
+#include <net/nexthop.h>
 
 #define RT6_LOOKUP_F_IFACE             0x00000001
 #define RT6_LOOKUP_F_REACHABLE         0x00000002
@@ -66,10 +67,13 @@ static inline bool rt6_need_strict(const struct in6_addr *daddr)
                (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)
 {
        /* 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;
 }
 
@@ -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)
 {
-       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 &&
               ipv6_addr_equal(&nha->fib_nh_gw6, &nhb->fib_nh_gw6) &&
               !lwtunnel_cmp_encap(nha->fib_nh_lws, nhb->fib_nh_lws);
index 2912a2d7a515ba9d95d98843bd7f9d434194495a..aff7b2410057c9cbcc9184206be2c0fba9c13382 100644 (file)
@@ -10,6 +10,7 @@
 #define __LINUX_NEXTHOP_H
 
 #include <linux/netdevice.h>
+#include <linux/route.h>
 #include <linux/types.h>
 #include <net/ip_fib.h>
 #include <net/ip6_fib.h>
@@ -78,6 +79,7 @@ struct nh_group {
 struct nexthop {
        struct rb_node          rb_node;    /* entry on netns rbtree */
        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 net              *net;
 
@@ -255,4 +257,52 @@ static inline struct fib_nh *fib_info_nh(struct fib_info *fi, int 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
index 63cbb04f697f22e50dba7da3f0cc43378ebbdda2..5e48762b6b5f7988318fe1607baa2a561ed60ea3 100644 (file)
@@ -106,6 +106,7 @@ static struct nexthop *nexthop_alloc(void)
        nh = kzalloc(sizeof(struct nexthop), GFP_KERNEL);
        if (nh) {
                INIT_LIST_HEAD(&nh->fi_list);
+               INIT_LIST_HEAD(&nh->f6i_list);
                INIT_LIST_HEAD(&nh->grp_list);
        }
        return nh;
@@ -516,6 +517,41 @@ struct nexthop *nexthop_select_path(struct nexthop *nh, int hash)
 }
 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,
                               struct netlink_ext_ack *extack)
 {
@@ -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)
 {
+       struct fib6_info *f6i, *tmp;
        bool do_flush = false;
        struct fib_info *fi;
 
@@ -667,6 +704,13 @@ static void __remove_nexthop_fib(struct net *net, struct nexthop *nh)
        }
        if (do_flush)
                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,
index 4c30726fa7c7246f3b7f00ba4e3c459c1cd3df55..d7fff86c2ef07866b64d8e491c6b19e8a3304b24 100644 (file)
@@ -2421,6 +2421,10 @@ static struct fib6_info *addrconf_get_prefix_route(const struct in6_addr *pfx,
                goto out;
 
        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)
                        continue;
                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)
        list_for_each_entry(ifa, &idev->addr_list, if_list) {
                spin_lock(&ifa->lock);
                if (ifa->rt) {
+                       /* host routes only use builtin fib6_nh */
                        struct fib6_nh *nh = ifa->rt->fib6_nh;
                        int cpu;
 
index cdfb8500ccae3891c47201ca8093615e98c075ef..02feda73a98e83a84da713db2445922f5d1ae02b 100644 (file)
@@ -159,6 +159,7 @@ struct fib6_info *fib6_info_alloc(gfp_t gfp_flags, bool with_fib6_nh)
        if (!f6i)
                return NULL;
 
+       /* fib6_siblings is a union with nh_list, so this initializes both */
        INIT_LIST_HEAD(&f6i->fib6_siblings);
        refcount_set(&f6i->fib6_ref, 1);
 
@@ -171,7 +172,11 @@ void fib6_info_destroy_rcu(struct rcu_head *head)
 
        WARN_ON(f6i->fib6_node);
 
-       fib6_nh_release(f6i->fib6_nh);
+       if (f6i->nh)
+               nexthop_put(f6i->nh);
+       else
+               fib6_nh_release(f6i->fib6_nh);
+
        ip_fib_metrics_put(f6i->fib6_metrics);
        kfree(f6i);
 }
@@ -927,6 +932,9 @@ static void fib6_purge_rt(struct fib6_info *rt, struct fib6_node *fn,
 
        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) {
                /* This route is used as dummy address holder in some split
                 * 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,
 
        err = fib6_add_rt2node(fn, rt, info, extack);
        if (!err) {
+               if (rt->nh)
+                       list_add(&rt->nh_list, &rt->nh->f6i_list);
                __fib6_update_sernum_upto_root(rt, sernum);
                fib6_start_gc(info->nl_net, rt);
        }
@@ -2295,9 +2305,13 @@ static int ipv6_route_seq_show(struct seq_file *seq, void *v)
 {
        struct fib6_info *rt = v;
        struct ipv6_route_iter *iter = seq->private;
+       struct fib6_nh *fib6_nh = rt->fib6_nh;
        unsigned int flags = rt->fib6_flags;
        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);
 
 #ifdef CONFIG_IPV6_SUBTREES
@@ -2305,14 +2319,14 @@ static int ipv6_route_seq_show(struct seq_file *seq, void *v)
 #else
        seq_puts(seq, "00000000000000000000000000000000 00 ");
 #endif
-       if (rt->fib6_nh->fib_nh_gw_family) {
+       if (fib6_nh->fib_nh_gw_family) {
                flags |= RTF_GATEWAY;
-               seq_printf(seq, "%pi6", &rt->fib6_nh->fib_nh_gw6);
+               seq_printf(seq, "%pi6", &fib6_nh->fib_nh_gw6);
        } else {
                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",
                   rt->fib6_metric, refcount_read(&rt->fib6_ref), 0,
                   flags, dev ? dev->name : "");
index f874dde1ee85b3f2219ece08393b9c5ceb19328b..6e3c51109c83fdcb53cbc6ed599745b9c1e6138c 100644 (file)
@@ -1289,9 +1289,8 @@ static void ndisc_router_discovery(struct sk_buff *skb)
            !in6_dev->cnf.accept_ra_rtr_pref)
                pref = ICMPV6_ROUTER_PREF_MEDIUM;
 #endif
-
+       /* routes added from RAs do not use nexthop objects */
        rt = rt6_get_dflt_router(net, &ipv6_hdr(skb)->saddr, skb->dev);
-
        if (rt) {
                neigh = ip6_neigh_lookup(&rt->fib6_nh->fib_nh_gw6,
                                         rt->fib6_nh->fib_nh_dev, NULL,
index 9b9a0159f7fdb059f7d08a6c39d8b2e96f313389..df5be3d5d3e529152000acaf222d33ae2f4e32a5 100644 (file)
@@ -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 *match = res->f6i;
 
-       if (!match->fib6_nsiblings || have_oif_match)
+       if ((!match->fib6_nsiblings && !match->nh) || have_oif_match)
                goto out;
 
        /* 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.
         */
-       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);
 
+       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))
                goto out;
 
@@ -496,7 +502,13 @@ static void rt6_device_match(struct net *net, struct fib6_result *res,
        struct fib6_nh *nh;
 
        if (!oif && ipv6_addr_any(saddr)) {
-               nh = f6i->fib6_nh;
+               if (unlikely(f6i->nh)) {
+                       nh = nexthop_fib6_nh(f6i->nh);
+                       if (nexthop_is_blackhole(f6i->nh))
+                               goto out_blackhole;
+               } else {
+                       nh = f6i->fib6_nh;
+               }
                if (!(nh->fib_nh_flags & RTNH_F_DEAD))
                        goto out;
        }
@@ -515,7 +527,14 @@ static void rt6_device_match(struct net *net, struct fib6_result *res,
                goto out;
        }
 
-       nh = f6i->fib6_nh;
+       if (unlikely(f6i->nh)) {
+               nh = nexthop_fib6_nh(f6i->nh);
+               if (nexthop_is_blackhole(f6i->nh))
+                       goto out_blackhole;
+       } else {
+               nh = f6i->fib6_nh;
+       }
+
        if (nh->fib_nh_flags & RTNH_F_DEAD) {
                res->f6i = net->ipv6.fib6_null_entry;
                nh = res->f6i->fib6_nh;
@@ -524,6 +543,12 @@ out:
        res->nh = nh;
        res->fib6_type = res->f6i->fib6_type;
        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
@@ -1117,6 +1142,8 @@ restart:
                rt = net->ipv6.ip6_null_entry;
                dst_hold(&rt->dst);
                goto out;
+       } else if (res.fib6_flags & RTF_REJECT) {
+               goto do_create;
        }
 
        fib6_select_path(net, &res, fl6, fl6->flowi6_oif,
@@ -1128,6 +1155,7 @@ restart:
                if (ip6_hold_safe(net, &rt))
                        dst_use_noref(&rt->dst, jiffies);
        } else {
+do_create:
                rt = ip6_create_rt_rcu(&res);
        }
 
@@ -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 fib6_info *rt = NULL;
+       struct nexthop *nh = NULL;
        struct fib6_table *table;
+       struct fib6_nh *fib6_nh;
        int err = -EINVAL;
        int addr_type;
 
@@ -3270,7 +3300,7 @@ static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg,
                goto out;
 
        err = -ENOMEM;
-       rt = fib6_info_alloc(gfp_flags, true);
+       rt = fib6_info_alloc(gfp_flags, !nh);
        if (!rt)
                goto out;
 
@@ -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);
        rt->fib6_src.plen = cfg->fc_src_len;
 #endif
-       err = fib6_nh_init(net, rt->fib6_nh, cfg, gfp_flags, extack);
-       if (err)
-               goto out;
+       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);
+               if (err)
+                       goto out;
 
-       /* 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);
-       if (fib6_is_reject(cfg->fc_flags, rt->fib6_nh->fib_nh_dev, addr_type))
-               rt->fib6_flags = RTF_REJECT | RTF_NONEXTHOP;
+               fib6_nh = rt->fib6_nh;
+
+               /* 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);
+               if (fib6_is_reject(cfg->fc_flags, rt->fib6_nh->fib_nh_dev,
+                                  addr_type))
+                       rt->fib6_flags = RTF_REJECT | RTF_NONEXTHOP;
+       }
 
        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)) {
                        NL_SET_ERR_MSG(extack, "Invalid source address");
@@ -3678,6 +3724,9 @@ static struct fib6_info *rt6_get_route_info(struct net *net,
                goto out;
 
        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)
                        continue;
                if (!(rt->fib6_flags & RTF_ROUTEINFO) ||
@@ -3741,8 +3790,13 @@ struct fib6_info *rt6_get_dflt_router(struct net *net,
 
        rcu_read_lock();
        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 &&
                    ((rt->fib6_flags & (RTF_ADDRCONF | RTF_DEFAULT)) == (RTF_ADDRCONF | RTF_DEFAULT)) &&
                    ipv6_addr_equal(&nh->fib_nh_gw6, addr))
@@ -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 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 &&
            ipv6_addr_equal(addr, &rt->fib6_prefsrc.addr)) {
                spin_lock_bh(&rt6_exception_lock);
@@ -4021,8 +4076,13 @@ void rt6_remove_prefsrc(struct inet6_ifaddr *ifp)
 static int fib6_clean_tohost(struct fib6_info *rt, void *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) &&
            nh->fib_nh_gw_family && ipv6_addr_equal(gateway, &nh->fib_nh_gw6))
                return -1;
@@ -4069,6 +4129,7 @@ static struct fib6_info *rt6_multipath_first_sibling(const struct fib6_info *rt)
        return NULL;
 }
 
+/* only called for fib entries with builtin fib6_nh */
 static bool rt6_is_dead(const struct fib6_info *rt)
 {
        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)
        const struct arg_netdev_event *arg = p_arg;
        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_flags &= ~arg->nh_flags;
                fib6_update_sernum_upto_root(net, rt);
@@ -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);
 }
 
+/* only called for fib entries with inline fib6_nh */
 static bool rt6_multipath_uses_dev(const struct fib6_info *rt,
                                   const struct net_device *dev)
 {
@@ -4232,7 +4294,7 @@ static int fib6_ifdown(struct fib6_info *rt, void *p_arg)
        const struct net_device *dev = arg->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;
 
        switch (arg->event) {
@@ -4786,6 +4848,9 @@ static size_t rt6_nlmsg_size(struct fib6_info *rt)
 {
        int nexthop_len = 0;
 
+       if (rt->nh)
+               nexthop_len += nla_total_size(4); /* RTA_NH_ID */
+
        if (rt->fib6_nsiblings) {
                nexthop_len = nla_total_size(0)  /* RTA_MULTIPATH */
                            + NLA_ALIGN(sizeof(struct rtnexthop))
@@ -4812,6 +4877,35 @@ static size_t rt6_nlmsg_size(struct fib6_info *rt)
               + 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,
                         struct fib6_info *rt, struct dst_entry *dst,
                         struct in6_addr *dest, struct in6_addr *src,
@@ -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 rt6key *rt6_dst, *rt6_src;
        u32 *pmetrics, table, rt6_flags;
+       unsigned char nh_flags = 0;
        struct nlmsghdr *nlh;
        struct rtmsg *rtm;
        long expires = 0;
@@ -4940,9 +5035,18 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb,
                }
 
                nla_nest_end(skb, mp);
-       } else {
-               unsigned char nh_flags = 0;
+       } else if (rt->nh) {
+               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,
                                     &nh_flags, false) < 0)
                        goto nla_put_failure;