Merge tag 'v3.10.98' into update
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / net / ipv6 / route.c
index ad0aa6b0b86ae02f80b6b2184588605a3d5d7a6c..84dd53c1c63e5e8f0864077cf2477cbc316dd03b 100644 (file)
 #include <linux/sysctl.h>
 #endif
 
+enum rt6_nud_state {
+       RT6_NUD_FAIL_HARD = -2,
+       RT6_NUD_FAIL_SOFT = -1,
+       RT6_NUD_SUCCEED = 1
+};
+
 static struct rt6_info *ip6_rt_copy(struct rt6_info *ort,
                                    const struct in6_addr *dest);
 static struct dst_entry        *ip6_dst_check(struct dst_entry *dst, u32 cookie);
@@ -78,6 +84,8 @@ static int             ip6_dst_gc(struct dst_ops *ops);
 
 static int             ip6_pkt_discard(struct sk_buff *skb);
 static int             ip6_pkt_discard_out(struct sk_buff *skb);
+static int             ip6_pkt_prohibit(struct sk_buff *skb);
+static int             ip6_pkt_prohibit_out(struct sk_buff *skb);
 static void            ip6_link_failure(struct sk_buff *skb);
 static void            ip6_rt_update_pmtu(struct dst_entry *dst, struct sock *sk,
                                           struct sk_buff *skb, u32 mtu);
@@ -85,13 +93,12 @@ static void         rt6_do_redirect(struct dst_entry *dst, struct sock *sk,
                                        struct sk_buff *skb);
 
 #ifdef CONFIG_IPV6_ROUTE_INFO
-static struct rt6_info *rt6_add_route_info(struct net *net,
+static struct rt6_info *rt6_add_route_info(struct net_device *dev,
                                           const struct in6_addr *prefix, int prefixlen,
-                                          const struct in6_addr *gwaddr, int ifindex,
-                                          unsigned int pref);
-static struct rt6_info *rt6_get_route_info(struct net *net,
+                                          const struct in6_addr *gwaddr, unsigned int pref);
+static struct rt6_info *rt6_get_route_info(struct net_device *dev,
                                           const struct in6_addr *prefix, int prefixlen,
-                                          const struct in6_addr *gwaddr, int ifindex);
+                                          const struct in6_addr *gwaddr);
 #endif
 
 static u32 *ipv6_cow_metrics(struct dst_entry *dst, unsigned long old)
@@ -101,7 +108,7 @@ static u32 *ipv6_cow_metrics(struct dst_entry *dst, unsigned long old)
        u32 *p = NULL;
 
        if (!(rt->dst.flags & DST_HOST))
-               return NULL;
+               return dst_cow_metrics_generic(dst, old);
 
        peer = rt6_get_peer_create(rt);
        if (peer) {
@@ -227,9 +234,6 @@ static const struct rt6_info ip6_null_entry_template = {
 
 #ifdef CONFIG_IPV6_MULTIPLE_TABLES
 
-static int ip6_pkt_prohibit(struct sk_buff *skb);
-static int ip6_pkt_prohibit_out(struct sk_buff *skb);
-
 static const struct rt6_info ip6_prohibit_entry_template = {
        .dst = {
                .__refcnt       = ATOMIC_INIT(1),
@@ -467,6 +471,24 @@ out:
 }
 
 #ifdef CONFIG_IPV6_ROUTER_PREF
+struct __rt6_probe_work {
+       struct work_struct work;
+       struct in6_addr target;
+       struct net_device *dev;
+};
+
+static void rt6_probe_deferred(struct work_struct *w)
+{
+       struct in6_addr mcaddr;
+       struct __rt6_probe_work *work =
+               container_of(w, struct __rt6_probe_work, work);
+
+       addrconf_addr_solict_mult(&work->target, &mcaddr);
+       ndisc_send_ns(work->dev, NULL, &work->target, &mcaddr, NULL);
+       dev_put(work->dev);
+       kfree(w);
+}
+
 static void rt6_probe(struct rt6_info *rt)
 {
        struct neighbour *neigh;
@@ -490,17 +512,23 @@ static void rt6_probe(struct rt6_info *rt)
 
        if (!neigh ||
            time_after(jiffies, neigh->updated + rt->rt6i_idev->cnf.rtr_probe_interval)) {
-               struct in6_addr mcaddr;
-               struct in6_addr *target;
+               struct __rt6_probe_work *work;
+
+               work = kmalloc(sizeof(*work), GFP_ATOMIC);
 
-               if (neigh) {
+               if (neigh && work)
                        neigh->updated = jiffies;
+
+               if (neigh)
                        write_unlock(&neigh->lock);
-               }
 
-               target = (struct in6_addr *)&rt->rt6i_gateway;
-               addrconf_addr_solict_mult(target, &mcaddr);
-               ndisc_send_ns(rt->dst.dev, NULL, target, &mcaddr, NULL);
+               if (work) {
+                       INIT_WORK(&work->work, rt6_probe_deferred);
+                       work->target = rt->rt6i_gateway;
+                       dev_hold(rt->dst.dev);
+                       work->dev = rt->dst.dev;
+                       schedule_work(&work->work);
+               }
        } else {
 out:
                write_unlock(&neigh->lock);
@@ -527,26 +555,29 @@ static inline int rt6_check_dev(struct rt6_info *rt, int oif)
        return 0;
 }
 
-static inline bool rt6_check_neigh(struct rt6_info *rt)
+static inline enum rt6_nud_state rt6_check_neigh(struct rt6_info *rt)
 {
        struct neighbour *neigh;
-       bool ret = false;
+       enum rt6_nud_state ret = RT6_NUD_FAIL_HARD;
 
        if (rt->rt6i_flags & RTF_NONEXTHOP ||
            !(rt->rt6i_flags & RTF_GATEWAY))
-               return true;
+               return RT6_NUD_SUCCEED;
 
        rcu_read_lock_bh();
        neigh = __ipv6_neigh_lookup_noref(rt->dst.dev, &rt->rt6i_gateway);
        if (neigh) {
                read_lock(&neigh->lock);
                if (neigh->nud_state & NUD_VALID)
-                       ret = true;
+                       ret = RT6_NUD_SUCCEED;
 #ifdef CONFIG_IPV6_ROUTER_PREF
                else if (!(neigh->nud_state & NUD_FAILED))
-                       ret = true;
+                       ret = RT6_NUD_SUCCEED;
 #endif
                read_unlock(&neigh->lock);
+       } else {
+               ret = IS_ENABLED(CONFIG_IPV6_ROUTER_PREF) ?
+                     RT6_NUD_SUCCEED : RT6_NUD_FAIL_SOFT;
        }
        rcu_read_unlock_bh();
 
@@ -560,43 +591,52 @@ static int rt6_score_route(struct rt6_info *rt, int oif,
 
        m = rt6_check_dev(rt, oif);
        if (!m && (strict & RT6_LOOKUP_F_IFACE))
-               return -1;
+               return RT6_NUD_FAIL_HARD;
 #ifdef CONFIG_IPV6_ROUTER_PREF
        m |= IPV6_DECODE_PREF(IPV6_EXTRACT_PREF(rt->rt6i_flags)) << 2;
 #endif
-       if (!rt6_check_neigh(rt) && (strict & RT6_LOOKUP_F_REACHABLE))
-               return -1;
+       if (strict & RT6_LOOKUP_F_REACHABLE) {
+               int n = rt6_check_neigh(rt);
+               if (n < 0)
+                       return n;
+       }
        return m;
 }
 
 static struct rt6_info *find_match(struct rt6_info *rt, int oif, int strict,
-                                  int *mpri, struct rt6_info *match)
+                                  int *mpri, struct rt6_info *match,
+                                  bool *do_rr)
 {
        int m;
+       bool match_do_rr = false;
 
        if (rt6_check_expired(rt))
                goto out;
 
        m = rt6_score_route(rt, oif, strict);
-       if (m < 0)
+       if (m == RT6_NUD_FAIL_SOFT && !IS_ENABLED(CONFIG_IPV6_ROUTER_PREF)) {
+               match_do_rr = true;
+               m = 0; /* lowest valid score */
+       } else if (m < 0) {
                goto out;
+       }
+
+       if (strict & RT6_LOOKUP_F_REACHABLE)
+               rt6_probe(rt);
 
        if (m > *mpri) {
-               if (strict & RT6_LOOKUP_F_REACHABLE)
-                       rt6_probe(match);
+               *do_rr = match_do_rr;
                *mpri = m;
                match = rt;
-       } else if (strict & RT6_LOOKUP_F_REACHABLE) {
-               rt6_probe(rt);
        }
-
 out:
        return match;
 }
 
 static struct rt6_info *find_rr_leaf(struct fib6_node *fn,
                                     struct rt6_info *rr_head,
-                                    u32 metric, int oif, int strict)
+                                    u32 metric, int oif, int strict,
+                                    bool *do_rr)
 {
        struct rt6_info *rt, *match;
        int mpri = -1;
@@ -604,10 +644,10 @@ static struct rt6_info *find_rr_leaf(struct fib6_node *fn,
        match = NULL;
        for (rt = rr_head; rt && rt->rt6i_metric == metric;
             rt = rt->dst.rt6_next)
-               match = find_match(rt, oif, strict, &mpri, match);
+               match = find_match(rt, oif, strict, &mpri, match, do_rr);
        for (rt = fn->leaf; rt && rt != rr_head && rt->rt6i_metric == metric;
             rt = rt->dst.rt6_next)
-               match = find_match(rt, oif, strict, &mpri, match);
+               match = find_match(rt, oif, strict, &mpri, match, do_rr);
 
        return match;
 }
@@ -616,15 +656,16 @@ static struct rt6_info *rt6_select(struct fib6_node *fn, int oif, int strict)
 {
        struct rt6_info *match, *rt0;
        struct net *net;
+       bool do_rr = false;
 
        rt0 = fn->rr_ptr;
        if (!rt0)
                fn->rr_ptr = rt0 = fn->leaf;
 
-       match = find_rr_leaf(fn, rt0, rt0->rt6i_metric, oif, strict);
+       match = find_rr_leaf(fn, rt0, rt0->rt6i_metric, oif, strict,
+                            &do_rr);
 
-       if (!match &&
-           (strict & RT6_LOOKUP_F_REACHABLE)) {
+       if (do_rr) {
                struct rt6_info *next = rt0->dst.rt6_next;
 
                /* no entries matched; do round-robin */
@@ -643,7 +684,6 @@ static struct rt6_info *rt6_select(struct fib6_node *fn, int oif, int strict)
 int rt6_route_rcv(struct net_device *dev, u8 *opt, int len,
                  const struct in6_addr *gwaddr)
 {
-       struct net *net = dev_net(dev);
        struct route_info *rinfo = (struct route_info *) opt;
        struct in6_addr prefix_buf, *prefix;
        unsigned int pref;
@@ -685,8 +725,10 @@ int rt6_route_rcv(struct net_device *dev, u8 *opt, int len,
                prefix = &prefix_buf;
        }
 
-       rt = rt6_get_route_info(net, prefix, rinfo->prefix_len, gwaddr,
-                               dev->ifindex);
+       if (rinfo->prefix_len == 0)
+               rt = rt6_get_dflt_router(gwaddr, dev);
+       else
+               rt = rt6_get_route_info(dev, prefix, rinfo->prefix_len, gwaddr);
 
        if (rt && !lifetime) {
                ip6_del_rt(rt);
@@ -694,8 +736,7 @@ int rt6_route_rcv(struct net_device *dev, u8 *opt, int len,
        }
 
        if (!rt && lifetime)
-               rt = rt6_add_route_info(net, prefix, rinfo->prefix_len, gwaddr, dev->ifindex,
-                                       pref);
+               rt = rt6_add_route_info(dev, prefix, rinfo->prefix_len, gwaddr, pref);
        else if (rt)
                rt->rt6i_flags = RTF_ROUTEINFO |
                                 (rt->rt6i_flags & ~RTF_PREF_MASK) | RTF_PREF(pref);
@@ -829,7 +870,6 @@ static struct rt6_info *rt6_alloc_cow(struct rt6_info *ort,
                        if (ort->rt6i_dst.plen != 128 &&
                            ipv6_addr_equal(&ort->rt6i_dst.addr, daddr))
                                rt->rt6i_flags |= RTF_ANYCAST;
-                       rt->rt6i_gateway = *daddr;
                }
 
                rt->rt6i_flags |= RTF_CACHE;
@@ -1042,10 +1082,13 @@ static struct dst_entry *ip6_dst_check(struct dst_entry *dst, u32 cookie)
        if (rt->rt6i_genid != rt_genid(dev_net(rt->dst.dev)))
                return NULL;
 
-       if (rt->rt6i_node && (rt->rt6i_node->fn_sernum == cookie))
-               return dst;
+       if (!rt->rt6i_node || (rt->rt6i_node->fn_sernum != cookie))
+               return NULL;
 
-       return NULL;
+       if (rt6_check_expired(rt))
+               return NULL;
+
+       return dst;
 }
 
 static struct dst_entry *ip6_negative_advice(struct dst_entry *dst)
@@ -1074,10 +1117,13 @@ static void ip6_link_failure(struct sk_buff *skb)
 
        rt = (struct rt6_info *) skb_dst(skb);
        if (rt) {
-               if (rt->rt6i_flags & RTF_CACHE)
-                       rt6_update_expires(rt, 0);
-               else if (rt->rt6i_node && (rt->rt6i_flags & RTF_DEFAULT))
+               if (rt->rt6i_flags & RTF_CACHE) {
+                       dst_hold(&rt->dst);
+                       if (ip6_del_rt(rt))
+                               dst_free(&rt->dst);
+               } else if (rt->rt6i_node && (rt->rt6i_flags & RTF_DEFAULT)) {
                        rt->rt6i_node->fn_sernum = -1;
+               }
        }
 }
 
@@ -1091,19 +1137,16 @@ static void ip6_rt_update_pmtu(struct dst_entry *dst, struct sock *sk,
                struct net *net = dev_net(dst->dev);
 
                rt6->rt6i_flags |= RTF_MODIFIED;
-               if (mtu < IPV6_MIN_MTU) {
-                       u32 features = dst_metric(dst, RTAX_FEATURES);
+               if (mtu < IPV6_MIN_MTU)
                        mtu = IPV6_MIN_MTU;
-                       features |= RTAX_FEATURE_ALLFRAG;
-                       dst_metric_set(dst, RTAX_FEATURES, features);
-               }
+
                dst_metric_set(dst, RTAX_MTU, mtu);
                rt6_update_expires(rt6, net->ipv6.sysctl.ip6_rt_mtu_expires);
        }
 }
 
 void ip6_update_pmtu(struct sk_buff *skb, struct net *net, __be32 mtu,
-                    int oif, u32 mark)
+                    int oif, u32 mark, kuid_t uid)
 {
        const struct ipv6hdr *iph = (struct ipv6hdr *) skb->data;
        struct dst_entry *dst;
@@ -1111,11 +1154,12 @@ void ip6_update_pmtu(struct sk_buff *skb, struct net *net, __be32 mtu,
 
        memset(&fl6, 0, sizeof(fl6));
        fl6.flowi6_oif = oif;
-       fl6.flowi6_mark = mark;
+       fl6.flowi6_mark = mark ? mark : IP6_REPLY_MARK(net, skb->mark);
        fl6.flowi6_flags = 0;
        fl6.daddr = iph->daddr;
        fl6.saddr = iph->saddr;
        fl6.flowlabel = ip6_flowinfo(iph);
+       fl6.flowi6_uid = uid;
 
        dst = ip6_route_output(net, NULL, &fl6);
        if (!dst->error)
@@ -1127,7 +1171,7 @@ EXPORT_SYMBOL_GPL(ip6_update_pmtu);
 void ip6_sk_update_pmtu(struct sk_buff *skb, struct sock *sk, __be32 mtu)
 {
        ip6_update_pmtu(skb, sock_net(sk), mtu,
-                       sk->sk_bound_dev_if, sk->sk_mark);
+                       sk->sk_bound_dev_if, sk->sk_mark, sock_i_uid(sk));
 }
 EXPORT_SYMBOL_GPL(ip6_sk_update_pmtu);
 
@@ -1186,7 +1230,7 @@ static unsigned int ip6_mtu(const struct dst_entry *dst)
        unsigned int mtu = dst_metric_raw(dst, RTAX_MTU);
 
        if (mtu)
-               return mtu;
+               goto out;
 
        mtu = IPV6_MIN_MTU;
 
@@ -1196,7 +1240,8 @@ static unsigned int ip6_mtu(const struct dst_entry *dst)
                mtu = idev->cnf.mtu6;
        rcu_read_unlock();
 
-       return mtu;
+out:
+       return min_t(unsigned int, mtu, IP6_MAX_MTU);
 }
 
 static struct dst_entry *icmp6_dst_gc_list;
@@ -1223,6 +1268,7 @@ 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;
@@ -1285,7 +1331,6 @@ static void icmp6_clean_all(int (*func)(struct rt6_info *rt, void *arg),
 
 static int ip6_dst_gc(struct dst_ops *ops)
 {
-       unsigned long now = jiffies;
        struct net *net = container_of(ops, struct net, ipv6.ip6_dst_ops);
        int rt_min_interval = net->ipv6.sysctl.ip6_rt_gc_min_interval;
        int rt_max_size = net->ipv6.sysctl.ip6_rt_max_size;
@@ -1295,13 +1340,12 @@ static int ip6_dst_gc(struct dst_ops *ops)
        int entries;
 
        entries = dst_entries_get_fast(ops);
-       if (time_after(rt_last_gc + rt_min_interval, now) &&
+       if (time_after(rt_last_gc + rt_min_interval, jiffies) &&
            entries <= rt_max_size)
                goto out;
 
        net->ipv6.ip6_rt_gc_expire++;
-       fib6_run_gc(net->ipv6.ip6_rt_gc_expire, net);
-       net->ipv6.ip6_rt_last_gc = now;
+       fib6_run_gc(net->ipv6.ip6_rt_gc_expire, net, entries > rt_max_size);
        entries = dst_entries_get_slow(ops);
        if (entries < ops->gc_thresh)
                net->ipv6.ip6_rt_gc_expire = rt_gc_timeout>>1;
@@ -1377,7 +1421,7 @@ int ip6_route_add(struct fib6_config *cfg)
        if (!table)
                goto out;
 
-       rt = ip6_dst_alloc(net, NULL, DST_NOCOUNT, table);
+       rt = ip6_dst_alloc(net, NULL, (cfg->fc_flags & RTF_ADDRCONF) ? 0 : DST_NOCOUNT, table);
 
        if (!rt) {
                err = -ENOMEM;
@@ -1446,21 +1490,24 @@ int ip6_route_add(struct fib6_config *cfg)
                                goto out;
                        }
                }
-               rt->dst.output = ip6_pkt_discard_out;
-               rt->dst.input = ip6_pkt_discard;
                rt->rt6i_flags = RTF_REJECT|RTF_NONEXTHOP;
                switch (cfg->fc_type) {
                case RTN_BLACKHOLE:
                        rt->dst.error = -EINVAL;
+                       rt->dst.output = dst_discard;
+                       rt->dst.input = dst_discard;
                        break;
                case RTN_PROHIBIT:
                        rt->dst.error = -EACCES;
+                       rt->dst.output = ip6_pkt_prohibit_out;
+                       rt->dst.input = ip6_pkt_prohibit;
                        break;
                case RTN_THROW:
-                       rt->dst.error = -EAGAIN;
-                       break;
                default:
-                       rt->dst.error = -ENETUNREACH;
+                       rt->dst.error = (cfg->fc_type == RTN_THROW) ? -EAGAIN
+                                       : -ENETUNREACH;
+                       rt->dst.output = ip6_pkt_discard_out;
+                       rt->dst.input = ip6_pkt_discard;
                        break;
                }
                goto install_route;
@@ -1779,11 +1826,12 @@ static struct rt6_info *ip6_rt_copy(struct rt6_info *ort,
                        in6_dev_hold(rt->rt6i_idev);
                rt->dst.lastuse = jiffies;
 
-               rt->rt6i_gateway = ort->rt6i_gateway;
+               if (ort->rt6i_flags & RTF_GATEWAY)
+                       rt->rt6i_gateway = ort->rt6i_gateway;
+               else
+                       rt->rt6i_gateway = *dest;
                rt->rt6i_flags = ort->rt6i_flags;
-               if ((ort->rt6i_flags & (RTF_DEFAULT | RTF_ADDRCONF)) ==
-                   (RTF_DEFAULT | RTF_ADDRCONF))
-                       rt6_set_from(rt, ort);
+               rt6_set_from(rt, ort);
                rt->rt6i_metric = 0;
 
 #ifdef CONFIG_IPV6_SUBTREES
@@ -1796,15 +1844,16 @@ static struct rt6_info *ip6_rt_copy(struct rt6_info *ort,
 }
 
 #ifdef CONFIG_IPV6_ROUTE_INFO
-static struct rt6_info *rt6_get_route_info(struct net *net,
+static struct rt6_info *rt6_get_route_info(struct net_device *dev,
                                           const struct in6_addr *prefix, int prefixlen,
-                                          const struct in6_addr *gwaddr, int ifindex)
+                                          const struct in6_addr *gwaddr)
 {
        struct fib6_node *fn;
        struct rt6_info *rt = NULL;
        struct fib6_table *table;
 
-       table = fib6_get_table(net, RT6_TABLE_INFO);
+       table = fib6_get_table(dev_net(dev),
+                              addrconf_rt_table(dev, RT6_TABLE_INFO));
        if (!table)
                return NULL;
 
@@ -1814,7 +1863,7 @@ static struct rt6_info *rt6_get_route_info(struct net *net,
                goto out;
 
        for (rt = fn->leaf; rt; rt = rt->dst.rt6_next) {
-               if (rt->dst.dev->ifindex != ifindex)
+               if (rt->dst.dev->ifindex != dev->ifindex)
                        continue;
                if ((rt->rt6i_flags & (RTF_ROUTEINFO|RTF_GATEWAY)) != (RTF_ROUTEINFO|RTF_GATEWAY))
                        continue;
@@ -1828,21 +1877,20 @@ out:
        return rt;
 }
 
-static struct rt6_info *rt6_add_route_info(struct net *net,
+static struct rt6_info *rt6_add_route_info(struct net_device *dev,
                                           const struct in6_addr *prefix, int prefixlen,
-                                          const struct in6_addr *gwaddr, int ifindex,
-                                          unsigned int pref)
+                                          const struct in6_addr *gwaddr, unsigned int pref)
 {
        struct fib6_config cfg = {
-               .fc_table       = RT6_TABLE_INFO,
+               .fc_table       = addrconf_rt_table(dev, RT6_TABLE_INFO),
                .fc_metric      = IP6_RT_PRIO_USER,
-               .fc_ifindex     = ifindex,
+               .fc_ifindex     = dev->ifindex,
                .fc_dst_len     = prefixlen,
                .fc_flags       = RTF_GATEWAY | RTF_ADDRCONF | RTF_ROUTEINFO |
                                  RTF_UP | RTF_PREF(pref),
                .fc_nlinfo.portid = 0,
                .fc_nlinfo.nlh = NULL,
-               .fc_nlinfo.nl_net = net,
+               .fc_nlinfo.nl_net = dev_net(dev),
        };
 
        cfg.fc_dst = *prefix;
@@ -1854,7 +1902,7 @@ static struct rt6_info *rt6_add_route_info(struct net *net,
 
        ip6_route_add(&cfg);
 
-       return rt6_get_route_info(net, prefix, prefixlen, gwaddr, ifindex);
+       return rt6_get_route_info(dev, prefix, prefixlen, gwaddr);
 }
 #endif
 
@@ -1863,7 +1911,8 @@ struct rt6_info *rt6_get_dflt_router(const struct in6_addr *addr, struct net_dev
        struct rt6_info *rt;
        struct fib6_table *table;
 
-       table = fib6_get_table(dev_net(dev), RT6_TABLE_DFLT);
+       table = fib6_get_table(dev_net(dev),
+                              addrconf_rt_table(dev, RT6_TABLE_MAIN));
        if (!table)
                return NULL;
 
@@ -1885,7 +1934,7 @@ struct rt6_info *rt6_add_dflt_router(const struct in6_addr *gwaddr,
                                     unsigned int pref)
 {
        struct fib6_config cfg = {
-               .fc_table       = RT6_TABLE_DFLT,
+               .fc_table       = addrconf_rt_table(dev, RT6_TABLE_DFLT),
                .fc_metric      = IP6_RT_PRIO_USER,
                .fc_ifindex     = dev->ifindex,
                .fc_flags       = RTF_GATEWAY | RTF_ADDRCONF | RTF_DEFAULT |
@@ -1902,28 +1951,17 @@ struct rt6_info *rt6_add_dflt_router(const struct in6_addr *gwaddr,
        return rt6_get_dflt_router(gwaddr, dev);
 }
 
-void rt6_purge_dflt_routers(struct net *net)
-{
-       struct rt6_info *rt;
-       struct fib6_table *table;
 
-       /* NOTE: Keep consistent with rt6_get_dflt_router */
-       table = fib6_get_table(net, RT6_TABLE_DFLT);
-       if (!table)
-               return;
+int rt6_addrconf_purge(struct rt6_info *rt, void *arg) {
+       if (rt->rt6i_flags & (RTF_DEFAULT | RTF_ADDRCONF) &&
+           (!rt->rt6i_idev || rt->rt6i_idev->cnf.accept_ra != 2))
+               return -1;
+       return 0;
+}
 
-restart:
-       read_lock_bh(&table->tb6_lock);
-       for (rt = table->tb6_root.leaf; rt; rt = rt->dst.rt6_next) {
-               if (rt->rt6i_flags & (RTF_DEFAULT | RTF_ADDRCONF) &&
-                   (!rt->rt6i_idev || rt->rt6i_idev->cnf.accept_ra != 2)) {
-                       dst_hold(&rt->dst);
-                       read_unlock_bh(&table->tb6_lock);
-                       ip6_del_rt(rt);
-                       goto restart;
-               }
-       }
-       read_unlock_bh(&table->tb6_lock);
+void rt6_purge_dflt_routers(struct net *net)
+{
+       fib6_clean_all(net, rt6_addrconf_purge, 0, NULL);
 }
 
 static void rtmsg_to_fib6_config(struct net *net,
@@ -2022,8 +2060,6 @@ static int ip6_pkt_discard_out(struct sk_buff *skb)
        return ip6_pkt_drop(skb, ICMPV6_NOROUTE, IPSTATS_MIB_OUTNOROUTES);
 }
 
-#ifdef CONFIG_IPV6_MULTIPLE_TABLES
-
 static int ip6_pkt_prohibit(struct sk_buff *skb)
 {
        return ip6_pkt_drop(skb, ICMPV6_ADM_PROHIBITED, IPSTATS_MIB_INNOROUTES);
@@ -2035,8 +2071,6 @@ static int ip6_pkt_prohibit_out(struct sk_buff *skb)
        return ip6_pkt_drop(skb, ICMPV6_ADM_PROHIBITED, IPSTATS_MIB_OUTNOROUTES);
 }
 
-#endif
-
 /*
  *     Allocate a dst for local (unicast / anycast) address.
  */
@@ -2046,12 +2080,10 @@ struct rt6_info *addrconf_dst_alloc(struct inet6_dev *idev,
                                    bool anycast)
 {
        struct net *net = dev_net(idev->dev);
-       struct rt6_info *rt = ip6_dst_alloc(net, net->loopback_dev, 0, NULL);
-
-       if (!rt) {
-               net_warn_ratelimited("Maximum number of routes reached, consider increasing route/max_size\n");
+       struct rt6_info *rt = ip6_dst_alloc(net, net->loopback_dev,
+                                           DST_NOCOUNT, NULL);
+       if (!rt)
                return ERR_PTR(-ENOMEM);
-       }
 
        in6_dev_hold(idev);
 
@@ -2066,6 +2098,7 @@ struct rt6_info *addrconf_dst_alloc(struct inet6_dev *idev,
        else
                rt->rt6i_flags |= RTF_LOCAL;
 
+       rt->rt6i_gateway  = *addr;
        rt->rt6i_dst.addr = *addr;
        rt->rt6i_dst.plen = 128;
        rt->rt6i_table = fib6_get_table(net, RT6_TABLE_LOCAL);
@@ -2213,6 +2246,7 @@ static const struct nla_policy rtm_ipv6_policy[RTA_MAX+1] = {
        [RTA_PRIORITY]          = { .type = NLA_U32 },
        [RTA_METRICS]           = { .type = NLA_NESTED },
        [RTA_MULTIPATH]         = { .len = sizeof(struct rtnexthop) },
+       [RTA_UID]               = { .type = NLA_U32 },
 };
 
 static int rtm_to_fib6_config(struct sk_buff *skb, struct nlmsghdr *nlh,
@@ -2599,6 +2633,12 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr* nlh)
        if (tb[RTA_OIF])
                oif = nla_get_u32(tb[RTA_OIF]);
 
+       if (tb[RTA_UID])
+               fl6.flowi6_uid = make_kuid(current_user_ns(),
+                                          nla_get_u32(tb[RTA_UID]));
+       else
+               fl6.flowi6_uid = iif ? INVALID_UID : current_uid();
+
        if (iif) {
                struct net_device *dev;
                int flags = 0;
@@ -2801,7 +2841,7 @@ int ipv6_sysctl_rtcache_flush(ctl_table *ctl, int write,
        net = (struct net *)ctl->extra1;
        delay = net->ipv6.sysctl.flush_delay;
        proc_dointvec(ctl, write, buffer, lenp, ppos);
-       fib6_run_gc(delay <= 0 ? ~0UL : (unsigned long)delay, net);
+       fib6_run_gc(delay <= 0 ? 0 : (unsigned long)delay, net, delay > 0);
        return 0;
 }