]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - net/ipv6/route.c
Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
[linux.git] / net / ipv6 / route.c
index 7178e32eb15d0a969eb39fcfec9973bb0150bf48..9ece8067a59be9024602f619d35f0b2966abd027 100644 (file)
@@ -102,7 +102,8 @@ static void         ip6_rt_update_pmtu(struct dst_entry *dst, struct sock *sk,
                                           struct sk_buff *skb, u32 mtu);
 static void            rt6_do_redirect(struct dst_entry *dst, struct sock *sk,
                                        struct sk_buff *skb);
-static int rt6_score_route(struct fib6_info *rt, int oif, int strict);
+static int rt6_score_route(const struct fib6_nh *nh, u32 fib6_flags, int oif,
+                          int strict);
 static size_t rt6_nlmsg_size(struct fib6_info *rt);
 static int rt6_fill_node(struct net *net, struct sk_buff *skb,
                         struct fib6_info *rt, struct dst_entry *dst,
@@ -441,17 +442,18 @@ struct fib6_info *fib6_multipath_select(const struct net *net,
        if (!fl6->mp_hash)
                fl6->mp_hash = rt6_multipath_hash(net, fl6, skb, NULL);
 
-       if (fl6->mp_hash <= atomic_read(&match->fib6_nh.nh_upper_bound))
+       if (fl6->mp_hash <= atomic_read(&match->fib6_nh.fib_nh_upper_bound))
                return match;
 
        list_for_each_entry_safe(sibling, next_sibling, &match->fib6_siblings,
                                 fib6_siblings) {
+               const struct fib6_nh *nh = &sibling->fib6_nh;
                int nh_upper_bound;
 
-               nh_upper_bound = atomic_read(&sibling->fib6_nh.nh_upper_bound);
+               nh_upper_bound = atomic_read(&nh->fib_nh_upper_bound);
                if (fl6->mp_hash > nh_upper_bound)
                        continue;
-               if (rt6_score_route(sibling, oif, strict) < 0)
+               if (rt6_score_route(nh, sibling->fib6_flags, oif, strict) < 0)
                        break;
                match = sibling;
                break;
@@ -464,38 +466,50 @@ struct fib6_info *fib6_multipath_select(const struct net *net,
  *     Route lookup. rcu_read_lock() should be held.
  */
 
+static bool __rt6_device_match(struct net *net, const struct fib6_nh *nh,
+                              const struct in6_addr *saddr, int oif, int flags)
+{
+       const struct net_device *dev;
+
+       if (nh->fib_nh_flags & RTNH_F_DEAD)
+               return false;
+
+       dev = nh->fib_nh_dev;
+       if (oif) {
+               if (dev->ifindex == oif)
+                       return true;
+       } else {
+               if (ipv6_chk_addr(net, saddr, dev,
+                                 flags & RT6_LOOKUP_F_IFACE))
+                       return true;
+       }
+
+       return false;
+}
+
 static inline struct fib6_info *rt6_device_match(struct net *net,
                                                 struct fib6_info *rt,
                                                    const struct in6_addr *saddr,
                                                    int oif,
                                                    int flags)
 {
+       const struct fib6_nh *nh;
        struct fib6_info *sprt;
 
        if (!oif && ipv6_addr_any(saddr) &&
-           !(rt->fib6_nh.nh_flags & RTNH_F_DEAD))
+           !(rt->fib6_nh.fib_nh_flags & RTNH_F_DEAD))
                return rt;
 
        for (sprt = rt; sprt; sprt = rcu_dereference(sprt->fib6_next)) {
-               const struct net_device *dev = sprt->fib6_nh.nh_dev;
-
-               if (sprt->fib6_nh.nh_flags & RTNH_F_DEAD)
-                       continue;
-
-               if (oif) {
-                       if (dev->ifindex == oif)
-                               return sprt;
-               } else {
-                       if (ipv6_chk_addr(net, saddr, dev,
-                                         flags & RT6_LOOKUP_F_IFACE))
-                               return sprt;
-               }
+               nh = &sprt->fib6_nh;
+               if (__rt6_device_match(net, nh, saddr, oif, flags))
+                       return sprt;
        }
 
        if (oif && flags & RT6_LOOKUP_F_IFACE)
                return net->ipv6.fib6_null_entry;
 
-       return rt->fib6_nh.nh_flags & RTNH_F_DEAD ? net->ipv6.fib6_null_entry : rt;
+       return rt->fib6_nh.fib_nh_flags & RTNH_F_DEAD ? net->ipv6.fib6_null_entry : rt;
 }
 
 #ifdef CONFIG_IPV6_ROUTER_PREF
@@ -517,7 +531,7 @@ static void rt6_probe_deferred(struct work_struct *w)
        kfree(work);
 }
 
-static void rt6_probe(struct fib6_info *rt)
+static void rt6_probe(struct fib6_nh *fib6_nh)
 {
        struct __rt6_probe_work *work = NULL;
        const struct in6_addr *nh_gw;
@@ -533,11 +547,11 @@ static void rt6_probe(struct fib6_info *rt)
         * Router Reachability Probe MUST be rate-limited
         * to no more than one per minute.
         */
-       if (!rt || !(rt->fib6_flags & RTF_GATEWAY))
+       if (fib6_nh->fib_nh_gw_family)
                return;
 
-       nh_gw = &rt->fib6_nh.nh_gw;
-       dev = rt->fib6_nh.nh_dev;
+       nh_gw = &fib6_nh->fib_nh_gw6;
+       dev = fib6_nh->fib_nh_dev;
        rcu_read_lock_bh();
        idev = __in6_dev_get(dev);
        neigh = __ipv6_neigh_lookup_noref(dev, nh_gw);
@@ -554,13 +568,13 @@ static void rt6_probe(struct fib6_info *rt)
                                __neigh_set_probe_once(neigh);
                }
                write_unlock(&neigh->lock);
-       } else if (time_after(jiffies, rt->last_probe +
+       } else if (time_after(jiffies, fib6_nh->last_probe +
                                       idev->cnf.rtr_probe_interval)) {
                work = kmalloc(sizeof(*work), GFP_ATOMIC);
        }
 
        if (work) {
-               rt->last_probe = jiffies;
+               fib6_nh->last_probe = jiffies;
                INIT_WORK(&work->work, rt6_probe_deferred);
                work->target = *nh_gw;
                dev_hold(dev);
@@ -572,7 +586,7 @@ static void rt6_probe(struct fib6_info *rt)
        rcu_read_unlock_bh();
 }
 #else
-static inline void rt6_probe(struct fib6_info *rt)
+static inline void rt6_probe(struct fib6_nh *fib6_nh)
 {
 }
 #endif
@@ -580,27 +594,14 @@ static inline void rt6_probe(struct fib6_info *rt)
 /*
  * Default Router Selection (RFC 2461 6.3.6)
  */
-static inline int rt6_check_dev(struct fib6_info *rt, int oif)
-{
-       const struct net_device *dev = rt->fib6_nh.nh_dev;
-
-       if (!oif || dev->ifindex == oif)
-               return 2;
-       return 0;
-}
-
-static inline enum rt6_nud_state rt6_check_neigh(struct fib6_info *rt)
+static enum rt6_nud_state rt6_check_neigh(const struct fib6_nh *fib6_nh)
 {
        enum rt6_nud_state ret = RT6_NUD_FAIL_HARD;
        struct neighbour *neigh;
 
-       if (rt->fib6_flags & RTF_NONEXTHOP ||
-           !(rt->fib6_flags & RTF_GATEWAY))
-               return RT6_NUD_SUCCEED;
-
        rcu_read_lock_bh();
-       neigh = __ipv6_neigh_lookup_noref(rt->fib6_nh.nh_dev,
-                                         &rt->fib6_nh.nh_gw);
+       neigh = __ipv6_neigh_lookup_noref(fib6_nh->fib_nh_dev,
+                                         &fib6_nh->fib_nh_gw6);
        if (neigh) {
                read_lock(&neigh->lock);
                if (neigh->nud_state & NUD_VALID)
@@ -621,58 +622,44 @@ static inline enum rt6_nud_state rt6_check_neigh(struct fib6_info *rt)
        return ret;
 }
 
-static int rt6_score_route(struct fib6_info *rt, int oif, int strict)
+static int rt6_score_route(const struct fib6_nh *nh, u32 fib6_flags, int oif,
+                          int strict)
 {
-       int m;
+       int m = 0;
+
+       if (!oif || nh->fib_nh_dev->ifindex == oif)
+               m = 2;
 
-       m = rt6_check_dev(rt, oif);
        if (!m && (strict & RT6_LOOKUP_F_IFACE))
                return RT6_NUD_FAIL_HARD;
 #ifdef CONFIG_IPV6_ROUTER_PREF
-       m |= IPV6_DECODE_PREF(IPV6_EXTRACT_PREF(rt->fib6_flags)) << 2;
+       m |= IPV6_DECODE_PREF(IPV6_EXTRACT_PREF(fib6_flags)) << 2;
 #endif
-       if (strict & RT6_LOOKUP_F_REACHABLE) {
-               int n = rt6_check_neigh(rt);
+       if ((strict & RT6_LOOKUP_F_REACHABLE) &&
+           !(fib6_flags & RTF_NONEXTHOP) && nh->fib_nh_gw_family) {
+               int n = rt6_check_neigh(nh);
                if (n < 0)
                        return n;
        }
        return m;
 }
 
-/* called with rc_read_lock held */
-static inline bool fib6_ignore_linkdown(const struct fib6_info *f6i)
+static bool find_match(struct fib6_nh *nh, u32 fib6_flags,
+                      int oif, int strict, int *mpri, bool *do_rr)
 {
-       const struct net_device *dev = fib6_info_nh_dev(f6i);
+       bool match_do_rr = false;
        bool rc = false;
-
-       if (dev) {
-               const struct inet6_dev *idev = __in6_dev_get(dev);
-
-               rc = !!idev->cnf.ignore_routes_with_linkdown;
-       }
-
-       return rc;
-}
-
-static struct fib6_info *find_match(struct fib6_info *rt, int oif, int strict,
-                                  int *mpri, struct fib6_info *match,
-                                  bool *do_rr)
-{
        int m;
-       bool match_do_rr = false;
 
-       if (rt->fib6_nh.nh_flags & RTNH_F_DEAD)
+       if (nh->fib_nh_flags & RTNH_F_DEAD)
                goto out;
 
-       if (fib6_ignore_linkdown(rt) &&
-           rt->fib6_nh.nh_flags & RTNH_F_LINKDOWN &&
+       if (ip6_ignore_linkdown(nh->fib_nh_dev) &&
+           nh->fib_nh_flags & RTNH_F_LINKDOWN &&
            !(strict & RT6_LOOKUP_F_IGNORE_LINKSTATE))
                goto out;
 
-       if (fib6_check_expired(rt))
-               goto out;
-
-       m = rt6_score_route(rt, oif, strict);
+       m = rt6_score_route(nh, fib6_flags, oif, strict);
        if (m == RT6_NUD_FAIL_DO_RR) {
                match_do_rr = true;
                m = 0; /* lowest valid score */
@@ -681,53 +668,64 @@ static struct fib6_info *find_match(struct fib6_info *rt, int oif, int strict,
        }
 
        if (strict & RT6_LOOKUP_F_REACHABLE)
-               rt6_probe(rt);
+               rt6_probe(nh);
 
        /* note that m can be RT6_NUD_FAIL_PROBE at this point */
        if (m > *mpri) {
                *do_rr = match_do_rr;
                *mpri = m;
-               match = rt;
+               rc = true;
        }
 out:
-       return match;
+       return rc;
 }
 
-static struct fib6_info *find_rr_leaf(struct fib6_node *fn,
-                                    struct fib6_info *leaf,
-                                    struct fib6_info *rr_head,
-                                    u32 metric, int oif, int strict,
-                                    bool *do_rr)
+static void __find_rr_leaf(struct fib6_info *rt_start,
+                          struct fib6_info *nomatch, u32 metric,
+                          struct fib6_info **match, struct fib6_info **cont,
+                          int oif, int strict, bool *do_rr, int *mpri)
 {
-       struct fib6_info *rt, *match, *cont;
-       int mpri = -1;
+       struct fib6_info *rt;
 
-       match = NULL;
-       cont = NULL;
-       for (rt = rr_head; rt; rt = rcu_dereference(rt->fib6_next)) {
-               if (rt->fib6_metric != metric) {
-                       cont = rt;
-                       break;
+       for (rt = rt_start;
+            rt && rt != nomatch;
+            rt = rcu_dereference(rt->fib6_next)) {
+               struct fib6_nh *nh;
+
+               if (cont && rt->fib6_metric != metric) {
+                       *cont = rt;
+                       return;
                }
 
-               match = find_match(rt, oif, strict, &mpri, match, do_rr);
+               if (fib6_check_expired(rt))
+                       continue;
+
+               nh = &rt->fib6_nh;
+               if (find_match(nh, rt->fib6_flags, oif, strict, mpri, do_rr))
+                       *match = rt;
        }
+}
 
-       for (rt = leaf; rt && rt != rr_head;
-            rt = rcu_dereference(rt->fib6_next)) {
-               if (rt->fib6_metric != metric) {
-                       cont = rt;
-                       break;
-               }
+static struct fib6_info *find_rr_leaf(struct fib6_node *fn,
+                                     struct fib6_info *leaf,
+                                     struct fib6_info *rr_head,
+                                     u32 metric, int oif, int strict,
+                                     bool *do_rr)
+{
+       struct fib6_info *match = NULL, *cont = NULL;
+       int mpri = -1;
 
-               match = find_match(rt, oif, strict, &mpri, match, do_rr);
-       }
+       __find_rr_leaf(rr_head, NULL, metric, &match, &cont,
+                      oif, strict, do_rr, &mpri);
+
+       __find_rr_leaf(leaf, rr_head, metric, &match, &cont,
+                      oif, strict, do_rr, &mpri);
 
        if (match || !cont)
                return match;
 
-       for (rt = cont; rt; rt = rcu_dereference(rt->fib6_next))
-               match = find_match(rt, oif, strict, &mpri, match, do_rr);
+       __find_rr_leaf(cont, NULL, metric, &match, NULL,
+                      oif, strict, do_rr, &mpri);
 
        return match;
 }
@@ -784,7 +782,7 @@ static struct fib6_info *rt6_select(struct net *net, struct fib6_node *fn,
 
 static bool rt6_is_gw_or_nonexthop(const struct fib6_info *rt)
 {
-       return (rt->fib6_flags & (RTF_NONEXTHOP | RTF_GATEWAY));
+       return (rt->fib6_flags & RTF_NONEXTHOP) || rt->fib6_nh.fib_nh_gw_family;
 }
 
 #ifdef CONFIG_IPV6_ROUTE_INFO
@@ -870,7 +868,7 @@ int rt6_route_rcv(struct net_device *dev, u8 *opt, int len,
 /* called with rcu_lock held */
 static struct net_device *ip6_rt_get_dev_rcu(struct fib6_info *rt)
 {
-       struct net_device *dev = rt->fib6_nh.nh_dev;
+       struct net_device *dev = rt->fib6_nh.fib_nh_dev;
 
        if (rt->fib6_flags & (RTF_LOCAL | RTF_ANYCAST)) {
                /* for copies of local routes, dst->dev needs to be the
@@ -964,8 +962,8 @@ static void ip6_rt_init_dst(struct rt6_info *rt, struct fib6_info *ort)
                rt->dst.input = ip6_forward;
        }
 
-       if (ort->fib6_nh.nh_lwtstate) {
-               rt->dst.lwtstate = lwtstate_get(ort->fib6_nh.nh_lwtstate);
+       if (ort->fib6_nh.fib_nh_lws) {
+               rt->dst.lwtstate = lwtstate_get(ort->fib6_nh.fib_nh_lws);
                lwtunnel_set_redirect(&rt->dst);
        }
 
@@ -989,8 +987,11 @@ static void ip6_rt_copy_init(struct rt6_info *rt, struct fib6_info *ort)
 
        rt->rt6i_dst = ort->fib6_dst;
        rt->rt6i_idev = dev ? in6_dev_get(dev) : NULL;
-       rt->rt6i_gateway = ort->fib6_nh.nh_gw;
        rt->rt6i_flags = ort->fib6_flags;
+       if (ort->fib6_nh.fib_nh_gw_family) {
+               rt->rt6i_gateway = ort->fib6_nh.fib_nh_gw6;
+               rt->rt6i_flags |= RTF_GATEWAY;
+       }
        rt6_set_from(rt, ort);
 #ifdef CONFIG_IPV6_SUBTREES
        rt->rt6i_src = ort->fib6_src;
@@ -1015,14 +1016,13 @@ static struct fib6_node* fib6_backtrack(struct fib6_node *fn,
        }
 }
 
-static bool ip6_hold_safe(struct net *net, struct rt6_info **prt,
-                         bool null_fallback)
+static bool ip6_hold_safe(struct net *net, struct rt6_info **prt)
 {
        struct rt6_info *rt = *prt;
 
        if (dst_hold_safe(&rt->dst))
                return true;
-       if (null_fallback) {
+       if (net) {
                rt = net->ipv6.ip6_null_entry;
                dst_hold(&rt->dst);
        } else {
@@ -1036,7 +1036,7 @@ static bool ip6_hold_safe(struct net *net, struct rt6_info **prt,
 static struct rt6_info *ip6_create_rt_rcu(struct fib6_info *rt)
 {
        unsigned short flags = fib6_info_dst_flags(rt);
-       struct net_device *dev = rt->fib6_nh.nh_dev;
+       struct net_device *dev = rt->fib6_nh.fib_nh_dev;
        struct rt6_info *nrt;
 
        if (!fib6_info_hold_safe(rt))
@@ -1074,36 +1074,37 @@ static struct rt6_info *ip6_pol_route_lookup(struct net *net,
        fn = fib6_node_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr);
 restart:
        f6i = rcu_dereference(fn->leaf);
-       if (!f6i) {
+       if (!f6i)
                f6i = net->ipv6.fib6_null_entry;
-       } else {
+       else
                f6i = rt6_device_match(net, f6i, &fl6->saddr,
                                      fl6->flowi6_oif, flags);
-               if (f6i->fib6_nsiblings && fl6->flowi6_oif == 0)
-                       f6i = fib6_multipath_select(net, f6i, fl6,
-                                                   fl6->flowi6_oif, skb,
-                                                   flags);
-       }
+
        if (f6i == net->ipv6.fib6_null_entry) {
                fn = fib6_backtrack(fn, &fl6->saddr);
                if (fn)
                        goto restart;
-       }
 
-       trace_fib6_table_lookup(net, f6i, table, fl6);
+               rt = net->ipv6.ip6_null_entry;
+               dst_hold(&rt->dst);
+               goto out;
+       }
 
+       if (f6i->fib6_nsiblings && fl6->flowi6_oif == 0)
+               f6i = fib6_multipath_select(net, f6i, fl6, fl6->flowi6_oif, skb,
+                                           flags);
        /* Search through exception table */
        rt = rt6_find_cached_rt(f6i, &fl6->daddr, &fl6->saddr);
        if (rt) {
-               if (ip6_hold_safe(net, &rt, true))
+               if (ip6_hold_safe(net, &rt))
                        dst_use_noref(&rt->dst, jiffies);
-       } else if (f6i == net->ipv6.fib6_null_entry) {
-               rt = net->ipv6.ip6_null_entry;
-               dst_hold(&rt->dst);
        } else {
                rt = ip6_create_rt_rcu(f6i);
        }
 
+out:
+       trace_fib6_table_lookup(net, f6i, table, fl6);
+
        rcu_read_unlock();
 
        return rt;
@@ -1242,7 +1243,7 @@ static struct rt6_info *rt6_get_pcpu_route(struct fib6_info *rt)
        pcpu_rt = *p;
 
        if (pcpu_rt)
-               ip6_hold_safe(NULL, &pcpu_rt, false);
+               ip6_hold_safe(NULL, &pcpu_rt);
 
        return pcpu_rt;
 }
@@ -1420,7 +1421,7 @@ static unsigned int fib6_mtu(const struct fib6_info *rt)
 
        mtu = min_t(unsigned int, mtu, IP6_MAX_MTU);
 
-       return mtu - lwtunnel_headroom(rt->fib6_nh.nh_lwtstate, mtu);
+       return mtu - lwtunnel_headroom(rt->fib6_nh.fib_nh_lws, mtu);
 }
 
 static int rt6_insert_exception(struct rt6_info *nrt,
@@ -1854,9 +1855,6 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table,
        rcu_read_lock();
 
        f6i = fib6_table_lookup(net, table, oif, fl6, strict);
-       if (f6i->fib6_nsiblings)
-               f6i = fib6_multipath_select(net, f6i, fl6, oif, skb, strict);
-
        if (f6i == net->ipv6.fib6_null_entry) {
                rt = net->ipv6.ip6_null_entry;
                rcu_read_unlock();
@@ -1864,16 +1862,19 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table,
                return rt;
        }
 
+       if (f6i->fib6_nsiblings)
+               f6i = fib6_multipath_select(net, f6i, fl6, oif, skb, strict);
+
        /*Search through exception table */
        rt = rt6_find_cached_rt(f6i, &fl6->daddr, &fl6->saddr);
        if (rt) {
-               if (ip6_hold_safe(net, &rt, true))
+               if (ip6_hold_safe(net, &rt))
                        dst_use_noref(&rt->dst, jiffies);
 
                rcu_read_unlock();
                return rt;
        } else if (unlikely((fl6->flowi6_flags & FLOWI_FLAG_KNOWN_NH) &&
-                           !(f6i->fib6_flags & RTF_GATEWAY))) {
+                           !f6i->fib6_nh.fib_nh_gw_family)) {
                /* Create a RTF_CACHE clone which will not be
                 * owned by the fib6 tree.  It is for the special case where
                 * the daddr in the skb during the neighbor look-up is different
@@ -2410,6 +2411,35 @@ void ip6_sk_dst_store_flow(struct sock *sk, struct dst_entry *dst,
                      NULL);
 }
 
+static bool ip6_redirect_nh_match(struct fib6_info *f6i,
+                                 struct fib6_nh *nh,
+                                 struct flowi6 *fl6,
+                                 const struct in6_addr *gw,
+                                 struct rt6_info **ret)
+{
+       if (nh->fib_nh_flags & RTNH_F_DEAD || !nh->fib_nh_gw_family ||
+           fl6->flowi6_oif != nh->fib_nh_dev->ifindex)
+               return false;
+
+       /* rt_cache's gateway might be different from its 'parent'
+        * in the case of an ip redirect.
+        * So we keep searching in the exception table if the gateway
+        * is different.
+        */
+       if (!ipv6_addr_equal(gw, &nh->fib_nh_gw6)) {
+               struct rt6_info *rt_cache;
+
+               rt_cache = rt6_find_cached_rt(f6i, &fl6->daddr, &fl6->saddr);
+               if (rt_cache &&
+                   ipv6_addr_equal(gw, &rt_cache->rt6i_gateway)) {
+                       *ret = rt_cache;
+                       return true;
+               }
+               return false;
+       }
+       return true;
+}
+
 /* Handle redirects */
 struct ip6rd_flowi {
        struct flowi6 fl6;
@@ -2423,7 +2453,7 @@ static struct rt6_info *__ip6_route_redirect(struct net *net,
                                             int flags)
 {
        struct ip6rd_flowi *rdfl = (struct ip6rd_flowi *)fl6;
-       struct rt6_info *ret = NULL, *rt_cache;
+       struct rt6_info *ret = NULL;
        struct fib6_info *rt;
        struct fib6_node *fn;
 
@@ -2441,34 +2471,13 @@ static struct rt6_info *__ip6_route_redirect(struct net *net,
        fn = fib6_node_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr);
 restart:
        for_each_fib6_node_rt_rcu(fn) {
-               if (rt->fib6_nh.nh_flags & RTNH_F_DEAD)
-                       continue;
                if (fib6_check_expired(rt))
                        continue;
                if (rt->fib6_flags & RTF_REJECT)
                        break;
-               if (!(rt->fib6_flags & RTF_GATEWAY))
-                       continue;
-               if (fl6->flowi6_oif != rt->fib6_nh.nh_dev->ifindex)
-                       continue;
-               /* rt_cache's gateway might be different from its 'parent'
-                * in the case of an ip redirect.
-                * So we keep searching in the exception table if the gateway
-                * is different.
-                */
-               if (!ipv6_addr_equal(&rdfl->gateway, &rt->fib6_nh.nh_gw)) {
-                       rt_cache = rt6_find_cached_rt(rt,
-                                                     &fl6->daddr,
-                                                     &fl6->saddr);
-                       if (rt_cache &&
-                           ipv6_addr_equal(&rdfl->gateway,
-                                           &rt_cache->rt6i_gateway)) {
-                               ret = rt_cache;
-                               break;
-                       }
-                       continue;
-               }
-               break;
+               if (ip6_redirect_nh_match(rt, &rt->fib6_nh, fl6,
+                                         &rdfl->gateway, &ret))
+                       goto out;
        }
 
        if (!rt)
@@ -2486,7 +2495,7 @@ static struct rt6_info *__ip6_route_redirect(struct net *net,
 
 out:
        if (ret)
-               ip6_hold_safe(net, &ret, true);
+               ip6_hold_safe(net, &ret);
        else
                ret = ip6_create_rt_rcu(rt);
 
@@ -2903,17 +2912,143 @@ static int ip6_validate_gw(struct net *net, struct fib6_config *cfg,
        return err;
 }
 
+static bool fib6_is_reject(u32 flags, struct net_device *dev, int addr_type)
+{
+       if ((flags & RTF_REJECT) ||
+           (dev && (dev->flags & IFF_LOOPBACK) &&
+            !(addr_type & IPV6_ADDR_LOOPBACK) &&
+            !(flags & RTF_LOCAL)))
+               return true;
+
+       return false;
+}
+
+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)
+{
+       struct net_device *dev = NULL;
+       struct inet6_dev *idev = NULL;
+       int addr_type;
+       int err;
+
+       fib6_nh->fib_nh_family = AF_INET6;
+
+       err = -ENODEV;
+       if (cfg->fc_ifindex) {
+               dev = dev_get_by_index(net, cfg->fc_ifindex);
+               if (!dev)
+                       goto out;
+               idev = in6_dev_get(dev);
+               if (!idev)
+                       goto out;
+       }
+
+       if (cfg->fc_flags & RTNH_F_ONLINK) {
+               if (!dev) {
+                       NL_SET_ERR_MSG(extack,
+                                      "Nexthop device required for onlink");
+                       goto out;
+               }
+
+               if (!(dev->flags & IFF_UP)) {
+                       NL_SET_ERR_MSG(extack, "Nexthop device is not up");
+                       err = -ENETDOWN;
+                       goto out;
+               }
+
+               fib6_nh->fib_nh_flags |= RTNH_F_ONLINK;
+       }
+
+       fib6_nh->fib_nh_weight = 1;
+
+       /* 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, dev, addr_type)) {
+               /* hold loopback dev/idev if we haven't done so. */
+               if (dev != net->loopback_dev) {
+                       if (dev) {
+                               dev_put(dev);
+                               in6_dev_put(idev);
+                       }
+                       dev = net->loopback_dev;
+                       dev_hold(dev);
+                       idev = in6_dev_get(dev);
+                       if (!idev) {
+                               err = -ENODEV;
+                               goto out;
+                       }
+               }
+               goto set_dev;
+       }
+
+       if (cfg->fc_flags & RTF_GATEWAY) {
+               err = ip6_validate_gw(net, cfg, &dev, &idev, extack);
+               if (err)
+                       goto out;
+
+               fib6_nh->fib_nh_gw6 = cfg->fc_gateway;
+               fib6_nh->fib_nh_gw_family = AF_INET6;
+       }
+
+       err = -ENODEV;
+       if (!dev)
+               goto out;
+
+       if (idev->cnf.disable_ipv6) {
+               NL_SET_ERR_MSG(extack, "IPv6 is disabled on nexthop device");
+               err = -EACCES;
+               goto out;
+       }
+
+       if (!(dev->flags & IFF_UP) && !cfg->fc_ignore_dev_down) {
+               NL_SET_ERR_MSG(extack, "Nexthop device is not up");
+               err = -ENETDOWN;
+               goto out;
+       }
+
+       if (!(cfg->fc_flags & (RTF_LOCAL | RTF_ANYCAST)) &&
+           !netif_carrier_ok(dev))
+               fib6_nh->fib_nh_flags |= RTNH_F_LINKDOWN;
+
+       err = fib_nh_common_init(&fib6_nh->nh_common, cfg->fc_encap,
+                                cfg->fc_encap_type, cfg, gfp_flags, extack);
+       if (err)
+               goto out;
+set_dev:
+       fib6_nh->fib_nh_dev = dev;
+       fib6_nh->fib_nh_oif = dev->ifindex;
+       err = 0;
+out:
+       if (idev)
+               in6_dev_put(idev);
+
+       if (err) {
+               lwtstate_put(fib6_nh->fib_nh_lws);
+               fib6_nh->fib_nh_lws = NULL;
+               if (dev)
+                       dev_put(dev);
+       }
+
+       return err;
+}
+
+void fib6_nh_release(struct fib6_nh *fib6_nh)
+{
+       fib_nh_common_release(&fib6_nh->nh_common);
+}
+
 static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg,
                                              gfp_t gfp_flags,
                                              struct netlink_ext_ack *extack)
 {
        struct net *net = cfg->fc_nlinfo.nl_net;
        struct fib6_info *rt = NULL;
-       struct net_device *dev = NULL;
-       struct inet6_dev *idev = NULL;
        struct fib6_table *table;
-       int addr_type;
        int err = -EINVAL;
+       int addr_type;
 
        /* RTF_PCPU is an internal flag; can not be set by userspace */
        if (cfg->fc_flags & RTF_PCPU) {
@@ -2947,33 +3082,6 @@ static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg,
                goto out;
        }
 #endif
-       if (cfg->fc_ifindex) {
-               err = -ENODEV;
-               dev = dev_get_by_index(net, cfg->fc_ifindex);
-               if (!dev)
-                       goto out;
-               idev = in6_dev_get(dev);
-               if (!idev)
-                       goto out;
-       }
-
-       if (cfg->fc_metric == 0)
-               cfg->fc_metric = IP6_RT_PRIO_USER;
-
-       if (cfg->fc_flags & RTNH_F_ONLINK) {
-               if (!dev) {
-                       NL_SET_ERR_MSG(extack,
-                                      "Nexthop device required for onlink");
-                       err = -ENODEV;
-                       goto out;
-               }
-
-               if (!(dev->flags & IFF_UP)) {
-                       NL_SET_ERR_MSG(extack, "Nexthop device is not up");
-                       err = -ENETDOWN;
-                       goto out;
-               }
-       }
 
        err = -ENOBUFS;
        if (cfg->fc_nlinfo.nlh &&
@@ -3017,18 +3125,10 @@ static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg,
                cfg->fc_protocol = RTPROT_BOOT;
        rt->fib6_protocol = cfg->fc_protocol;
 
-       addr_type = ipv6_addr_type(&cfg->fc_dst);
-
-       if (cfg->fc_encap) {
-               struct lwtunnel_state *lwtstate;
-
-               err = lwtunnel_build_state(cfg->fc_encap_type,
-                                          cfg->fc_encap, AF_INET6, cfg,
-                                          &lwtstate, extack);
-               if (err)
-                       goto out;
-               rt->fib6_nh.nh_lwtstate = lwtstate_get(lwtstate);
-       }
+       rt->fib6_table = table;
+       rt->fib6_metric = cfg->fc_metric;
+       rt->fib6_type = cfg->fc_type;
+       rt->fib6_flags = cfg->fc_flags & ~RTF_GATEWAY;
 
        ipv6_addr_prefix(&rt->fib6_dst.addr, &cfg->fc_dst, cfg->fc_dst_len);
        rt->fib6_dst.plen = cfg->fc_dst_len;
@@ -3039,62 +3139,20 @@ 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
-
-       rt->fib6_metric = cfg->fc_metric;
-       rt->fib6_nh.nh_weight = 1;
-
-       rt->fib6_type = cfg->fc_type;
+       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
+        * they would result in kernel looping; promote them to reject routes
         */
-       if ((cfg->fc_flags & RTF_REJECT) ||
-           (dev && (dev->flags & IFF_LOOPBACK) &&
-            !(addr_type & IPV6_ADDR_LOOPBACK) &&
-            !(cfg->fc_flags & RTF_LOCAL))) {
-               /* hold loopback dev/idev if we haven't done so. */
-               if (dev != net->loopback_dev) {
-                       if (dev) {
-                               dev_put(dev);
-                               in6_dev_put(idev);
-                       }
-                       dev = net->loopback_dev;
-                       dev_hold(dev);
-                       idev = in6_dev_get(dev);
-                       if (!idev) {
-                               err = -ENODEV;
-                               goto out;
-                       }
-               }
-               rt->fib6_flags = RTF_REJECT|RTF_NONEXTHOP;
-               goto install_route;
-       }
-
-       if (cfg->fc_flags & RTF_GATEWAY) {
-               err = ip6_validate_gw(net, cfg, &dev, &idev, extack);
-               if (err)
-                       goto out;
-
-               rt->fib6_nh.nh_gw = cfg->fc_gateway;
-       }
-
-       err = -ENODEV;
-       if (!dev)
-               goto out;
-
-       if (idev->cnf.disable_ipv6) {
-               NL_SET_ERR_MSG(extack, "IPv6 is disabled on nexthop device");
-               err = -EACCES;
-               goto out;
-       }
-
-       if (!(dev->flags & IFF_UP)) {
-               NL_SET_ERR_MSG(extack, "Nexthop device is not up");
-               err = -ENETDOWN;
-               goto out;
-       }
+       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);
+
                if (!ipv6_chk_addr(net, &cfg->fc_prefsrc, dev, 0)) {
                        NL_SET_ERR_MSG(extack, "Invalid source address");
                        err = -EINVAL;
@@ -3105,26 +3163,8 @@ static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg,
        } else
                rt->fib6_prefsrc.plen = 0;
 
-       rt->fib6_flags = cfg->fc_flags;
-
-install_route:
-       if (!(rt->fib6_flags & (RTF_LOCAL | RTF_ANYCAST)) &&
-           !netif_carrier_ok(dev))
-               rt->fib6_nh.nh_flags |= RTNH_F_LINKDOWN;
-       rt->fib6_nh.nh_flags |= (cfg->fc_flags & RTNH_F_ONLINK);
-       rt->fib6_nh.nh_dev = dev;
-       rt->fib6_table = table;
-
-       if (idev)
-               in6_dev_put(idev);
-
        return rt;
 out:
-       if (dev)
-               dev_put(dev);
-       if (idev)
-               in6_dev_put(idev);
-
        fib6_info_release(rt);
        return ERR_PTR(err);
 }
@@ -3265,6 +3305,8 @@ static int ip6_route_del(struct fib6_config *cfg,
 
        if (fn) {
                for_each_fib6_node_rt_rcu(fn) {
+                       struct fib6_nh *nh;
+
                        if (cfg->fc_flags & RTF_CACHE) {
                                int rc;
 
@@ -3279,12 +3321,14 @@ static int ip6_route_del(struct fib6_config *cfg,
                                }
                                continue;
                        }
+
+                       nh = &rt->fib6_nh;
                        if (cfg->fc_ifindex &&
-                           (!rt->fib6_nh.nh_dev ||
-                            rt->fib6_nh.nh_dev->ifindex != cfg->fc_ifindex))
+                           (!nh->fib_nh_dev ||
+                            nh->fib_nh_dev->ifindex != cfg->fc_ifindex))
                                continue;
                        if (cfg->fc_flags & RTF_GATEWAY &&
-                           !ipv6_addr_equal(&cfg->fc_gateway, &rt->fib6_nh.nh_gw))
+                           !ipv6_addr_equal(&cfg->fc_gateway, &nh->fib_nh_gw6))
                                continue;
                        if (cfg->fc_metric && cfg->fc_metric != rt->fib6_metric)
                                continue;
@@ -3455,11 +3499,12 @@ static struct fib6_info *rt6_get_route_info(struct net *net,
                goto out;
 
        for_each_fib6_node_rt_rcu(fn) {
-               if (rt->fib6_nh.nh_dev->ifindex != ifindex)
+               if (rt->fib6_nh.fib_nh_dev->ifindex != ifindex)
                        continue;
-               if ((rt->fib6_flags & (RTF_ROUTEINFO|RTF_GATEWAY)) != (RTF_ROUTEINFO|RTF_GATEWAY))
+               if (!(rt->fib6_flags & RTF_ROUTEINFO) ||
+                   !rt->fib6_nh.fib_nh_gw_family)
                        continue;
-               if (!ipv6_addr_equal(&rt->fib6_nh.nh_gw, gwaddr))
+               if (!ipv6_addr_equal(&rt->fib6_nh.fib_nh_gw6, gwaddr))
                        continue;
                if (!fib6_info_hold_safe(rt))
                        continue;
@@ -3517,9 +3562,11 @@ struct fib6_info *rt6_get_dflt_router(struct net *net,
 
        rcu_read_lock();
        for_each_fib6_node_rt_rcu(&table->tb6_root) {
-               if (dev == rt->fib6_nh.nh_dev &&
+               struct fib6_nh *nh = &rt->fib6_nh;
+
+               if (dev == nh->fib_nh_dev &&
                    ((rt->fib6_flags & (RTF_ADDRCONF | RTF_DEFAULT)) == (RTF_ADDRCONF | RTF_DEFAULT)) &&
-                   ipv6_addr_equal(&rt->fib6_nh.nh_gw, addr))
+                   ipv6_addr_equal(&nh->fib_nh_gw6, addr))
                        break;
        }
        if (rt && !fib6_info_hold_safe(rt))
@@ -3610,7 +3657,7 @@ static void rtmsg_to_fib6_config(struct net *net,
                .fc_table = l3mdev_fib_table_by_index(net, rtmsg->rtmsg_ifindex) ?
                         : RT6_TABLE_MAIN,
                .fc_ifindex = rtmsg->rtmsg_ifindex,
-               .fc_metric = rtmsg->rtmsg_metric,
+               .fc_metric = rtmsg->rtmsg_metric ? : IP6_RT_PRIO_USER,
                .fc_expires = rtmsg->rtmsg_info,
                .fc_dst_len = rtmsg->rtmsg_dst_len,
                .fc_src_len = rtmsg->rtmsg_src_len,
@@ -3721,36 +3768,26 @@ struct fib6_info *addrconf_f6i_alloc(struct net *net,
                                     const struct in6_addr *addr,
                                     bool anycast, gfp_t gfp_flags)
 {
-       u32 tb_id;
-       struct net_device *dev = idev->dev;
-       struct fib6_info *f6i;
-
-       f6i = fib6_info_alloc(gfp_flags);
-       if (!f6i)
-               return ERR_PTR(-ENOMEM);
+       struct fib6_config cfg = {
+               .fc_table = l3mdev_fib_table(idev->dev) ? : RT6_TABLE_LOCAL,
+               .fc_ifindex = idev->dev->ifindex,
+               .fc_flags = RTF_UP | RTF_ADDRCONF | RTF_NONEXTHOP,
+               .fc_dst = *addr,
+               .fc_dst_len = 128,
+               .fc_protocol = RTPROT_KERNEL,
+               .fc_nlinfo.nl_net = net,
+               .fc_ignore_dev_down = true,
+       };
 
-       f6i->fib6_metrics = ip_fib_metrics_init(net, NULL, 0, NULL);
-       f6i->dst_nocount = true;
-       f6i->dst_host = true;
-       f6i->fib6_protocol = RTPROT_KERNEL;
-       f6i->fib6_flags = RTF_UP | RTF_NONEXTHOP;
        if (anycast) {
-               f6i->fib6_type = RTN_ANYCAST;
-               f6i->fib6_flags |= RTF_ANYCAST;
+               cfg.fc_type = RTN_ANYCAST;
+               cfg.fc_flags |= RTF_ANYCAST;
        } else {
-               f6i->fib6_type = RTN_LOCAL;
-               f6i->fib6_flags |= RTF_LOCAL;
+               cfg.fc_type = RTN_LOCAL;
+               cfg.fc_flags |= RTF_LOCAL;
        }
 
-       f6i->fib6_nh.nh_gw = *addr;
-       dev_hold(dev);
-       f6i->fib6_nh.nh_dev = dev;
-       f6i->fib6_dst.addr = *addr;
-       f6i->fib6_dst.plen = 128;
-       tb_id = l3mdev_fib_table(idev->dev) ? : RT6_TABLE_LOCAL;
-       f6i->fib6_table = fib6_get_table(net, tb_id);
-
-       return f6i;
+       return ip6_route_info_create(&cfg, gfp_flags, NULL);
 }
 
 /* remove deleted ip from prefsrc entries */
@@ -3766,7 +3803,7 @@ 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.nh_dev == dev || !dev) &&
+       if (((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);
@@ -3788,7 +3825,7 @@ void rt6_remove_prefsrc(struct inet6_ifaddr *ifp)
        fib6_clean_all(net, fib6_remove_prefsrc, &adni);
 }
 
-#define RTF_RA_ROUTER          (RTF_ADDRCONF | RTF_DEFAULT | RTF_GATEWAY)
+#define RTF_RA_ROUTER          (RTF_ADDRCONF | RTF_DEFAULT)
 
 /* Remove routers and update dst entries when gateway turn into host. */
 static int fib6_clean_tohost(struct fib6_info *rt, void *arg)
@@ -3796,7 +3833,8 @@ static int fib6_clean_tohost(struct fib6_info *rt, void *arg)
        struct in6_addr *gateway = (struct in6_addr *)arg;
 
        if (((rt->fib6_flags & RTF_RA_ROUTER) == RTF_RA_ROUTER) &&
-           ipv6_addr_equal(gateway, &rt->fib6_nh.nh_gw)) {
+           rt->fib6_nh.fib_nh_gw_family &&
+           ipv6_addr_equal(gateway, &rt->fib6_nh.fib_nh_gw6)) {
                return -1;
        }
 
@@ -3844,9 +3882,9 @@ static struct fib6_info *rt6_multipath_first_sibling(const struct fib6_info *rt)
 
 static bool rt6_is_dead(const struct fib6_info *rt)
 {
-       if (rt->fib6_nh.nh_flags & RTNH_F_DEAD ||
-           (rt->fib6_nh.nh_flags & RTNH_F_LINKDOWN &&
-            fib6_ignore_linkdown(rt)))
+       if (rt->fib6_nh.fib_nh_flags & RTNH_F_DEAD ||
+           (rt->fib6_nh.fib_nh_flags & RTNH_F_LINKDOWN &&
+            ip6_ignore_linkdown(rt->fib6_nh.fib_nh_dev)))
                return true;
 
        return false;
@@ -3858,11 +3896,11 @@ static int rt6_multipath_total_weight(const struct fib6_info *rt)
        int total = 0;
 
        if (!rt6_is_dead(rt))
-               total += rt->fib6_nh.nh_weight;
+               total += rt->fib6_nh.fib_nh_weight;
 
        list_for_each_entry(iter, &rt->fib6_siblings, fib6_siblings) {
                if (!rt6_is_dead(iter))
-                       total += iter->fib6_nh.nh_weight;
+                       total += iter->fib6_nh.fib_nh_weight;
        }
 
        return total;
@@ -3873,11 +3911,11 @@ static void rt6_upper_bound_set(struct fib6_info *rt, int *weight, int total)
        int upper_bound = -1;
 
        if (!rt6_is_dead(rt)) {
-               *weight += rt->fib6_nh.nh_weight;
+               *weight += rt->fib6_nh.fib_nh_weight;
                upper_bound = DIV_ROUND_CLOSEST_ULL((u64) (*weight) << 31,
                                                    total) - 1;
        }
-       atomic_set(&rt->fib6_nh.nh_upper_bound, upper_bound);
+       atomic_set(&rt->fib6_nh.fib_nh_upper_bound, upper_bound);
 }
 
 static void rt6_multipath_upper_bound_set(struct fib6_info *rt, int total)
@@ -3920,8 +3958,9 @@ 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 && rt->fib6_nh.nh_dev == arg->dev) {
-               rt->fib6_nh.nh_flags &= ~arg->nh_flags;
+       if (rt != net->ipv6.fib6_null_entry &&
+           rt->fib6_nh.fib_nh_dev == arg->dev) {
+               rt->fib6_nh.fib_nh_flags &= ~arg->nh_flags;
                fib6_update_sernum_upto_root(net, rt);
                rt6_multipath_rebalance(rt);
        }
@@ -3949,10 +3988,10 @@ static bool rt6_multipath_uses_dev(const struct fib6_info *rt,
 {
        struct fib6_info *iter;
 
-       if (rt->fib6_nh.nh_dev == dev)
+       if (rt->fib6_nh.fib_nh_dev == dev)
                return true;
        list_for_each_entry(iter, &rt->fib6_siblings, fib6_siblings)
-               if (iter->fib6_nh.nh_dev == dev)
+               if (iter->fib6_nh.fib_nh_dev == dev)
                        return true;
 
        return false;
@@ -3973,12 +4012,12 @@ static unsigned int rt6_multipath_dead_count(const struct fib6_info *rt,
        struct fib6_info *iter;
        unsigned int dead = 0;
 
-       if (rt->fib6_nh.nh_dev == down_dev ||
-           rt->fib6_nh.nh_flags & RTNH_F_DEAD)
+       if (rt->fib6_nh.fib_nh_dev == down_dev ||
+           rt->fib6_nh.fib_nh_flags & RTNH_F_DEAD)
                dead++;
        list_for_each_entry(iter, &rt->fib6_siblings, fib6_siblings)
-               if (iter->fib6_nh.nh_dev == down_dev ||
-                   iter->fib6_nh.nh_flags & RTNH_F_DEAD)
+               if (iter->fib6_nh.fib_nh_dev == down_dev ||
+                   iter->fib6_nh.fib_nh_flags & RTNH_F_DEAD)
                        dead++;
 
        return dead;
@@ -3990,11 +4029,11 @@ static void rt6_multipath_nh_flags_set(struct fib6_info *rt,
 {
        struct fib6_info *iter;
 
-       if (rt->fib6_nh.nh_dev == dev)
-               rt->fib6_nh.nh_flags |= nh_flags;
+       if (rt->fib6_nh.fib_nh_dev == dev)
+               rt->fib6_nh.fib_nh_flags |= nh_flags;
        list_for_each_entry(iter, &rt->fib6_siblings, fib6_siblings)
-               if (iter->fib6_nh.nh_dev == dev)
-                       iter->fib6_nh.nh_flags |= nh_flags;
+               if (iter->fib6_nh.fib_nh_dev == dev)
+                       iter->fib6_nh.fib_nh_flags |= nh_flags;
 }
 
 /* called with write lock held for table with rt */
@@ -4009,12 +4048,12 @@ static int fib6_ifdown(struct fib6_info *rt, void *p_arg)
 
        switch (arg->event) {
        case NETDEV_UNREGISTER:
-               return rt->fib6_nh.nh_dev == dev ? -1 : 0;
+               return rt->fib6_nh.fib_nh_dev == dev ? -1 : 0;
        case NETDEV_DOWN:
                if (rt->should_flush)
                        return -1;
                if (!rt->fib6_nsiblings)
-                       return rt->fib6_nh.nh_dev == dev ? -1 : 0;
+                       return rt->fib6_nh.fib_nh_dev == dev ? -1 : 0;
                if (rt6_multipath_uses_dev(rt, dev)) {
                        unsigned int count;
 
@@ -4030,10 +4069,10 @@ static int fib6_ifdown(struct fib6_info *rt, void *p_arg)
                }
                return -2;
        case NETDEV_CHANGE:
-               if (rt->fib6_nh.nh_dev != dev ||
+               if (rt->fib6_nh.fib_nh_dev != dev ||
                    rt->fib6_flags & (RTF_LOCAL | RTF_ANYCAST))
                        break;
-               rt->fib6_nh.nh_flags |= RTNH_F_LINKDOWN;
+               rt->fib6_nh.fib_nh_flags |= RTNH_F_LINKDOWN;
                rt6_multipath_rebalance(rt);
                break;
        }
@@ -4089,7 +4128,7 @@ static int rt6_mtu_change_route(struct fib6_info *rt, void *p_arg)
           Since RFC 1981 doesn't include administrative MTU increase
           update PMTU increase is a MUST. (i.e. jumbo frame)
         */
-       if (rt->fib6_nh.nh_dev == arg->dev &&
+       if (rt->fib6_nh.fib_nh_dev == arg->dev &&
            !fib6_metric_locked(rt, RTAX_MTU)) {
                u32 mtu = rt->fib6_pmtu;
 
@@ -4380,7 +4419,7 @@ static int ip6_route_multipath_add(struct fib6_config *cfg,
                        goto cleanup;
                }
 
-               rt->fib6_nh.nh_weight = rtnh->rtnh_hops + 1;
+               rt->fib6_nh.fib_nh_weight = rtnh->rtnh_hops + 1;
 
                err = ip6_route_info_append(info->nl_net, &rt6_nh_list,
                                            rt, &r_cfg);
@@ -4530,6 +4569,9 @@ static int inet6_rtm_newroute(struct sk_buff *skb, struct nlmsghdr *nlh,
        if (err < 0)
                return err;
 
+       if (cfg.fc_metric == 0)
+               cfg.fc_metric = IP6_RT_PRIO_USER;
+
        if (cfg.fc_mp)
                return ip6_route_multipath_add(&cfg, extack);
        else
@@ -4544,7 +4586,7 @@ static size_t rt6_nlmsg_size(struct fib6_info *rt)
                nexthop_len = nla_total_size(0)  /* RTA_MULTIPATH */
                            + NLA_ALIGN(sizeof(struct rtnexthop))
                            + nla_total_size(16) /* RTA_GATEWAY */
-                           + lwtunnel_get_encap_size(rt->fib6_nh.nh_lwtstate);
+                           + lwtunnel_get_encap_size(rt->fib6_nh.fib_nh_lws);
 
                nexthop_len *= rt->fib6_nsiblings;
        }
@@ -4562,77 +4604,10 @@ static size_t rt6_nlmsg_size(struct fib6_info *rt)
               + nla_total_size(sizeof(struct rta_cacheinfo))
               + nla_total_size(TCP_CA_NAME_MAX) /* RTAX_CC_ALGO */
               + nla_total_size(1) /* RTA_PREF */
-              + lwtunnel_get_encap_size(rt->fib6_nh.nh_lwtstate)
+              + lwtunnel_get_encap_size(rt->fib6_nh.fib_nh_lws)
               + nexthop_len;
 }
 
-static int rt6_nexthop_info(struct sk_buff *skb, struct fib6_info *rt,
-                           unsigned int *flags, bool skip_oif)
-{
-       if (rt->fib6_nh.nh_flags & RTNH_F_DEAD)
-               *flags |= RTNH_F_DEAD;
-
-       if (rt->fib6_nh.nh_flags & RTNH_F_LINKDOWN) {
-               *flags |= RTNH_F_LINKDOWN;
-
-               rcu_read_lock();
-               if (fib6_ignore_linkdown(rt))
-                       *flags |= RTNH_F_DEAD;
-               rcu_read_unlock();
-       }
-
-       if (rt->fib6_flags & RTF_GATEWAY) {
-               if (nla_put_in6_addr(skb, RTA_GATEWAY, &rt->fib6_nh.nh_gw) < 0)
-                       goto nla_put_failure;
-       }
-
-       *flags |= (rt->fib6_nh.nh_flags & RTNH_F_ONLINK);
-       if (rt->fib6_nh.nh_flags & RTNH_F_OFFLOAD)
-               *flags |= RTNH_F_OFFLOAD;
-
-       /* not needed for multipath encoding b/c it has a rtnexthop struct */
-       if (!skip_oif && rt->fib6_nh.nh_dev &&
-           nla_put_u32(skb, RTA_OIF, rt->fib6_nh.nh_dev->ifindex))
-               goto nla_put_failure;
-
-       if (rt->fib6_nh.nh_lwtstate &&
-           lwtunnel_fill_encap(skb, rt->fib6_nh.nh_lwtstate) < 0)
-               goto nla_put_failure;
-
-       return 0;
-
-nla_put_failure:
-       return -EMSGSIZE;
-}
-
-/* add multipath next hop */
-static int rt6_add_nexthop(struct sk_buff *skb, struct fib6_info *rt)
-{
-       const struct net_device *dev = rt->fib6_nh.nh_dev;
-       struct rtnexthop *rtnh;
-       unsigned int flags = 0;
-
-       rtnh = nla_reserve_nohdr(skb, sizeof(*rtnh));
-       if (!rtnh)
-               goto nla_put_failure;
-
-       rtnh->rtnh_hops = rt->fib6_nh.nh_weight - 1;
-       rtnh->rtnh_ifindex = dev ? dev->ifindex : 0;
-
-       if (rt6_nexthop_info(skb, rt, &flags, true) < 0)
-               goto nla_put_failure;
-
-       rtnh->rtnh_flags = flags;
-
-       /* length of rtnetlink header + attributes */
-       rtnh->rtnh_len = nlmsg_get_pos(skb) - (void *)rtnh;
-
-       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,
@@ -4749,18 +4724,21 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb,
                if (!mp)
                        goto nla_put_failure;
 
-               if (rt6_add_nexthop(skb, rt) < 0)
+               if (fib_add_nexthop(skb, &rt->fib6_nh.nh_common,
+                                   rt->fib6_nh.fib_nh_weight) < 0)
                        goto nla_put_failure;
 
                list_for_each_entry_safe(sibling, next_sibling,
                                         &rt->fib6_siblings, fib6_siblings) {
-                       if (rt6_add_nexthop(skb, sibling) < 0)
+                       if (fib_add_nexthop(skb, &sibling->fib6_nh.nh_common,
+                                           sibling->fib6_nh.fib_nh_weight) < 0)
                                goto nla_put_failure;
                }
 
                nla_nest_end(skb, mp);
        } else {
-               if (rt6_nexthop_info(skb, rt, &rtm->rtm_flags, false) < 0)
+               if (fib_nexthop_info(skb, &rt->fib6_nh.nh_common,
+                                    &rtm->rtm_flags, false) < 0)
                        goto nla_put_failure;
        }
 
@@ -4787,7 +4765,7 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb,
 static bool fib6_info_uses_dev(const struct fib6_info *f6i,
                               const struct net_device *dev)
 {
-       if (f6i->fib6_nh.nh_dev == dev)
+       if (f6i->fib6_nh.fib_nh_dev == dev)
                return true;
 
        if (f6i->fib6_nsiblings) {
@@ -4795,7 +4773,7 @@ static bool fib6_info_uses_dev(const struct fib6_info *f6i,
 
                list_for_each_entry_safe(sibling, next_sibling,
                                         &f6i->fib6_siblings, fib6_siblings) {
-                       if (sibling->fib6_nh.nh_dev == dev)
+                       if (sibling->fib6_nh.fib_nh_dev == dev)
                                return true;
                }
        }
@@ -5080,7 +5058,7 @@ static int ip6_route_dev_notify(struct notifier_block *this,
                return NOTIFY_OK;
 
        if (event == NETDEV_REGISTER) {
-               net->ipv6.fib6_null_entry->fib6_nh.nh_dev = dev;
+               net->ipv6.fib6_null_entry->fib6_nh.fib_nh_dev = dev;
                net->ipv6.ip6_null_entry->dst.dev = dev;
                net->ipv6.ip6_null_entry->rt6i_idev = in6_dev_get(dev);
 #ifdef CONFIG_IPV6_MULTIPLE_TABLES
@@ -5415,7 +5393,7 @@ void __init ip6_route_init_special_entries(void)
        /* Registering of the loopback is done before this portion of code,
         * the loopback reference in rt6_info will not be taken, do it
         * manually for init_net */
-       init_net.ipv6.fib6_null_entry->fib6_nh.nh_dev = init_net.loopback_dev;
+       init_net.ipv6.fib6_null_entry->fib6_nh.fib_nh_dev = init_net.loopback_dev;
        init_net.ipv6.ip6_null_entry->dst.dev = init_net.loopback_dev;
        init_net.ipv6.ip6_null_entry->rt6i_idev = in6_dev_get(init_net.loopback_dev);
   #ifdef CONFIG_IPV6_MULTIPLE_TABLES