Commit 8e3d5be7 authored by Martin KaFai Lau's avatar Martin KaFai Lau Committed by David S. Miller

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: default avatarMartin KaFai Lau <kafai@fb.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent cdf3464e
...@@ -5127,13 +5127,12 @@ static void __ipv6_ifa_notify(int event, struct inet6_ifaddr *ifp) ...@@ -5127,13 +5127,12 @@ static void __ipv6_ifa_notify(int event, struct inet6_ifaddr *ifp)
rt = addrconf_get_prefix_route(&ifp->peer_addr, 128, rt = addrconf_get_prefix_route(&ifp->peer_addr, 128,
ifp->idev->dev, 0, 0); ifp->idev->dev, 0, 0);
if (rt && ip6_del_rt(rt)) if (rt)
dst_free(&rt->dst); ip6_del_rt(rt);
} }
dst_hold(&ifp->rt->dst); dst_hold(&ifp->rt->dst);
if (ip6_del_rt(ifp->rt)) ip6_del_rt(ifp->rt);
dst_free(&ifp->rt->dst);
rt_genid_bump_ipv6(net); rt_genid_bump_ipv6(net);
break; break;
......
...@@ -933,6 +933,10 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt, ...@@ -933,6 +933,10 @@ 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((rt->dst.flags & DST_NOCACHE) &&
!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;
...@@ -1025,6 +1029,7 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt, ...@@ -1025,6 +1029,7 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt,
fib6_start_gc(info->nl_net, rt); fib6_start_gc(info->nl_net, rt);
if (!(rt->rt6i_flags & RTF_CACHE)) if (!(rt->rt6i_flags & RTF_CACHE))
fib6_prune_clones(info->nl_net, pn); fib6_prune_clones(info->nl_net, pn);
rt->dst.flags &= ~DST_NOCACHE;
} }
out: out:
...@@ -1049,7 +1054,8 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt, ...@@ -1049,7 +1054,8 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt,
atomic_inc(&pn->leaf->rt6i_ref); atomic_inc(&pn->leaf->rt6i_ref);
} }
#endif #endif
dst_free(&rt->dst); if (!(rt->dst.flags & DST_NOCACHE))
dst_free(&rt->dst);
} }
return err; return err;
...@@ -1060,7 +1066,8 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt, ...@@ -1060,7 +1066,8 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt,
st_failure: st_failure:
if (fn && !(fn->fn_flags & (RTN_RTINFO|RTN_ROOT))) if (fn && !(fn->fn_flags & (RTN_RTINFO|RTN_ROOT)))
fib6_repair_tree(info->nl_net, fn); fib6_repair_tree(info->nl_net, fn);
dst_free(&rt->dst); if (!(rt->dst.flags & DST_NOCACHE))
dst_free(&rt->dst);
return err; return err;
#endif #endif
} }
......
...@@ -1322,8 +1322,7 @@ static void ip6_link_failure(struct sk_buff *skb) ...@@ -1322,8 +1322,7 @@ 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) {
dst_hold(&rt->dst); dst_hold(&rt->dst);
if (ip6_del_rt(rt)) ip6_del_rt(rt);
dst_free(&rt->dst);
} else if (rt->rt6i_node && (rt->rt6i_flags & RTF_DEFAULT)) { } else if (rt->rt6i_node && (rt->rt6i_flags & RTF_DEFAULT)) {
rt->rt6i_node->fn_sernum = -1; rt->rt6i_node->fn_sernum = -1;
} }
...@@ -2028,7 +2027,8 @@ static int __ip6_del_rt(struct rt6_info *rt, struct nl_info *info) ...@@ -2028,7 +2027,8 @@ static int __ip6_del_rt(struct rt6_info *rt, struct nl_info *info)
struct fib6_table *table; struct fib6_table *table;
struct net *net = dev_net(rt->dst.dev); 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; err = -ENOENT;
goto out; goto out;
} }
...@@ -2515,6 +2515,7 @@ struct rt6_info *addrconf_dst_alloc(struct inet6_dev *idev, ...@@ -2515,6 +2515,7 @@ struct rt6_info *addrconf_dst_alloc(struct inet6_dev *idev,
rt->rt6i_dst.addr = *addr; rt->rt6i_dst.addr = *addr;
rt->rt6i_dst.plen = 128; rt->rt6i_dst.plen = 128;
rt->rt6i_table = fib6_get_table(net, RT6_TABLE_LOCAL); rt->rt6i_table = fib6_get_table(net, RT6_TABLE_LOCAL);
rt->dst.flags |= DST_NOCACHE;
atomic_set(&rt->dst.__refcnt, 1); atomic_set(&rt->dst.__refcnt, 1);
......
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