]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - net/ipv6/route.c
Merge tag 'for-v4.13-2' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux...
[karo-tx-linux.git] / net / ipv6 / route.c
index 322bd62e688bfb5d2a88766a9537af51db0a69e8..4d30c96a819dee548ec34704a34b22774fac5da1 100644 (file)
@@ -128,7 +128,6 @@ static void rt6_uncached_list_add(struct rt6_info *rt)
 {
        struct uncached_list *ul = raw_cpu_ptr(&rt6_uncached_list);
 
-       rt->dst.flags |= DST_NOCACHE;
        rt->rt6i_uncached_list = ul;
 
        spin_lock_bh(&ul->lock);
@@ -354,7 +353,7 @@ static struct rt6_info *__ip6_dst_alloc(struct net *net,
                                        int flags)
 {
        struct rt6_info *rt = dst_alloc(&net->ipv6.ip6_dst_ops, dev,
-                                       0, DST_OBSOLETE_FORCE_CHK, flags);
+                                       1, DST_OBSOLETE_FORCE_CHK, flags);
 
        if (rt)
                rt6_info_init(rt);
@@ -381,7 +380,7 @@ struct rt6_info *ip6_dst_alloc(struct net *net,
                                *p =  NULL;
                        }
                } else {
-                       dst_destroy((struct dst_entry *)rt);
+                       dst_release_immediate(&rt->dst);
                        return NULL;
                }
        }
@@ -932,20 +931,21 @@ struct rt6_info *rt6_lookup(struct net *net, const struct in6_addr *daddr,
 EXPORT_SYMBOL(rt6_lookup);
 
 /* ip6_ins_rt is called with FREE table->tb6_lock.
  It takes new route entry, the addition fails by any reason the
-   route is freed. In any case, if caller does not hold it, it may
  be destroyed.
* It takes new route entry, the addition fails by any reason the
+ * route is released.
* Caller must hold dst before calling it.
  */
 
 static int __ip6_ins_rt(struct rt6_info *rt, struct nl_info *info,
-                       struct mx6_config *mxc)
+                       struct mx6_config *mxc,
+                       struct netlink_ext_ack *extack)
 {
        int err;
        struct fib6_table *table;
 
        table = rt->rt6i_table;
        write_lock_bh(&table->tb6_lock);
-       err = fib6_add(&table->tb6_root, rt, info, mxc);
+       err = fib6_add(&table->tb6_root, rt, info, mxc, extack);
        write_unlock_bh(&table->tb6_lock);
 
        return err;
@@ -956,7 +956,9 @@ int ip6_ins_rt(struct rt6_info *rt)
        struct nl_info info = { .nl_net = dev_net(rt->dst.dev), };
        struct mx6_config mxc = { .mx = NULL, };
 
-       return __ip6_ins_rt(rt, &info, &mxc);
+       /* Hold dst to account for the reference from the fib6 tree */
+       dst_hold(&rt->dst);
+       return __ip6_ins_rt(rt, &info, &mxc, NULL);
 }
 
 static struct rt6_info *ip6_rt_cache_alloc(struct rt6_info *ort,
@@ -1048,7 +1050,7 @@ static struct rt6_info *rt6_make_pcpu_route(struct rt6_info *rt)
                prev = cmpxchg(p, NULL, pcpu_rt);
                if (prev) {
                        /* If someone did it before us, return prev instead */
-                       dst_destroy(&pcpu_rt->dst);
+                       dst_release_immediate(&pcpu_rt->dst);
                        pcpu_rt = prev;
                }
        } else {
@@ -1058,7 +1060,7 @@ static struct rt6_info *rt6_make_pcpu_route(struct rt6_info *rt)
                 * since rt is going away anyway.  The next
                 * dst_check() will trigger a re-lookup.
                 */
-               dst_destroy(&pcpu_rt->dst);
+               dst_release_immediate(&pcpu_rt->dst);
                pcpu_rt = rt;
        }
        dst_hold(&pcpu_rt->dst);
