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

net/ipv6: separate handling of FIB entries from dst based routes

Last step before flipping the data type for FIB entries:
- use fib6_info_alloc to create FIB entries in ip6_route_info_create
  and addrconf_dst_alloc
- use fib6_info_release in place of dst_release, ip6_rt_put and
  rt6_release
- remove the dst_hold before calling __ip6_ins_rt or ip6_del_rt
- when purging routes, drop per-cpu routes
- replace inc and dec of rt6i_ref with fib6_info_hold and fib6_info_release
- use rt->from since it points to the FIB entry
- drop references to exception bucket, fib6_metrics and per-cpu from
  dst entries (those are relevant for fib entries only)
Signed-off-by: default avatarDavid Ahern <dsahern@gmail.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent a64efe14
...@@ -314,9 +314,7 @@ static inline u32 rt6_get_cookie(const struct rt6_info *rt) ...@@ -314,9 +314,7 @@ static inline u32 rt6_get_cookie(const struct rt6_info *rt)
if (rt->rt6i_flags & RTF_PCPU || if (rt->rt6i_flags & RTF_PCPU ||
(unlikely(!list_empty(&rt->rt6i_uncached)) && rt->from)) (unlikely(!list_empty(&rt->rt6i_uncached)) && rt->from))
rt = rt->from; rt6_get_cookie_safe(rt->from, &cookie);
rt6_get_cookie_safe(rt, &cookie);
return cookie; return cookie;
} }
......
...@@ -114,8 +114,7 @@ static inline int ip6_route_get_saddr(struct net *net, struct rt6_info *rt, ...@@ -114,8 +114,7 @@ static inline int ip6_route_get_saddr(struct net *net, struct rt6_info *rt,
unsigned int prefs, unsigned int prefs,
struct in6_addr *saddr) struct in6_addr *saddr)
{ {
struct inet6_dev *idev = struct inet6_dev *idev = rt ? rt->rt6i_idev : NULL;
rt ? ip6_dst_idev((struct dst_entry *)rt) : NULL;
int err = 0; int err = 0;
if (rt && rt->rt6i_prefsrc.plen) if (rt && rt->rt6i_prefsrc.plen)
......
...@@ -916,7 +916,6 @@ void inet6_ifa_finish_destroy(struct inet6_ifaddr *ifp) ...@@ -916,7 +916,6 @@ void inet6_ifa_finish_destroy(struct inet6_ifaddr *ifp)
pr_warn("Freeing alive inet6 address %p\n", ifp); pr_warn("Freeing alive inet6 address %p\n", ifp);
return; return;
} }
ip6_rt_put(ifp->rt);
kfree_rcu(ifp, rcu); kfree_rcu(ifp, rcu);
} }
...@@ -1102,8 +1101,8 @@ ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr, ...@@ -1102,8 +1101,8 @@ ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr,
inet6addr_notifier_call_chain(NETDEV_UP, ifa); inet6addr_notifier_call_chain(NETDEV_UP, ifa);
out: out:
if (unlikely(err < 0)) { if (unlikely(err < 0)) {
if (rt) fib6_info_release(rt);
ip6_rt_put(rt);
if (ifa) { if (ifa) {
if (ifa->idev) if (ifa->idev)
in6_dev_put(ifa->idev); in6_dev_put(ifa->idev);
...@@ -1191,7 +1190,7 @@ cleanup_prefix_route(struct inet6_ifaddr *ifp, unsigned long expires, bool del_r ...@@ -1191,7 +1190,7 @@ cleanup_prefix_route(struct inet6_ifaddr *ifp, unsigned long expires, bool del_r
else { else {
if (!(rt->rt6i_flags & RTF_EXPIRES)) if (!(rt->rt6i_flags & RTF_EXPIRES))
fib6_set_expires(rt, expires); fib6_set_expires(rt, expires);
ip6_rt_put(rt); fib6_info_release(rt);
} }
} }
} }
...@@ -2375,8 +2374,7 @@ static struct rt6_info *addrconf_get_prefix_route(const struct in6_addr *pfx, ...@@ -2375,8 +2374,7 @@ static struct rt6_info *addrconf_get_prefix_route(const struct in6_addr *pfx,
continue; continue;
if ((rt->rt6i_flags & noflags) != 0) if ((rt->rt6i_flags & noflags) != 0)
continue; continue;
if (!dst_hold_safe(&rt->dst)) fib6_info_hold(rt);
rt = NULL;
break; break;
} }
out: out:
...@@ -2687,7 +2685,7 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao) ...@@ -2687,7 +2685,7 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
addrconf_prefix_route(&pinfo->prefix, pinfo->prefix_len, addrconf_prefix_route(&pinfo->prefix, pinfo->prefix_len,
dev, expires, flags, GFP_ATOMIC); dev, expires, flags, GFP_ATOMIC);
} }
ip6_rt_put(rt); fib6_info_release(rt);
} }
/* Try to figure out our local address for this prefix */ /* Try to figure out our local address for this prefix */
...@@ -3361,7 +3359,7 @@ static int fixup_permanent_addr(struct net *net, ...@@ -3361,7 +3359,7 @@ static int fixup_permanent_addr(struct net *net,
ifp->rt = rt; ifp->rt = rt;
spin_unlock(&ifp->lock); spin_unlock(&ifp->lock);
ip6_rt_put(prev); fib6_info_release(prev);
} }
if (!(ifp->flags & IFA_F_NOPREFIXROUTE)) { if (!(ifp->flags & IFA_F_NOPREFIXROUTE)) {
...@@ -5636,8 +5634,8 @@ static void __ipv6_ifa_notify(int event, struct inet6_ifaddr *ifp) ...@@ -5636,8 +5634,8 @@ static void __ipv6_ifa_notify(int event, struct inet6_ifaddr *ifp)
ip6_del_rt(net, rt); ip6_del_rt(net, rt);
} }
if (ifp->rt) { if (ifp->rt) {
if (dst_hold_safe(&ifp->rt->dst))
ip6_del_rt(net, ifp->rt); ip6_del_rt(net, ifp->rt);
ifp->rt = NULL;
} }
rt_genid_bump_ipv6(net); rt_genid_bump_ipv6(net);
break; break;
......
...@@ -213,7 +213,7 @@ static void aca_put(struct ifacaddr6 *ac) ...@@ -213,7 +213,7 @@ static void aca_put(struct ifacaddr6 *ac)
{ {
if (refcount_dec_and_test(&ac->aca_refcnt)) { if (refcount_dec_and_test(&ac->aca_refcnt)) {
in6_dev_put(ac->aca_idev); in6_dev_put(ac->aca_idev);
dst_release(&ac->aca_rt->dst); fib6_info_release(ac->aca_rt);
kfree(ac); kfree(ac);
} }
} }
...@@ -231,6 +231,7 @@ static struct ifacaddr6 *aca_alloc(struct rt6_info *rt, ...@@ -231,6 +231,7 @@ static struct ifacaddr6 *aca_alloc(struct rt6_info *rt,
aca->aca_addr = *addr; aca->aca_addr = *addr;
in6_dev_hold(idev); in6_dev_hold(idev);
aca->aca_idev = idev; aca->aca_idev = idev;
fib6_info_hold(rt);
aca->aca_rt = rt; aca->aca_rt = rt;
aca->aca_users = 1; aca->aca_users = 1;
/* aca_tstamp should be updated upon changes */ /* aca_tstamp should be updated upon changes */
...@@ -274,7 +275,7 @@ int __ipv6_dev_ac_inc(struct inet6_dev *idev, const struct in6_addr *addr) ...@@ -274,7 +275,7 @@ int __ipv6_dev_ac_inc(struct inet6_dev *idev, const struct in6_addr *addr)
} }
aca = aca_alloc(rt, addr); aca = aca_alloc(rt, addr);
if (!aca) { if (!aca) {
ip6_rt_put(rt); fib6_info_release(rt);
err = -ENOMEM; err = -ENOMEM;
goto out; goto out;
} }
...@@ -330,7 +331,6 @@ int __ipv6_dev_ac_dec(struct inet6_dev *idev, const struct in6_addr *addr) ...@@ -330,7 +331,6 @@ int __ipv6_dev_ac_dec(struct inet6_dev *idev, const struct in6_addr *addr)
write_unlock_bh(&idev->lock); write_unlock_bh(&idev->lock);
addrconf_leave_solict(idev, &aca->aca_addr); addrconf_leave_solict(idev, &aca->aca_addr);
dst_hold(&aca->aca_rt->dst);
ip6_del_rt(dev_net(idev->dev), aca->aca_rt); ip6_del_rt(dev_net(idev->dev), aca->aca_rt);
aca_put(aca); aca_put(aca);
...@@ -358,7 +358,6 @@ void ipv6_ac_destroy_dev(struct inet6_dev *idev) ...@@ -358,7 +358,6 @@ void ipv6_ac_destroy_dev(struct inet6_dev *idev)
addrconf_leave_solict(idev, &aca->aca_addr); addrconf_leave_solict(idev, &aca->aca_addr);
dst_hold(&aca->aca_rt->dst);
ip6_del_rt(dev_net(idev->dev), aca->aca_rt); ip6_del_rt(dev_net(idev->dev), aca->aca_rt);
aca_put(aca); aca_put(aca);
......
...@@ -170,6 +170,7 @@ struct rt6_info *fib6_info_alloc(gfp_t gfp_flags) ...@@ -170,6 +170,7 @@ struct rt6_info *fib6_info_alloc(gfp_t gfp_flags)
void fib6_info_destroy(struct rt6_info *f6i) void fib6_info_destroy(struct rt6_info *f6i)
{ {
struct rt6_exception_bucket *bucket; struct rt6_exception_bucket *bucket;
struct dst_metrics *m;
WARN_ON(f6i->rt6i_node); WARN_ON(f6i->rt6i_node);
...@@ -201,6 +202,10 @@ void fib6_info_destroy(struct rt6_info *f6i) ...@@ -201,6 +202,10 @@ void fib6_info_destroy(struct rt6_info *f6i)
if (f6i->fib6_nh.nh_dev) if (f6i->fib6_nh.nh_dev)
dev_put(f6i->fib6_nh.nh_dev); dev_put(f6i->fib6_nh.nh_dev);
m = f6i->fib6_metrics;
if (m != &dst_default_metrics && refcount_dec_and_test(&m->refcnt))
kfree(m);
kfree(f6i); kfree(f6i);
} }
EXPORT_SYMBOL_GPL(fib6_info_destroy); EXPORT_SYMBOL_GPL(fib6_info_destroy);
...@@ -714,7 +719,7 @@ static struct fib6_node *fib6_add_1(struct net *net, ...@@ -714,7 +719,7 @@ static struct fib6_node *fib6_add_1(struct net *net,
/* clean up an intermediate node */ /* clean up an intermediate node */
if (!(fn->fn_flags & RTN_RTINFO)) { if (!(fn->fn_flags & RTN_RTINFO)) {
RCU_INIT_POINTER(fn->leaf, NULL); RCU_INIT_POINTER(fn->leaf, NULL);
rt6_release(leaf); fib6_info_release(leaf);
/* remove null_entry in the root node */ /* remove null_entry in the root node */
} else if (fn->fn_flags & RTN_TL_ROOT && } else if (fn->fn_flags & RTN_TL_ROOT &&
rcu_access_pointer(fn->leaf) == rcu_access_pointer(fn->leaf) ==
...@@ -898,12 +903,32 @@ static void fib6_purge_rt(struct rt6_info *rt, struct fib6_node *fn, ...@@ -898,12 +903,32 @@ static void fib6_purge_rt(struct rt6_info *rt, struct fib6_node *fn,
if (!(fn->fn_flags & RTN_RTINFO) && leaf == rt) { if (!(fn->fn_flags & RTN_RTINFO) && leaf == rt) {
new_leaf = fib6_find_prefix(net, table, fn); new_leaf = fib6_find_prefix(net, table, fn);
atomic_inc(&new_leaf->rt6i_ref); atomic_inc(&new_leaf->rt6i_ref);
rcu_assign_pointer(fn->leaf, new_leaf); rcu_assign_pointer(fn->leaf, new_leaf);
rt6_release(rt); fib6_info_release(rt);
} }
fn = rcu_dereference_protected(fn->parent, fn = rcu_dereference_protected(fn->parent,
lockdep_is_held(&table->tb6_lock)); lockdep_is_held(&table->tb6_lock));
} }
if (rt->rt6i_pcpu) {
int cpu;
/* release the reference to this fib entry from
* all of its cached pcpu routes
*/
for_each_possible_cpu(cpu) {
struct rt6_info **ppcpu_rt;
struct rt6_info *pcpu_rt;
ppcpu_rt = per_cpu_ptr(rt->rt6i_pcpu, cpu);
pcpu_rt = *ppcpu_rt;
if (pcpu_rt) {
fib6_info_release(pcpu_rt->from);
pcpu_rt->from = NULL;
}
}
}
} }
} }
...@@ -1099,7 +1124,7 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt, ...@@ -1099,7 +1124,7 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
fib6_purge_rt(iter, fn, info->nl_net); fib6_purge_rt(iter, fn, info->nl_net);
if (rcu_access_pointer(fn->rr_ptr) == iter) if (rcu_access_pointer(fn->rr_ptr) == iter)
fn->rr_ptr = NULL; fn->rr_ptr = NULL;
rt6_release(iter); fib6_info_release(iter);
if (nsiblings) { if (nsiblings) {
/* Replacing an ECMP route, remove all siblings */ /* Replacing an ECMP route, remove all siblings */
...@@ -1115,7 +1140,7 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt, ...@@ -1115,7 +1140,7 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
fib6_purge_rt(iter, fn, info->nl_net); fib6_purge_rt(iter, fn, info->nl_net);
if (rcu_access_pointer(fn->rr_ptr) == iter) if (rcu_access_pointer(fn->rr_ptr) == iter)
fn->rr_ptr = NULL; fn->rr_ptr = NULL;
rt6_release(iter); fib6_info_release(iter);
nsiblings--; nsiblings--;
info->nl_net->ipv6.rt6_stats->fib_rt_entries--; info->nl_net->ipv6.rt6_stats->fib_rt_entries--;
} else { } else {
...@@ -1183,9 +1208,6 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt, ...@@ -1183,9 +1208,6 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt,
int replace_required = 0; int replace_required = 0;
int sernum = fib6_new_sernum(info->nl_net); int sernum = fib6_new_sernum(info->nl_net);
if (WARN_ON_ONCE(!atomic_read(&rt->dst.__refcnt)))
return -EINVAL;
if (info->nlh) { if (info->nlh) {
if (!(info->nlh->nlmsg_flags & NLM_F_CREATE)) if (!(info->nlh->nlmsg_flags & NLM_F_CREATE))
allow_create = 0; allow_create = 0;
...@@ -1300,7 +1322,7 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt, ...@@ -1300,7 +1322,7 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt,
if (pn_leaf == rt) { if (pn_leaf == rt) {
pn_leaf = NULL; pn_leaf = NULL;
RCU_INIT_POINTER(pn->leaf, NULL); RCU_INIT_POINTER(pn->leaf, NULL);
atomic_dec(&rt->rt6i_ref); fib6_info_release(rt);
} }
if (!pn_leaf && !(pn->fn_flags & RTN_RTINFO)) { if (!pn_leaf && !(pn->fn_flags & RTN_RTINFO)) {
pn_leaf = fib6_find_prefix(info->nl_net, table, pn_leaf = fib6_find_prefix(info->nl_net, table,
...@@ -1312,7 +1334,7 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt, ...@@ -1312,7 +1334,7 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt,
info->nl_net->ipv6.fib6_null_entry; info->nl_net->ipv6.fib6_null_entry;
} }
#endif #endif
atomic_inc(&pn_leaf->rt6i_ref); fib6_info_hold(pn_leaf);
rcu_assign_pointer(pn->leaf, pn_leaf); rcu_assign_pointer(pn->leaf, pn_leaf);
} }
} }
...@@ -1334,10 +1356,6 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt, ...@@ -1334,10 +1356,6 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt,
(fn->fn_flags & RTN_TL_ROOT && (fn->fn_flags & RTN_TL_ROOT &&
!rcu_access_pointer(fn->leaf)))) !rcu_access_pointer(fn->leaf))))
fib6_repair_tree(info->nl_net, table, fn); fib6_repair_tree(info->nl_net, table, fn);
/* Always release dst as dst->__refcnt is guaranteed
* to be taken before entering this function
*/
dst_release_immediate(&rt->dst);
return err; return err;
} }
...@@ -1637,7 +1655,7 @@ static struct fib6_node *fib6_repair_tree(struct net *net, ...@@ -1637,7 +1655,7 @@ static struct fib6_node *fib6_repair_tree(struct net *net,
new_fn_leaf = net->ipv6.fib6_null_entry; new_fn_leaf = net->ipv6.fib6_null_entry;
} }
#endif #endif
atomic_inc(&new_fn_leaf->rt6i_ref); fib6_info_hold(new_fn_leaf);
rcu_assign_pointer(fn->leaf, new_fn_leaf); rcu_assign_pointer(fn->leaf, new_fn_leaf);
return pn; return pn;
} }
...@@ -1693,7 +1711,7 @@ static struct fib6_node *fib6_repair_tree(struct net *net, ...@@ -1693,7 +1711,7 @@ static struct fib6_node *fib6_repair_tree(struct net *net,
return pn; return pn;
RCU_INIT_POINTER(pn->leaf, NULL); RCU_INIT_POINTER(pn->leaf, NULL);
rt6_release(pn_leaf); fib6_info_release(pn_leaf);
fn = pn; fn = pn;
} }
} }
...@@ -1763,7 +1781,7 @@ static void fib6_del_route(struct fib6_table *table, struct fib6_node *fn, ...@@ -1763,7 +1781,7 @@ static void fib6_del_route(struct fib6_table *table, struct fib6_node *fn,
call_fib6_entry_notifiers(net, FIB_EVENT_ENTRY_DEL, rt, NULL); call_fib6_entry_notifiers(net, FIB_EVENT_ENTRY_DEL, rt, NULL);
if (!info->skip_notify) if (!info->skip_notify)
inet6_rt_notify(RTM_DELROUTE, rt, info, 0); inet6_rt_notify(RTM_DELROUTE, rt, info, 0);
rt6_release(rt); fib6_info_release(rt);
} }
/* Need to own table->tb6_lock */ /* Need to own table->tb6_lock */
...@@ -2261,9 +2279,8 @@ static int ipv6_route_seq_show(struct seq_file *seq, void *v) ...@@ -2261,9 +2279,8 @@ static int ipv6_route_seq_show(struct seq_file *seq, void *v)
dev = rt->fib6_nh.nh_dev; dev = rt->fib6_nh.nh_dev;
seq_printf(seq, " %08x %08x %08x %08x %8s\n", seq_printf(seq, " %08x %08x %08x %08x %8s\n",
rt->rt6i_metric, atomic_read(&rt->dst.__refcnt), rt->rt6i_metric, atomic_read(&rt->rt6i_ref), 0,
rt->dst.__use, rt->rt6i_flags, rt->rt6i_flags, dev ? dev->name : "");
dev ? dev->name : "");
iter->w.leaf = NULL; iter->w.leaf = NULL;
return 0; return 0;
} }
......
...@@ -968,7 +968,8 @@ static int ip6_dst_lookup_tail(struct net *net, const struct sock *sk, ...@@ -968,7 +968,8 @@ static int ip6_dst_lookup_tail(struct net *net, const struct sock *sk,
if (!had_dst) if (!had_dst)
*dst = ip6_route_output(net, sk, fl6); *dst = ip6_route_output(net, sk, fl6);
rt = (*dst)->error ? NULL : (struct rt6_info *)*dst; rt = (*dst)->error ? NULL : (struct rt6_info *)*dst;
err = ip6_route_get_saddr(net, rt, &fl6->daddr, err = ip6_route_get_saddr(net, rt ? rt->from : NULL,
&fl6->daddr,
sk ? inet6_sk(sk)->srcprefs : 0, sk ? inet6_sk(sk)->srcprefs : 0,
&fl6->saddr); &fl6->saddr);
if (err) if (err)
......
...@@ -1283,7 +1283,7 @@ static void ndisc_router_discovery(struct sk_buff *skb) ...@@ -1283,7 +1283,7 @@ static void ndisc_router_discovery(struct sk_buff *skb)
ND_PRINTK(0, err, ND_PRINTK(0, err,
"RA: %s got default router without neighbour\n", "RA: %s got default router without neighbour\n",
__func__); __func__);
ip6_rt_put(rt); fib6_info_release(rt);
return; return;
} }
} }
...@@ -1313,7 +1313,7 @@ static void ndisc_router_discovery(struct sk_buff *skb) ...@@ -1313,7 +1313,7 @@ static void ndisc_router_discovery(struct sk_buff *skb)
ND_PRINTK(0, err, ND_PRINTK(0, err,
"RA: %s got default router without neighbour\n", "RA: %s got default router without neighbour\n",
__func__); __func__);
ip6_rt_put(rt); fib6_info_release(rt);
return; return;
} }
neigh->flags |= NTF_ROUTER; neigh->flags |= NTF_ROUTER;
...@@ -1499,7 +1499,7 @@ static void ndisc_router_discovery(struct sk_buff *skb) ...@@ -1499,7 +1499,7 @@ static void ndisc_router_discovery(struct sk_buff *skb)
ND_PRINTK(2, warn, "RA: invalid RA options\n"); ND_PRINTK(2, warn, "RA: invalid RA options\n");
} }
out: out:
ip6_rt_put(rt); fib6_info_release(rt);
if (neigh) if (neigh)
neigh_release(neigh); neigh_release(neigh);
} }
......
...@@ -351,12 +351,10 @@ static void rt6_info_init(struct rt6_info *rt) ...@@ -351,12 +351,10 @@ static void rt6_info_init(struct rt6_info *rt)
memset(dst + 1, 0, sizeof(*rt) - sizeof(*dst)); memset(dst + 1, 0, sizeof(*rt) - sizeof(*dst));
INIT_LIST_HEAD(&rt->rt6i_siblings); INIT_LIST_HEAD(&rt->rt6i_siblings);
INIT_LIST_HEAD(&rt->rt6i_uncached); INIT_LIST_HEAD(&rt->rt6i_uncached);
rt->fib6_metrics = (struct dst_metrics *)&dst_default_metrics;
} }
/* allocate dst with ip6_dst_ops */ /* allocate dst with ip6_dst_ops */
static struct rt6_info *__ip6_dst_alloc(struct net *net, struct rt6_info *ip6_dst_alloc(struct net *net, struct net_device *dev,
struct net_device *dev,
int flags) int flags)
{ {
struct rt6_info *rt = dst_alloc(&net->ipv6.ip6_dst_ops, dev, struct rt6_info *rt = dst_alloc(&net->ipv6.ip6_dst_ops, dev,
...@@ -369,35 +367,15 @@ static struct rt6_info *__ip6_dst_alloc(struct net *net, ...@@ -369,35 +367,15 @@ static struct rt6_info *__ip6_dst_alloc(struct net *net,
return rt; return rt;
} }
struct rt6_info *ip6_dst_alloc(struct net *net,
struct net_device *dev,
int flags)
{
struct rt6_info *rt = __ip6_dst_alloc(net, dev, flags);
if (rt) {
rt->rt6i_pcpu = alloc_percpu_gfp(struct rt6_info *, GFP_ATOMIC);
if (!rt->rt6i_pcpu) {
dst_release_immediate(&rt->dst);
return NULL;
}
}
return rt;
}
EXPORT_SYMBOL(ip6_dst_alloc); EXPORT_SYMBOL(ip6_dst_alloc);
static void ip6_dst_destroy(struct dst_entry *dst) static void ip6_dst_destroy(struct dst_entry *dst)
{ {
struct rt6_info *rt = (struct rt6_info *)dst; struct rt6_info *rt = (struct rt6_info *)dst;
struct rt6_exception_bucket *bucket;
struct rt6_info *from = rt->from; struct rt6_info *from = rt->from;
struct inet6_dev *idev; struct inet6_dev *idev;
struct dst_metrics *m;
dst_destroy_metrics_generic(dst); dst_destroy_metrics_generic(dst);
free_percpu(rt->rt6i_pcpu);
rt6_uncached_list_del(rt); rt6_uncached_list_del(rt);
idev = rt->rt6i_idev; idev = rt->rt6i_idev;
...@@ -405,18 +383,9 @@ static void ip6_dst_destroy(struct dst_entry *dst) ...@@ -405,18 +383,9 @@ static void ip6_dst_destroy(struct dst_entry *dst)
rt->rt6i_idev = NULL; rt->rt6i_idev = NULL;
in6_dev_put(idev); in6_dev_put(idev);
} }
bucket = rcu_dereference_protected(rt->rt6i_exception_bucket, 1);
if (bucket) {
rt->rt6i_exception_bucket = NULL;
kfree(bucket);
}
m = rt->fib6_metrics;
if (m != &dst_default_metrics && refcount_dec_and_test(&m->refcnt))
kfree(m);
rt->from = NULL; rt->from = NULL;
dst_release(&from->dst); fib6_info_release(from);
} }
static void ip6_dst_ifdown(struct dst_entry *dst, struct net_device *dev, static void ip6_dst_ifdown(struct dst_entry *dst, struct net_device *dev,
...@@ -891,7 +860,7 @@ int rt6_route_rcv(struct net_device *dev, u8 *opt, int len, ...@@ -891,7 +860,7 @@ int rt6_route_rcv(struct net_device *dev, u8 *opt, int len,
else else
fib6_set_expires(rt, jiffies + HZ * lifetime); fib6_set_expires(rt, jiffies + HZ * lifetime);
ip6_rt_put(rt); fib6_info_release(rt);
} }
return 0; return 0;
} }
...@@ -1010,10 +979,8 @@ static void ip6_rt_init_dst(struct rt6_info *rt, struct rt6_info *ort) ...@@ -1010,10 +979,8 @@ static void ip6_rt_init_dst(struct rt6_info *rt, struct rt6_info *ort)
static void rt6_set_from(struct rt6_info *rt, struct rt6_info *from) static void rt6_set_from(struct rt6_info *rt, struct rt6_info *from)
{ {
BUG_ON(from->from);
rt->rt6i_flags &= ~RTF_EXPIRES; rt->rt6i_flags &= ~RTF_EXPIRES;
if (dst_hold_safe(&from->dst)) fib6_info_hold(from);
rt->from = from; rt->from = from;
dst_init_metrics(&rt->dst, from->fib6_metrics->metrics, true); dst_init_metrics(&rt->dst, from->fib6_metrics->metrics, true);
if (from->fib6_metrics != &dst_default_metrics) { if (from->fib6_metrics != &dst_default_metrics) {
...@@ -1084,7 +1051,7 @@ static struct rt6_info *ip6_create_rt_rcu(struct rt6_info *rt) ...@@ -1084,7 +1051,7 @@ static struct rt6_info *ip6_create_rt_rcu(struct rt6_info *rt)
struct net_device *dev = rt->fib6_nh.nh_dev; struct net_device *dev = rt->fib6_nh.nh_dev;
struct rt6_info *nrt; struct rt6_info *nrt;
nrt = __ip6_dst_alloc(dev_net(dev), dev, flags); nrt = ip6_dst_alloc(dev_net(dev), dev, flags);
if (nrt) if (nrt)
ip6_rt_copy_init(nrt, rt); ip6_rt_copy_init(nrt, rt);
...@@ -1203,8 +1170,6 @@ int ip6_ins_rt(struct net *net, struct rt6_info *rt) ...@@ -1203,8 +1170,6 @@ int ip6_ins_rt(struct net *net, struct rt6_info *rt)
{ {
struct nl_info info = { .nl_net = net, }; struct nl_info info = { .nl_net = net, };
/* Hold dst to account for the reference from the fib6 tree */
dst_hold(&rt->dst);
return __ip6_ins_rt(rt, &info, NULL); return __ip6_ins_rt(rt, &info, NULL);
} }
...@@ -1221,7 +1186,7 @@ static struct rt6_info *ip6_rt_cache_alloc(struct rt6_info *ort, ...@@ -1221,7 +1186,7 @@ static struct rt6_info *ip6_rt_cache_alloc(struct rt6_info *ort,
rcu_read_lock(); rcu_read_lock();
dev = ip6_rt_get_dev_rcu(ort); dev = ip6_rt_get_dev_rcu(ort);
rt = __ip6_dst_alloc(dev_net(dev), dev, 0); rt = ip6_dst_alloc(dev_net(dev), dev, 0);
rcu_read_unlock(); rcu_read_unlock();
if (!rt) if (!rt)
return NULL; return NULL;
...@@ -1256,7 +1221,7 @@ static struct rt6_info *ip6_rt_pcpu_alloc(struct rt6_info *rt) ...@@ -1256,7 +1221,7 @@ static struct rt6_info *ip6_rt_pcpu_alloc(struct rt6_info *rt)
rcu_read_lock(); rcu_read_lock();
dev = ip6_rt_get_dev_rcu(rt); dev = ip6_rt_get_dev_rcu(rt);
pcpu_rt = __ip6_dst_alloc(dev_net(dev), dev, flags); pcpu_rt = ip6_dst_alloc(dev_net(dev), dev, flags);
rcu_read_unlock(); rcu_read_unlock();
if (!pcpu_rt) if (!pcpu_rt)
return NULL; return NULL;
...@@ -1317,7 +1282,7 @@ static void rt6_remove_exception(struct rt6_exception_bucket *bucket, ...@@ -1317,7 +1282,7 @@ static void rt6_remove_exception(struct rt6_exception_bucket *bucket,
net = dev_net(rt6_ex->rt6i->dst.dev); net = dev_net(rt6_ex->rt6i->dst.dev);
rt6_ex->rt6i->rt6i_node = NULL; rt6_ex->rt6i->rt6i_node = NULL;
hlist_del_rcu(&rt6_ex->hlist); hlist_del_rcu(&rt6_ex->hlist);
rt6_release(rt6_ex->rt6i); ip6_rt_put(rt6_ex->rt6i);
kfree_rcu(rt6_ex, rcu); kfree_rcu(rt6_ex, rcu);
WARN_ON_ONCE(!bucket->depth); WARN_ON_ONCE(!bucket->depth);
bucket->depth--; bucket->depth--;
...@@ -1907,17 +1872,11 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table, ...@@ -1907,17 +1872,11 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table,
struct rt6_info *uncached_rt; struct rt6_info *uncached_rt;
if (ip6_hold_safe(net, &f6i, true)) { fib6_info_hold(f6i);
dst_use_noref(&f6i->dst, jiffies);
} else {
rcu_read_unlock();
uncached_rt = f6i;
goto uncached_rt_out;
}
rcu_read_unlock(); rcu_read_unlock();
uncached_rt = ip6_rt_cache_alloc(f6i, &fl6->daddr, NULL); uncached_rt = ip6_rt_cache_alloc(f6i, &fl6->daddr, NULL);
dst_release(&rt->dst); fib6_info_release(f6i);
if (uncached_rt) { if (uncached_rt) {
/* Uncached_rt's refcnt is taken during ip6_rt_cache_alloc() /* Uncached_rt's refcnt is taken during ip6_rt_cache_alloc()
...@@ -1930,7 +1889,6 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table, ...@@ -1930,7 +1889,6 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table,
dst_hold(&uncached_rt->dst); dst_hold(&uncached_rt->dst);
} }
uncached_rt_out:
trace_fib6_table_lookup(net, uncached_rt, table, fl6); trace_fib6_table_lookup(net, uncached_rt, table, fl6);
return uncached_rt; return uncached_rt;
...@@ -1939,24 +1897,12 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table, ...@@ -1939,24 +1897,12 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table,
struct rt6_info *pcpu_rt; struct rt6_info *pcpu_rt;
dst_use_noref(&f6i->dst, jiffies);
local_bh_disable(); local_bh_disable();
pcpu_rt = rt6_get_pcpu_route(f6i); pcpu_rt = rt6_get_pcpu_route(f6i);
if (!pcpu_rt) { if (!pcpu_rt)
/* atomic_inc_not_zero() is needed when using rcu */
if (atomic_inc_not_zero(&f6i->rt6i_ref)) {
/* No dst_hold() on rt is needed because grabbing
* rt->rt6i_ref makes sure rt can't be released.
*/
pcpu_rt = rt6_make_pcpu_route(net, f6i); pcpu_rt = rt6_make_pcpu_route(net, f6i);
rt6_release(f6i);
} else {
/* rt is already removed from tree */
pcpu_rt = net->ipv6.ip6_null_entry;
dst_hold(&pcpu_rt->dst);
}
}
local_bh_enable(); local_bh_enable();
rcu_read_unlock(); rcu_read_unlock();
trace_fib6_table_lookup(net, pcpu_rt, table, fl6); trace_fib6_table_lookup(net, pcpu_rt, table, fl6);
...@@ -2193,11 +2139,26 @@ struct dst_entry *ip6_blackhole_route(struct net *net, struct dst_entry *dst_ori ...@@ -2193,11 +2139,26 @@ struct dst_entry *ip6_blackhole_route(struct net *net, struct dst_entry *dst_ori
* Destination cache support functions * Destination cache support functions
*/ */
static bool fib6_check(struct rt6_info *f6i, u32 cookie)
{
u32 rt_cookie = 0;
if ((f6i && !rt6_get_cookie_safe(f6i, &rt_cookie)) ||
rt_cookie != cookie)
return false;
if (fib6_check_expired(f6i))
return false;
return true;
}
static struct dst_entry *rt6_check(struct rt6_info *rt, u32 cookie) static struct dst_entry *rt6_check(struct rt6_info *rt, u32 cookie)
{ {
u32 rt_cookie = 0; u32 rt_cookie = 0;
if (!rt6_get_cookie_safe(rt, &rt_cookie) || rt_cookie != cookie) if ((rt->from && !rt6_get_cookie_safe(rt->from, &rt_cookie)) ||
rt_cookie != cookie)
return NULL; return NULL;
if (rt6_check_expired(rt)) if (rt6_check_expired(rt))
...@@ -2210,7 +2171,7 @@ static struct dst_entry *rt6_dst_from_check(struct rt6_info *rt, u32 cookie) ...@@ -2210,7 +2171,7 @@ static struct dst_entry *rt6_dst_from_check(struct rt6_info *rt, u32 cookie)
{ {
if (!__rt6_check_expired(rt) && if (!__rt6_check_expired(rt) &&
rt->dst.obsolete == DST_OBSOLETE_FORCE_CHK && rt->dst.obsolete == DST_OBSOLETE_FORCE_CHK &&
rt6_check(rt->from, cookie)) fib6_check(rt->from, cookie))
return &rt->dst; return &rt->dst;
else else
return NULL; return NULL;
...@@ -2241,7 +2202,7 @@ static struct dst_entry *ip6_negative_advice(struct dst_entry *dst) ...@@ -2241,7 +2202,7 @@ static struct dst_entry *ip6_negative_advice(struct dst_entry *dst)
if (rt) { if (rt) {
if (rt->rt6i_flags & RTF_CACHE) { if (rt->rt6i_flags & RTF_CACHE) {
if (rt6_check_expired(rt)) { if (rt6_check_expired(rt)) {
ip6_del_rt(dev_net(dst->dev), rt); rt6_remove_exception_rt(rt);
dst = NULL; dst = NULL;
} }
} else { } else {
...@@ -2262,12 +2223,12 @@ static void ip6_link_failure(struct sk_buff *skb) ...@@ -2262,12 +2223,12 @@ static void ip6_link_failure(struct sk_buff *skb)
if (rt) { if (rt) {
if (rt->rt6i_flags & RTF_CACHE) { if (rt->rt6i_flags & RTF_CACHE) {
if (dst_hold_safe(&rt->dst)) if (dst_hold_safe(&rt->dst))
ip6_del_rt(dev_net(rt->dst.dev), rt); rt6_remove_exception_rt(rt);
} else { } else if (rt->from) {
struct fib6_node *fn; struct fib6_node *fn;
rcu_read_lock(); rcu_read_lock();
fn = rcu_dereference(rt->rt6i_node); fn = rcu_dereference(rt->from->rt6i_node);
if (fn && (rt->rt6i_flags & RTF_DEFAULT)) if (fn && (rt->rt6i_flags & RTF_DEFAULT))
fn->fn_sernum = -1; fn->fn_sernum = -1;
rcu_read_unlock(); rcu_read_unlock();
...@@ -2949,13 +2910,13 @@ static struct rt6_info *ip6_route_info_create(struct fib6_config *cfg, ...@@ -2949,13 +2910,13 @@ static struct rt6_info *ip6_route_info_create(struct fib6_config *cfg,
if (!table) if (!table)
goto out; goto out;
rt = ip6_dst_alloc(net, NULL,
(cfg->fc_flags & RTF_ADDRCONF) ? 0 : DST_NOCOUNT);
if (!rt) {
err = -ENOMEM; err = -ENOMEM;
rt = fib6_info_alloc(gfp_flags);
if (!rt)
goto out; goto out;
}
if (cfg->fc_flags & RTF_ADDRCONF)
rt->dst_nocount = true;
err = ip6_convert_metrics(net, rt, cfg); err = ip6_convert_metrics(net, rt, cfg);
if (err < 0) if (err < 0)
...@@ -3029,7 +2990,7 @@ static struct rt6_info *ip6_route_info_create(struct fib6_config *cfg, ...@@ -3029,7 +2990,7 @@ static struct rt6_info *ip6_route_info_create(struct fib6_config *cfg,
if (err) if (err)
goto out; goto out;
rt->fib6_nh.nh_gw = rt->rt6i_gateway = cfg->fc_gateway; rt->fib6_nh.nh_gw = cfg->fc_gateway;
} }
err = -ENODEV; err = -ENODEV;
...@@ -3066,7 +3027,7 @@ static struct rt6_info *ip6_route_info_create(struct fib6_config *cfg, ...@@ -3066,7 +3027,7 @@ static struct rt6_info *ip6_route_info_create(struct fib6_config *cfg,
!netif_carrier_ok(dev)) !netif_carrier_ok(dev))
rt->fib6_nh.nh_flags |= RTNH_F_LINKDOWN; rt->fib6_nh.nh_flags |= RTNH_F_LINKDOWN;
rt->fib6_nh.nh_flags |= (cfg->fc_flags & RTNH_F_ONLINK); rt->fib6_nh.nh_flags |= (cfg->fc_flags & RTNH_F_ONLINK);
rt->fib6_nh.nh_dev = rt->dst.dev = dev; rt->fib6_nh.nh_dev = dev;
rt->rt6i_idev = idev; rt->rt6i_idev = idev;
rt->rt6i_table = table; rt->rt6i_table = table;
...@@ -3078,9 +3039,8 @@ static struct rt6_info *ip6_route_info_create(struct fib6_config *cfg, ...@@ -3078,9 +3039,8 @@ static struct rt6_info *ip6_route_info_create(struct fib6_config *cfg,
dev_put(dev); dev_put(dev);
if (idev) if (idev)
in6_dev_put(idev); in6_dev_put(idev);
if (rt)
dst_release_immediate(&rt->dst);
fib6_info_release(rt);
return ERR_PTR(err); return ERR_PTR(err);
} }
...@@ -3095,6 +3055,7 @@ int ip6_route_add(struct fib6_config *cfg, gfp_t gfp_flags, ...@@ -3095,6 +3055,7 @@ int ip6_route_add(struct fib6_config *cfg, gfp_t gfp_flags,
return PTR_ERR(rt); return PTR_ERR(rt);
err = __ip6_ins_rt(rt, &cfg->fc_nlinfo, extack); err = __ip6_ins_rt(rt, &cfg->fc_nlinfo, extack);
fib6_info_release(rt);
return err; return err;
} }
...@@ -3116,7 +3077,7 @@ static int __ip6_del_rt(struct rt6_info *rt, struct nl_info *info) ...@@ -3116,7 +3077,7 @@ static int __ip6_del_rt(struct rt6_info *rt, struct nl_info *info)
spin_unlock_bh(&table->tb6_lock); spin_unlock_bh(&table->tb6_lock);
out: out:
ip6_rt_put(rt); fib6_info_release(rt);
return err; return err;
} }
...@@ -3170,7 +3131,7 @@ static int __ip6_del_rt_siblings(struct rt6_info *rt, struct fib6_config *cfg) ...@@ -3170,7 +3131,7 @@ static int __ip6_del_rt_siblings(struct rt6_info *rt, struct fib6_config *cfg)
out_unlock: out_unlock:
spin_unlock_bh(&table->tb6_lock); spin_unlock_bh(&table->tb6_lock);
out_put: out_put:
ip6_rt_put(rt); fib6_info_release(rt);
if (skb) { if (skb) {
rtnl_notify(skb, net, info->portid, RTNLGRP_IPV6_ROUTE, rtnl_notify(skb, net, info->portid, RTNLGRP_IPV6_ROUTE,
...@@ -3241,8 +3202,7 @@ static int ip6_route_del(struct fib6_config *cfg, ...@@ -3241,8 +3202,7 @@ static int ip6_route_del(struct fib6_config *cfg,
continue; continue;
if (cfg->fc_protocol && cfg->fc_protocol != rt->rt6i_protocol) if (cfg->fc_protocol && cfg->fc_protocol != rt->rt6i_protocol)
continue; continue;
if (!dst_hold_safe(&rt->dst)) fib6_info_hold(rt);
break;
rcu_read_unlock(); rcu_read_unlock();
/* if gateway was specified only delete the one hop */ /* if gateway was specified only delete the one hop */
...@@ -3510,12 +3470,9 @@ static void __rt6_purge_dflt_routers(struct net *net, ...@@ -3510,12 +3470,9 @@ static void __rt6_purge_dflt_routers(struct net *net,
for_each_fib6_node_rt_rcu(&table->tb6_root) { for_each_fib6_node_rt_rcu(&table->tb6_root) {
if (rt->rt6i_flags & (RTF_DEFAULT | RTF_ADDRCONF) && if (rt->rt6i_flags & (RTF_DEFAULT | RTF_ADDRCONF) &&
(!rt->rt6i_idev || rt->rt6i_idev->cnf.accept_ra != 2)) { (!rt->rt6i_idev || rt->rt6i_idev->cnf.accept_ra != 2)) {
if (dst_hold_safe(&rt->dst)) { fib6_info_hold(rt);
rcu_read_unlock(); rcu_read_unlock();
ip6_del_rt(net, rt); ip6_del_rt(net, rt);
} else {
rcu_read_unlock();
}
goto restart; goto restart;
} }
} }
...@@ -3666,7 +3623,7 @@ struct rt6_info *addrconf_dst_alloc(struct net *net, ...@@ -3666,7 +3623,7 @@ struct rt6_info *addrconf_dst_alloc(struct net *net,
struct net_device *dev = idev->dev; struct net_device *dev = idev->dev;
struct rt6_info *rt; struct rt6_info *rt;
rt = ip6_dst_alloc(net, dev, DST_NOCOUNT); rt = fib6_info_alloc(gfp_flags);
if (!rt) if (!rt)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
...@@ -3687,8 +3644,8 @@ struct rt6_info *addrconf_dst_alloc(struct net *net, ...@@ -3687,8 +3644,8 @@ struct rt6_info *addrconf_dst_alloc(struct net *net,
} }
rt->fib6_nh.nh_gw = *addr; rt->fib6_nh.nh_gw = *addr;
dev_hold(dev);
rt->fib6_nh.nh_dev = dev; rt->fib6_nh.nh_dev = dev;
rt->rt6i_gateway = *addr;
rt->rt6i_dst.addr = *addr; rt->rt6i_dst.addr = *addr;
rt->rt6i_dst.plen = 128; rt->rt6i_dst.plen = 128;
tb_id = l3mdev_fib_table(idev->dev) ? : RT6_TABLE_LOCAL; tb_id = l3mdev_fib_table(idev->dev) ? : RT6_TABLE_LOCAL;
...@@ -4325,7 +4282,7 @@ static int ip6_route_multipath_add(struct fib6_config *cfg, ...@@ -4325,7 +4282,7 @@ static int ip6_route_multipath_add(struct fib6_config *cfg,
err = ip6_route_info_append(info->nl_net, &rt6_nh_list, err = ip6_route_info_append(info->nl_net, &rt6_nh_list,
rt, &r_cfg); rt, &r_cfg);
if (err) { if (err) {
dst_release_immediate(&rt->dst); fib6_info_release(rt);
goto cleanup; goto cleanup;
} }
...@@ -4342,6 +4299,8 @@ static int ip6_route_multipath_add(struct fib6_config *cfg, ...@@ -4342,6 +4299,8 @@ static int ip6_route_multipath_add(struct fib6_config *cfg,
list_for_each_entry(nh, &rt6_nh_list, next) { list_for_each_entry(nh, &rt6_nh_list, next) {
rt_last = nh->rt6_info; rt_last = nh->rt6_info;
err = __ip6_ins_rt(nh->rt6_info, info, extack); err = __ip6_ins_rt(nh->rt6_info, info, extack);
fib6_info_release(nh->rt6_info);
/* save reference to first route for notification */ /* save reference to first route for notification */
if (!rt_notif && !err) if (!rt_notif && !err)
rt_notif = nh->rt6_info; rt_notif = nh->rt6_info;
...@@ -4389,7 +4348,7 @@ static int ip6_route_multipath_add(struct fib6_config *cfg, ...@@ -4389,7 +4348,7 @@ static int ip6_route_multipath_add(struct fib6_config *cfg,
cleanup: cleanup:
list_for_each_entry_safe(nh, nh_safe, &rt6_nh_list, next) { list_for_each_entry_safe(nh, nh_safe, &rt6_nh_list, next) {
if (nh->rt6_info) if (nh->rt6_info)
dst_release_immediate(&nh->rt6_info->dst); fib6_info_release(nh->rt6_info);
list_del(&nh->next); list_del(&nh->next);
kfree(nh); kfree(nh);
} }
...@@ -4814,14 +4773,6 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh, ...@@ -4814,14 +4773,6 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,
goto errout; goto errout;
} }
if (fibmatch && rt->from) {
struct rt6_info *ort = rt->from;
dst_hold(&ort->dst);
ip6_rt_put(rt);
rt = ort;
}
skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL); skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
if (!skb) { if (!skb) {
ip6_rt_put(rt); ip6_rt_put(rt);
...@@ -4831,12 +4782,12 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh, ...@@ -4831,12 +4782,12 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,
skb_dst_set(skb, &rt->dst); skb_dst_set(skb, &rt->dst);
if (fibmatch) if (fibmatch)
err = rt6_fill_node(net, skb, rt, NULL, NULL, NULL, iif, err = rt6_fill_node(net, skb, rt->from, NULL, NULL, NULL, iif,
RTM_NEWROUTE, NETLINK_CB(in_skb).portid, RTM_NEWROUTE, NETLINK_CB(in_skb).portid,
nlh->nlmsg_seq, 0); nlh->nlmsg_seq, 0);
else else
err = rt6_fill_node(net, skb, rt, dst, &fl6.daddr, &fl6.saddr, err = rt6_fill_node(net, skb, rt->from, dst,
iif, RTM_NEWROUTE, &fl6.daddr, &fl6.saddr, iif, RTM_NEWROUTE,
NETLINK_CB(in_skb).portid, nlh->nlmsg_seq, NETLINK_CB(in_skb).portid, nlh->nlmsg_seq,
0); 0);
if (err < 0) { if (err < 0) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment