mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-10-30 16:07:39 +00:00
ipv6: Avoid double dst_free
It is a prep work to get dst freeing from fib tree undergo a rcu grace period. The following is a common paradigm: if (ip6_del_rt(rt)) dst_free(rt) which means, if rt cannot be deleted from the fib tree, dst_free(rt) now. 1. We don't know the ip6_del_rt(rt) failure is because it was not managed by fib tree (e.g. DST_NOCACHE) or it had already been removed from the fib tree. 2. If rt had been managed by the fib tree, ip6_del_rt(rt) failure means dst_free(rt) has been called already. A second dst_free(rt) is not always obviously safe. The rt may have been destroyed already. 3. If rt is a DST_NOCACHE, dst_free(rt) should not be called. 4. It is a stopper to make dst freeing from fib tree undergo a rcu grace period. This patch is to use a DST_NOCACHE flag to indicate a rt is not managed by the fib tree. Signed-off-by: Martin KaFai Lau <kafai@fb.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
cdf3464e6c
commit
8e3d5be736
3 changed files with 16 additions and 9 deletions
|
@ -5127,13 +5127,12 @@ static void __ipv6_ifa_notify(int event, struct inet6_ifaddr *ifp)
|
|||
|
||||
rt = addrconf_get_prefix_route(&ifp->peer_addr, 128,
|
||||
ifp->idev->dev, 0, 0);
|
||||
if (rt && ip6_del_rt(rt))
|
||||
dst_free(&rt->dst);
|
||||
if (rt)
|
||||
ip6_del_rt(rt);
|
||||
}
|
||||
dst_hold(&ifp->rt->dst);
|
||||
|
||||
if (ip6_del_rt(ifp->rt))
|
||||
dst_free(&ifp->rt->dst);
|
||||
ip6_del_rt(ifp->rt);
|
||||
|
||||
rt_genid_bump_ipv6(net);
|
||||
break;
|
||||
|
|
|
@ -933,6 +933,10 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt,
|
|||
int replace_required = 0;
|
||||
int sernum = fib6_new_sernum(info->nl_net);
|
||||
|
||||
if (WARN_ON_ONCE((rt->dst.flags & DST_NOCACHE) &&
|
||||
!atomic_read(&rt->dst.__refcnt)))
|
||||
return -EINVAL;
|
||||
|
||||
if (info->nlh) {
|
||||
if (!(info->nlh->nlmsg_flags & NLM_F_CREATE))
|
||||
allow_create = 0;
|
||||
|
@ -1025,6 +1029,7 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt,
|
|||
fib6_start_gc(info->nl_net, rt);
|
||||
if (!(rt->rt6i_flags & RTF_CACHE))
|
||||
fib6_prune_clones(info->nl_net, pn);
|
||||
rt->dst.flags &= ~DST_NOCACHE;
|
||||
}
|
||||
|
||||
out:
|
||||
|
@ -1049,7 +1054,8 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt,
|
|||
atomic_inc(&pn->leaf->rt6i_ref);
|
||||
}
|
||||
#endif
|
||||
dst_free(&rt->dst);
|
||||
if (!(rt->dst.flags & DST_NOCACHE))
|
||||
dst_free(&rt->dst);
|
||||
}
|
||||
return err;
|
||||
|
||||
|
@ -1060,7 +1066,8 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt,
|
|||
st_failure:
|
||||
if (fn && !(fn->fn_flags & (RTN_RTINFO|RTN_ROOT)))
|
||||
fib6_repair_tree(info->nl_net, fn);
|
||||
dst_free(&rt->dst);
|
||||
if (!(rt->dst.flags & DST_NOCACHE))
|
||||
dst_free(&rt->dst);
|
||||
return err;
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -1322,8 +1322,7 @@ static void ip6_link_failure(struct sk_buff *skb)
|
|||
if (rt) {
|
||||
if (rt->rt6i_flags & RTF_CACHE) {
|
||||
dst_hold(&rt->dst);
|
||||
if (ip6_del_rt(rt))
|
||||
dst_free(&rt->dst);
|
||||
ip6_del_rt(rt);
|
||||
} else if (rt->rt6i_node && (rt->rt6i_flags & RTF_DEFAULT)) {
|
||||
rt->rt6i_node->fn_sernum = -1;
|
||||
}
|
||||
|
@ -2028,7 +2027,8 @@ 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) {
|
||||
if (rt == net->ipv6.ip6_null_entry ||
|
||||
rt->dst.flags & DST_NOCACHE) {
|
||||
err = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
|
@ -2515,6 +2515,7 @@ struct rt6_info *addrconf_dst_alloc(struct inet6_dev *idev,
|
|||
rt->rt6i_dst.addr = *addr;
|
||||
rt->rt6i_dst.plen = 128;
|
||||
rt->rt6i_table = fib6_get_table(net, RT6_TABLE_LOCAL);
|
||||
rt->dst.flags |= DST_NOCACHE;
|
||||
|
||||
atomic_set(&rt->dst.__refcnt, 1);
|
||||
|
||||
|
|
Loading…
Reference in a new issue