@@ -1128,12 +1130,15 @@ redo_rt6_select:
                uncached_rt = ip6_rt_cache_alloc(rt, &fl6->daddr, NULL);
                dst_release(&rt->dst);
 
-               if (uncached_rt)
+               if (uncached_rt) {
+                       /* Uncached_rt's refcnt is taken during ip6_rt_cache_alloc()
+                        * No need for another dst_hold()
+                        */
                        rt6_uncached_list_add(uncached_rt);
-               else
+               } else {
                        uncached_rt = net->ipv6.ip6_null_entry;
-
-               dst_hold(&uncached_rt->dst);
+                       dst_hold(&uncached_rt->dst);
+               }
 
                trace_fib6_table_lookup(net, uncached_rt, table->tb6_id, fl6);
                return uncached_rt;
@@ -1244,9 +1249,11 @@ EXPORT_SYMBOL_GPL(ip6_route_output_flags);
 struct dst_entry *ip6_blackhole_route(struct net *net, struct dst_entry *dst_orig)
 {
        struct rt6_info *rt, *ort = (struct rt6_info *) dst_orig;
+       struct net_device *loopback_dev = net->loopback_dev;
        struct dst_entry *new = NULL;
 
-       rt = dst_alloc(&ip6_dst_blackhole_ops, ort->dst.dev, 1, DST_OBSOLETE_NONE, 0);
+       rt = dst_alloc(&ip6_dst_blackhole_ops, loopback_dev, 1,
+                      DST_OBSOLETE_NONE, 0);
        if (rt) {
                rt6_info_init(rt);
 
@@ -1256,10 +1263,8 @@ struct dst_entry *ip6_blackhole_route(struct net *net, struct dst_entry *dst_ori
                new->output = dst_discard_out;
 
                dst_copy_metrics(new, &ort->dst);
-               rt->rt6i_idev = ort->rt6i_idev;
-               if (rt->rt6i_idev)
-                       in6_dev_hold(rt->rt6i_idev);
 
+               rt->rt6i_idev = in6_dev_get(loopback_dev);
                rt->rt6i_gateway = ort->rt6i_gateway;
                rt->rt6i_flags = ort->rt6i_flags & ~RTF_PCPU;
                rt->rt6i_metric = 0;
@@ -1268,8 +1273,6 @@ struct dst_entry *ip6_blackhole_route(struct net *net, struct dst_entry *dst_ori
 #ifdef CONFIG_IPV6_SUBTREES
                memcpy(&rt->rt6i_src, &ort->rt6i_src, sizeof(struct rt6key));
 #endif
-
-               dst_free(new);
        }
 
        dst_release(dst_orig);
@@ -1322,7 +1325,7 @@ static struct dst_entry *ip6_dst_check(struct dst_entry *dst, u32 cookie)
        rt6_dst_from_metrics_check(rt);
 
        if (rt->rt6i_flags & RTF_PCPU ||
-           (unlikely(dst->flags & DST_NOCACHE) && rt->dst.from))
+           (unlikely(!list_empty(&rt->rt6i_uncached)) && rt->dst.from))
                return rt6_dst_from_check(rt, cookie);
        else
                return rt6_check(rt, cookie);
@@ -1355,8 +1358,8 @@ static void ip6_link_failure(struct sk_buff *skb)
        rt = (struct rt6_info *) skb_dst(skb);
        if (rt) {
                if (rt->rt6i_flags & RTF_CACHE) {
-                       dst_hold(&rt->dst);
-                       ip6_del_rt(rt);
+                       if (dst_hold_safe(&rt->dst))
+                               ip6_del_rt(rt);
                } else if (rt->rt6i_node && (rt->rt6i_flags & RTF_DEFAULT)) {
                        rt->rt6i_node->fn_sernum = -1;
                }
@@ -1420,6 +1423,10 @@ static void __ip6_rt_update_pmtu(struct dst_entry *dst, const struct sock *sk,
                         * invalidate the sk->sk_dst_cache.
                         */
                        ip6_ins_rt(nrt6);
+                       /* Release the reference taken in
+                        * ip6_rt_cache_alloc()
+                        */
+                       dst_release(&nrt6->dst);
                }
        }
 }
@@ -1648,9 +1655,6 @@ out:
        return mtu - lwtunnel_headroom(dst->lwtstate, mtu);
 }
 
-static struct dst_entry *icmp6_dst_gc_list;
-static DEFINE_SPINLOCK(icmp6_dst_lock);
-
 struct dst_entry *icmp6_dst_alloc(struct net_device *dev,
                                  struct flowi6 *fl6)
 {
@@ -1671,19 +1675,16 @@ struct dst_entry *icmp6_dst_alloc(struct net_device *dev,
 
        rt->dst.flags |= DST_HOST;
        rt->dst.output  = ip6_output;
-       atomic_set(&rt->dst.__refcnt, 1);
        rt->rt6i_gateway  = fl6->daddr;
        rt->rt6i_dst.addr = fl6->daddr;
        rt->rt6i_dst.plen = 128;
        rt->rt6i_idev     = idev;
        dst_metric_set(&rt->dst, RTAX_HOPLIMIT, 0);
 
-       spin_lock_bh(&icmp6_dst_lock);
-       rt->dst.next = icmp6_dst_gc_list;
-       icmp6_dst_gc_list = &rt->dst;
-       spin_unlock_bh(&icmp6_dst_lock);
-
-       fib6_force_start_gc(net);
+       /* Add this dst into uncached_list so that rt6_ifdown() can
+        * do proper release of the net_device
+        */
+       rt6_uncached_list_add(rt);
 
        dst = xfrm_lookup(net, &rt->dst, flowi6_to_flowi(fl6), NULL, 0);
 
@@ -1691,48 +1692,6 @@ out:
        return dst;
 }
 
-int icmp6_dst_gc(void)
-{
-       struct dst_entry *dst, **pprev;
-       int more = 0;
-
-       spin_lock_bh(&icmp6_dst_lock);
-       pprev = &icmp6_dst_gc_list;
-
-       while ((dst = *pprev) != NULL) {
-               if (!atomic_read(&dst->__refcnt)) {
-                       *pprev = dst->next;
-                       dst_free(dst);
-               } else {
-                       pprev = &dst->next;
-                       ++more;
-               }
-       }
-
-       spin_unlock_bh(&icmp6_dst_lock);
-
-       return more;
-}
-
-static void icmp6_clean_all(int (*func)(struct rt6_info *rt, void *arg),
-                           void *arg)
-{
-       struct dst_entry *dst, **pprev;
-
-       spin_lock_bh(&icmp6_dst_lock);
-       pprev = &icmp6_dst_gc_list;
-       while ((dst = *pprev) != NULL) {
-               struct rt6_info *rt = (struct rt6_info *) dst;
-               if (func(rt, arg)) {
-                       *pprev = dst->next;
-                       dst_free(dst);
-               } else {
-                       pprev = &dst->next;
-               }
-       }
-       spin_unlock_bh(&icmp6_dst_lock);
-}
-
 static int ip6_dst_gc(struct dst_ops *ops)
 {
        struct net *net = container_of(ops, struct net, ipv6.ip6_dst_ops);
@@ -1844,7 +1803,8 @@ static struct rt6_info *ip6_nh_lookup_table(struct net *net,
        return rt;
 }
 
-static struct rt6_info *ip6_route_info_create(struct fib6_config *cfg)
+static struct rt6_info *ip6_route_info_create(struct fib6_config *cfg,
+                                             struct netlink_ext_ack *extack)
 {
        struct net *net = cfg->fc_nlinfo.nl_net;
        struct rt6_info *rt = NULL;
@@ -1855,14 +1815,25 @@ static struct rt6_info *ip6_route_info_create(struct fib6_config *cfg)
        int err = -EINVAL;
 
        /* RTF_PCPU is an internal flag; can not be set by userspace */
-       if (cfg->fc_flags & RTF_PCPU)
+       if (cfg->fc_flags & RTF_PCPU) {
+               NL_SET_ERR_MSG(extack, "Userspace can not set RTF_PCPU");
                goto out;
+       }
 
-       if (cfg->fc_dst_len > 128 || cfg->fc_src_len > 128)
+       if (cfg->fc_dst_len > 128) {
+               NL_SET_ERR_MSG(extack, "Invalid prefix length");
+               goto out;
+       }
+       if (cfg->fc_src_len > 128) {
+               NL_SET_ERR_MSG(extack, "Invalid source address length");
                goto out;
+       }
 #ifndef CONFIG_IPV6_SUBTREES
-       if (cfg->fc_src_len)
+       if (cfg->fc_src_len) {
+               NL_SET_ERR_MSG(extack,
+                              "Specifying source address requires IPV6_SUBTREES to be enabled");
                goto out;
+       }
 #endif
        if (cfg->fc_ifindex) {
                err = -ENODEV;
@@ -1926,7 +1897,7 @@ static struct rt6_info *ip6_route_info_create(struct fib6_config *cfg)
 
                err = lwtunnel_build_state(cfg->fc_encap_type,
                                           cfg->fc_encap, AF_INET6, cfg,
-                                          &lwtstate);
+                                          &lwtstate, extack);
                if (err)
                        goto out;
                rt->dst.lwtstate = lwtstate_get(lwtstate);
@@ -2013,9 +1984,10 @@ static struct rt6_info *ip6_route_info_create(struct fib6_config *cfg)
                err = -EINVAL;
                if (ipv6_chk_addr_and_flags(net, gw_addr,
                                            gwa_type & IPV6_ADDR_LINKLOCAL ?
-                                           dev : NULL, 0, 0))
+                                           dev : NULL, 0, 0)) {
+                       NL_SET_ERR_MSG(extack, "Invalid gateway address");
                        goto out;
-
+               }
                rt->rt6i_gateway = *gw_addr;
 
                if (gwa_type != (IPV6_ADDR_LINKLOCAL|IPV6_ADDR_UNICAST)) {
@@ -2031,8 +2003,11 @@ static struct rt6_info *ip6_route_info_create(struct fib6_config *cfg)
                           addressing
                         */
                        if (!(gwa_type & (IPV6_ADDR_UNICAST |
-                                         IPV6_ADDR_MAPPED)))
+                                         IPV6_ADDR_MAPPED))) {
+                               NL_SET_ERR_MSG(extack,
+                                              "Invalid gateway address");
                                goto out;
+                       }
 
                        if (cfg->fc_table) {
                                grt = ip6_nh_lookup_table(net, cfg, gw_addr);
@@ -2072,8 +2047,14 @@ static struct rt6_info *ip6_route_info_create(struct fib6_config *cfg)
                                goto out;
                }
                err = -EINVAL;
-               if (!dev || (dev->flags & IFF_LOOPBACK))
+               if (!dev) {
+                       NL_SET_ERR_MSG(extack, "Egress device not specified");
+                       goto out;
+               } else if (dev->flags & IFF_LOOPBACK) {
+                       NL_SET_ERR_MSG(extack,
+                                      "Egress device can not be loopback device for this route");
                        goto out;
+               }
        }
 
        err = -ENODEV;
@@ -2082,6 +2063,7 @@ static struct rt6_info *ip6_route_info_create(struct fib6_config *cfg)
 
        if (!ipv6_addr_any(&cfg->fc_prefsrc)) {
                if (!ipv6_chk_addr(net, &cfg->fc_prefsrc, dev, 0)) {
+                       NL_SET_ERR_MSG(extack, "Invalid source address");
                        err = -EINVAL;
                        goto out;
                }
@@ -2106,18 +2088,19 @@ out:
        if (idev)
                in6_dev_put(idev);
        if (rt)
-               dst_free(&rt->dst);
+               dst_release_immediate(&rt->dst);
 
        return ERR_PTR(err);
 }
 
-int ip6_route_add(struct fib6_config *cfg)
+int ip6_route_add(struct fib6_config *cfg,
+                 struct netlink_ext_ack *extack)
 {
        struct mx6_config mxc = { .mx = NULL, };
        struct rt6_info *rt;
        int err;
 
-       rt = ip6_route_info_create(cfg);
+       rt = ip6_route_info_create(cfg, extack);
        if (IS_ERR(rt)) {
                err = PTR_ERR(rt);
                rt = NULL;
@@ -2128,14 +2111,14 @@ int ip6_route_add(struct fib6_config *cfg)
        if (err)
                goto out;
 
-       err = __ip6_ins_rt(rt, &cfg->fc_nlinfo, &mxc);
+       err = __ip6_ins_rt(rt, &cfg->fc_nlinfo, &mxc, extack);
 
        kfree(mxc.mx);
 
        return err;
 out:
        if (rt)
-               dst_free(&rt->dst);
+               dst_release_immediate(&rt->dst);
 
        return err;
 }
@@ -2146,8 +2129,7 @@ static int __ip6_del_rt(struct rt6_info *rt, struct nl_info *info)
        struct fib6_table *table;
        struct net *net = dev_net(rt->dst.dev);
 
-       if (rt == net->ipv6.ip6_null_entry ||
-           rt->dst.flags & DST_NOCACHE) {
+       if (rt == net->ipv6.ip6_null_entry) {
                err = -ENOENT;
                goto out;
        }
@@ -2222,7 +2204,8 @@ out_put:
        return err;
 }
 
-static int ip6_route_del(struct fib6_config *cfg)
+static int ip6_route_del(struct fib6_config *cfg,
+                        struct netlink_ext_ack *extack)
 {
        struct fib6_table *table;
        struct fib6_node *fn;
@@ -2230,8 +2213,10 @@ static int ip6_route_del(struct fib6_config *cfg)
        int err = -ESRCH;
 
        table = fib6_get_table(cfg->fc_nlinfo.nl_net, cfg->fc_table);
-       if (!table)
+       if (!table) {
+               NL_SET_ERR_MSG(extack, "FIB table does not exist");
                return err;
+       }
 
        read_lock_bh(&table->tb6_lock);
 
@@ -2369,7 +2354,7 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
        nrt->rt6i_gateway = *(struct in6_addr *)neigh->primary_key;
 
        if (ip6_ins_rt(nrt))
-               goto out;
+               goto out_release;
 
        netevent.old = &rt->dst;
        netevent.new = &nrt->dst;
@@ -2382,6 +2367,12 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
                ip6_del_rt(rt);
        }
 
+out_release:
+       /* Release the reference taken in
+        * ip6_rt_cache_alloc()
+        */
+       dst_release(&nrt->dst);
+
 out:
        neigh_release(neigh);
 }
@@ -2483,7 +2474,7 @@ static struct rt6_info *rt6_add_route_info(struct net *net,
        if (!prefixlen)
                cfg.fc_flags |= RTF_DEFAULT;
 
-       ip6_route_add(&cfg);
+       ip6_route_add(&cfg, NULL);
 
        return rt6_get_route_info(net, prefix, prefixlen, gwaddr, dev);
 }
@@ -2529,7 +2520,7 @@ struct rt6_info *rt6_add_dflt_router(const struct in6_addr *gwaddr,
 
        cfg.fc_gateway = *gwaddr;
 
-       if (!ip6_route_add(&cfg)) {
+       if (!ip6_route_add(&cfg, NULL)) {
                struct fib6_table *table;
 
                table = fib6_get_table(dev_net(dev), cfg.fc_table);
@@ -2622,10 +2613,10 @@ int ipv6_route_ioctl(struct net *net, unsigned int cmd, void __user *arg)
                rtnl_lock();
                switch (cmd) {
                case SIOCADDRT:
-                       err = ip6_route_add(&cfg);
+                       err = ip6_route_add(&cfg, NULL);
                        break;
                case SIOCDELRT:
-                       err = ip6_route_del(&cfg);
+                       err = ip6_route_del(&cfg, NULL);
                        break;
                default:
                        err = -EINVAL;
@@ -2729,9 +2720,6 @@ struct rt6_info *addrconf_dst_alloc(struct inet6_dev *idev,
        rt->rt6i_dst.plen = 128;
        tb_id = l3mdev_fib_table(idev->dev) ? : RT6_TABLE_LOCAL;
        rt->rt6i_table = fib6_get_table(net, tb_id);
-       rt->dst.flags |= DST_NOCACHE;
-
-       atomic_set(&rt->dst.__refcnt, 1);
 
        return rt;
 }
@@ -2819,7 +2807,6 @@ void rt6_ifdown(struct net *net, struct net_device *dev)
        };
 
        fib6_clean_all(net, fib6_ifdown, &adn);
-       icmp6_clean_all(fib6_ifdown, &adn);
        if (dev)
                rt6_uncached_list_flush_dev(net, dev);
 }
@@ -2904,7 +2891,8 @@ static const struct nla_policy rtm_ipv6_policy[RTA_MAX+1] = {
 };
 
 static int rtm_to_fib6_config(struct sk_buff *skb, struct nlmsghdr *nlh,
-                             struct fib6_config *cfg)
+                             struct fib6_config *cfg,
+                             struct netlink_ext_ack *extack)
 {
        struct rtmsg *rtm;
        struct nlattr *tb[RTA_MAX+1];
@@ -2988,7 +2976,7 @@ static int rtm_to_fib6_config(struct sk_buff *skb, struct nlmsghdr *nlh,
                cfg->fc_mp_len = nla_len(tb[RTA_MULTIPATH]);
 
                err = lwtunnel_valid_encap_type_attr(cfg->fc_mp,
-                                                    cfg->fc_mp_len);
+                                                    cfg->fc_mp_len, extack);
                if (err < 0)
                        goto errout;
        }
@@ -3007,7 +2995,7 @@ static int rtm_to_fib6_config(struct sk_buff *skb, struct nlmsghdr *nlh,
        if (tb[RTA_ENCAP_TYPE]) {
                cfg->fc_encap_type = nla_get_u16(tb[RTA_ENCAP_TYPE]);
 
-               err = lwtunnel_valid_encap_type(cfg->fc_encap_type);
+               err = lwtunnel_valid_encap_type(cfg->fc_encap_type, extack);
                if (err < 0)
                        goto errout;
        }
@@ -3048,17 +3036,11 @@ static int ip6_route_info_append(struct list_head *rt6_nh_list,
                                 struct rt6_info *rt, struct fib6_config *r_cfg)
 {
        struct rt6_nh *nh;
-       struct rt6_info *rtnh;
        int err = -EEXIST;
 
        list_for_each_entry(nh, rt6_nh_list, next) {
                /* check if rt6_info already exists */
-               rtnh = nh->rt6_info;
-
-               if (rtnh->dst.dev == rt->dst.dev &&
-                   rtnh->rt6i_idev == rt->rt6i_idev &&
-                   ipv6_addr_equal(&rtnh->rt6i_gateway,
-                                   &rt->rt6i_gateway))
+               if (rt6_duplicate_nexthop(nh->rt6_info, rt))
                        return err;
        }
 
@@ -3098,7 +3080,8 @@ static void ip6_route_mpath_notify(struct rt6_info *rt,
                inet6_rt_notify(RTM_NEWROUTE, rt, info, nlflags);
 }
 
-static int ip6_route_multipath_add(struct fib6_config *cfg)
+static int ip6_route_multipath_add(struct fib6_config *cfg,
+                                  struct netlink_ext_ack *extack)
 {
        struct rt6_info *rt_notif = NULL, *rt_last = NULL;
        struct nl_info *info = &cfg->fc_nlinfo;
@@ -3146,7 +3129,7 @@ static int ip6_route_multipath_add(struct fib6_config *cfg)
                                r_cfg.fc_encap_type = nla_get_u16(nla);
                }
 
-               rt = ip6_route_info_create(&r_cfg);
+               rt = ip6_route_info_create(&r_cfg, extack);
                if (IS_ERR(rt)) {
                        err = PTR_ERR(rt);
                        rt = NULL;
@@ -3155,7 +3138,7 @@ static int ip6_route_multipath_add(struct fib6_config *cfg)
 
                err = ip6_route_info_append(&rt6_nh_list, rt, &r_cfg);
                if (err) {
-                       dst_free(&rt->dst);
+                       dst_release_immediate(&rt->dst);
                        goto cleanup;
                }
 
@@ -3171,7 +3154,7 @@ static int ip6_route_multipath_add(struct fib6_config *cfg)
        err_nh = NULL;
        list_for_each_entry(nh, &rt6_nh_list, next) {
                rt_last = nh->rt6_info;
-               err = __ip6_ins_rt(nh->rt6_info, info, &nh->mxc);
+               err = __ip6_ins_rt(nh->rt6_info, info, &nh->mxc, extack);
                /* save reference to first route for notification */
                if (!rt_notif && !err)
                        rt_notif = nh->rt6_info;
@@ -3213,13 +3196,13 @@ add_errout:
        list_for_each_entry(nh, &rt6_nh_list, next) {
                if (err_nh == nh)
                        break;
-               ip6_route_del(&nh->r_cfg);
+               ip6_route_del(&nh->r_cfg, extack);
        }
 
 cleanup:
        list_for_each_entry_safe(nh, nh_safe, &rt6_nh_list, next) {
                if (nh->rt6_info)
-                       dst_free(&nh->rt6_info->dst);
+                       dst_release_immediate(&nh->rt6_info->dst);
                kfree(nh->mxc.mx);
                list_del(&nh->next);
                kfree(nh);
@@ -3228,7 +3211,8 @@ cleanup:
        return err;
 }
 
-static int ip6_route_multipath_del(struct fib6_config *cfg)
+static int ip6_route_multipath_del(struct fib6_config *cfg,
+                                  struct netlink_ext_ack *extack)
 {
        struct fib6_config r_cfg;
        struct rtnexthop *rtnh;
@@ -3255,7 +3239,7 @@ static int ip6_route_multipath_del(struct fib6_config *cfg)
                                r_cfg.fc_flags |= RTF_GATEWAY;
                        }
                }
-               err = ip6_route_del(&r_cfg);
+               err = ip6_route_del(&r_cfg, extack);
                if (err)
                        last_err = err;
 
@@ -3271,15 +3255,15 @@ static int inet6_rtm_delroute(struct sk_buff *skb, struct nlmsghdr *nlh,
        struct fib6_config cfg;
        int err;
 
-       err = rtm_to_fib6_config(skb, nlh, &cfg);
+       err = rtm_to_fib6_config(skb, nlh, &cfg, extack);
        if (err < 0)
                return err;
 
        if (cfg.fc_mp)
-               return ip6_route_multipath_del(&cfg);
+               return ip6_route_multipath_del(&cfg, extack);
        else {
                cfg.fc_delete_all_nh = 1;
-               return ip6_route_del(&cfg);
+               return ip6_route_del(&cfg, extack);
        }
 }
 
@@ -3289,14 +3273,14 @@ static int inet6_rtm_newroute(struct sk_buff *skb, struct nlmsghdr *nlh,
        struct fib6_config cfg;
        int err;
 
-       err = rtm_to_fib6_config(skb, nlh, &cfg);
+       err = rtm_to_fib6_config(skb, nlh, &cfg, extack);
        if (err < 0)
                return err;
 
        if (cfg.fc_mp)
-               return ip6_route_multipath_add(&cfg);
+               return ip6_route_multipath_add(&cfg, extack);
        else
-               return ip6_route_add(&cfg);
+               return ip6_route_add(&cfg, extack);
 }
 
 static size_t rt6_nlmsg_size(struct rt6_info *rt)
@@ -3577,11 +3561,13 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,
 {
        struct net *net = sock_net(in_skb->sk);
        struct nlattr *tb[RTA_MAX+1];
+       int err, iif = 0, oif = 0;
+       struct dst_entry *dst;
        struct rt6_info *rt;
        struct sk_buff *skb;
        struct rtmsg *rtm;
        struct flowi6 fl6;
-       int err, iif = 0, oif = 0;
+       bool fibmatch;
 
        err = nlmsg_parse(nlh, sizeof(*rtm), tb, RTA_MAX, rtm_ipv6_policy,
                          extack);
@@ -3592,6 +3578,7 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,
        memset(&fl6, 0, sizeof(fl6));
        rtm = nlmsg_data(nlh);
        fl6.flowlabel = ip6_make_flowinfo(rtm->rtm_tos, 0);
+       fibmatch = !!(rtm->rtm_flags & RTM_F_FIB_MATCH);
 
        if (tb[RTA_SRC]) {
                if (nla_len(tb[RTA_SRC]) < sizeof(struct in6_addr))
@@ -3637,12 +3624,23 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,
                if (!ipv6_addr_any(&fl6.saddr))
                        flags |= RT6_LOOKUP_F_HAS_SADDR;
 
-               rt = (struct rt6_info *)ip6_route_input_lookup(net, dev, &fl6,
-                                                              flags);
+               if (!fibmatch)
+                       dst = ip6_route_input_lookup(net, dev, &fl6, flags);
        } else {
                fl6.flowi6_oif = oif;
 
-               rt = (struct rt6_info *)ip6_route_output(net, NULL, &fl6);
+               if (!fibmatch)
+                       dst = ip6_route_output(net, NULL, &fl6);
+       }
+
+       if (fibmatch)
+               dst = ip6_route_lookup(net, &fl6, 0);
+
+       rt = container_of(dst, struct rt6_info, dst);
+       if (rt->dst.error) {
+               err = rt->dst.error;
+               ip6_rt_put(rt);
+               goto errout;
        }
 
        if (rt == net->ipv6.ip6_null_entry) {
@@ -3659,10 +3657,14 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,
        }
 
        skb_dst_set(skb, &rt->dst);
-
-       err = rt6_fill_node(net, skb, rt, &fl6.daddr, &fl6.saddr, iif,
-                           RTM_NEWROUTE, NETLINK_CB(in_skb).portid,
-                           nlh->nlmsg_seq, 0);
+       if (fibmatch)
+               err = rt6_fill_node(net, skb, rt, NULL, NULL, iif,
+                                   RTM_NEWROUTE, NETLINK_CB(in_skb).portid,
+                                   nlh->nlmsg_seq, 0);
+       else
+               err = rt6_fill_node(net, skb, rt, &fl6.daddr, &fl6.saddr, iif,
+                                   RTM_NEWROUTE, NETLINK_CB(in_skb).portid,
+                                   nlh->nlmsg_seq, 0);
        if (err < 0) {
                kfree_skb(skb);
                goto errout;