diff mbox series

[SRU,Artful,2/2,v2] xfrm: reuse uncached_list to track xdsts

Message ID 084f05687e64e0ac03905073d761198bbbe5d648.1524589550.git.joseph.salisbury@canonical.com
State New
Headers show
Series Fixes for LP:1746474 | expand

Commit Message

Joseph Salisbury April 27, 2018, 7 p.m. UTC
From: Xin Long <lucien.xin@gmail.com>

BugLink: http://bugs.launchpad.net/bugs/1746474

In early time, when freeing a xdst, it would be inserted into
dst_garbage.list first. Then if it's refcnt was still held
somewhere, later it would be put into dst_busy_list in
dst_gc_task().

When one dev was being unregistered, the dev of these dsts in
dst_busy_list would be set with loopback_dev and put this dev.
So that this dev's removal wouldn't get blocked, and avoid the
kmsg warning:

  kernel:unregister_netdevice: waiting for veth0 to become \
  free. Usage count = 2

However after Commit 52df157f17e5 ("xfrm: take refcnt of dst
when creating struct xfrm_dst bundle"), the xdst will not be
freed with dst gc, and this warning happens.

To fix it, we need to find these xdsts that are still held by
others when removing the dev, and free xdst's dev and set it
with loopback_dev.

But unfortunately after flow_cache for xfrm was deleted, no
list tracks them anymore. So we need to save these xdsts
somewhere to release the xdst's dev later.

To make this easier, this patch is to reuse uncached_list to
track xdsts, so that the dev refcnt can be released in the
event NETDEV_UNREGISTER process of fib_netdev_notifier.

Thanks to Florian, we could move forward this fix quickly.

Fixes: 52df157f17e5 ("xfrm: take refcnt of dst when creating struct xfrm_dst bundle")
Reported-by: Jianlin Shi <jishi@redhat.com>
Reported-by: Hangbin Liu <liuhangbin@gmail.com>
Tested-by: Eyal Birger <eyal.birger@gmail.com>
Signed-off-by: Xin Long <lucien.xin@gmail.com>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
(back ported from commit 510c321b557121861601f9d259aadd65aa274f35)
Signed-off-by: Joseph Salisbury <joseph.salisbury@canonical.com>
---
 include/net/ip6_route.h | 13 +++++++++++++
 include/net/route.h     |  3 +++
 net/ipv4/route.c        | 21 +++++++++++++--------
 net/ipv4/xfrm4_policy.c |  4 +++-
 net/ipv6/route.c        |  4 ++--
 net/ipv6/xfrm6_policy.c |  4 ++++
 6 files changed, 38 insertions(+), 11 deletions(-)

Comments

Khalid Elmously May 2, 2018, 5:12 a.m. UTC | #1
On 2018-04-27 15:00:12 , Joseph Salisbury wrote:
> From: Xin Long <lucien.xin@gmail.com>
> 
> BugLink: http://bugs.launchpad.net/bugs/1746474
> 
> In early time, when freeing a xdst, it would be inserted into
> dst_garbage.list first. Then if it's refcnt was still held
> somewhere, later it would be put into dst_busy_list in
> dst_gc_task().
> 
> When one dev was being unregistered, the dev of these dsts in
> dst_busy_list would be set with loopback_dev and put this dev.
> So that this dev's removal wouldn't get blocked, and avoid the
> kmsg warning:
> 
>   kernel:unregister_netdevice: waiting for veth0 to become \
>   free. Usage count = 2
> 
> However after Commit 52df157f17e5 ("xfrm: take refcnt of dst
> when creating struct xfrm_dst bundle"), the xdst will not be
> freed with dst gc, and this warning happens.
> 
> To fix it, we need to find these xdsts that are still held by
> others when removing the dev, and free xdst's dev and set it
> with loopback_dev.
> 
> But unfortunately after flow_cache for xfrm was deleted, no
> list tracks them anymore. So we need to save these xdsts
> somewhere to release the xdst's dev later.
> 
> To make this easier, this patch is to reuse uncached_list to
> track xdsts, so that the dev refcnt can be released in the
> event NETDEV_UNREGISTER process of fib_netdev_notifier.
> 
> Thanks to Florian, we could move forward this fix quickly.
> 
> Fixes: 52df157f17e5 ("xfrm: take refcnt of dst when creating struct xfrm_dst bundle")
> Reported-by: Jianlin Shi <jishi@redhat.com>
> Reported-by: Hangbin Liu <liuhangbin@gmail.com>
> Tested-by: Eyal Birger <eyal.birger@gmail.com>
> Signed-off-by: Xin Long <lucien.xin@gmail.com>
> Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
> (back ported from commit 510c321b557121861601f9d259aadd65aa274f35)
> Signed-off-by: Joseph Salisbury <joseph.salisbury@canonical.com>
> ---
>  include/net/ip6_route.h | 13 +++++++++++++
>  include/net/route.h     |  3 +++
>  net/ipv4/route.c        | 21 +++++++++++++--------
>  net/ipv4/xfrm4_policy.c |  4 +++-
>  net/ipv6/route.c        |  4 ++--
>  net/ipv6/xfrm6_policy.c |  4 ++++
>  6 files changed, 38 insertions(+), 11 deletions(-)
> 
> diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h
> index 1990569..f37ea3e 100644
> --- a/include/net/ip6_route.h
> +++ b/include/net/ip6_route.h
> @@ -163,6 +163,19 @@ void rt6_mtu_change(struct net_device *dev, unsigned int mtu);
>  void rt6_remove_prefsrc(struct inet6_ifaddr *ifp);
>  void rt6_clean_tohost(struct net *net, struct in6_addr *gateway);
>  
> +void rt6_uncached_list_add(struct rt6_info *rt);
> +void rt6_uncached_list_del(struct rt6_info *rt);
> +
> +static inline const struct rt6_info *skb_rt6_info(const struct sk_buff *skb)
> +{
> +	const struct dst_entry *dst = skb_dst(skb);
> +	const struct rt6_info *rt6 = NULL;
> +
> +	if (dst)
> +		rt6 = container_of(dst, struct rt6_info, dst);
> +
> +	return rt6;
> +}

Any reason you didn't just cherry-pick 1b70d792cf6775fb5d0737524387893daeb5374a ? It looks like it would apply cleanly and bring this in for you.



>  
>  /*
>   *	Store a destination cache entry in a socket
> diff --git a/include/net/route.h b/include/net/route.h
> index 5845896..09a3507 100644
> --- a/include/net/route.h
> +++ b/include/net/route.h
> @@ -226,6 +226,9 @@ struct in_ifaddr;
>  void fib_add_ifaddr(struct in_ifaddr *);
>  void fib_del_ifaddr(struct in_ifaddr *, struct in_ifaddr *);
>  
> +void rt_add_uncached_list(struct rtable *rt);
> +void rt_del_uncached_list(struct rtable *rt);
> +
>  static inline void ip_rt_put(struct rtable *rt)
>  {
>  	/* dst_release() accepts a NULL parameter.
> diff --git a/net/ipv4/route.c b/net/ipv4/route.c
> index 83a2c494..33db041 100644
> --- a/net/ipv4/route.c
> +++ b/net/ipv4/route.c
> @@ -1382,7 +1382,7 @@ struct uncached_list {
>  
>  static DEFINE_PER_CPU_ALIGNED(struct uncached_list, rt_uncached_list);
>  
> -static void rt_add_uncached_list(struct rtable *rt)
> +void rt_add_uncached_list(struct rtable *rt)
>  {
>  	struct uncached_list *ul = raw_cpu_ptr(&rt_uncached_list);
>  
> @@ -1393,14 +1393,8 @@ static void rt_add_uncached_list(struct rtable *rt)
>  	spin_unlock_bh(&ul->lock);
>  }
>  
> -static void ipv4_dst_destroy(struct dst_entry *dst)
> +void rt_del_uncached_list(struct rtable *rt)
>  {
> -	struct dst_metrics *p = (struct dst_metrics *)DST_METRICS_PTR(dst);
> -	struct rtable *rt = (struct rtable *) dst;
> -
> -	if (p != &dst_default_metrics && refcount_dec_and_test(&p->refcnt))
> -		kfree(p);
> -
>  	if (!list_empty(&rt->rt_uncached)) {
>  		struct uncached_list *ul = rt->rt_uncached_list;
>  
> @@ -1410,6 +1404,17 @@ static void ipv4_dst_destroy(struct dst_entry *dst)
>  	}
>  }
>  
> +static void ipv4_dst_destroy(struct dst_entry *dst)
> +{
> +	struct dst_metrics *p = (struct dst_metrics *)DST_METRICS_PTR(dst);
> +	struct rtable *rt = (struct rtable *)dst;
> +
> +	if (p != &dst_default_metrics && refcount_dec_and_test(&p->refcnt))
> +		kfree(p);
> +
> +	rt_del_uncached_list(rt);
> +}
> +
>  void rt_flush_dev(struct net_device *dev)
>  {
>  	struct net *net = dev_net(dev);
> diff --git a/net/ipv4/xfrm4_policy.c b/net/ipv4/xfrm4_policy.c
> index 71b4ecc1..cb9890a 100644
> --- a/net/ipv4/xfrm4_policy.c
> +++ b/net/ipv4/xfrm4_policy.c
> @@ -97,6 +97,7 @@ static int xfrm4_fill_dst(struct xfrm_dst *xdst, struct net_device *dev,
>  	xdst->u.rt.rt_pmtu = rt->rt_pmtu;
>  	xdst->u.rt.rt_table_id = rt->rt_table_id;
>  	INIT_LIST_HEAD(&xdst->u.rt.rt_uncached);
> +	rt_add_uncached_list(&xdst->u.rt);
>  
>  	return 0;
>  }
> @@ -244,7 +245,8 @@ static void xfrm4_dst_destroy(struct dst_entry *dst)
>  	struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
>  
>  	dst_destroy_metrics_generic(dst);
> -
> +	if (xdst->u.rt.rt_uncached_list)
> +		rt_del_uncached_list(&xdst->u.rt);
>  	xfrm_dst_destroy(xdst);
>  }
>  
> diff --git a/net/ipv6/route.c b/net/ipv6/route.c
> index d00e41c..6062c65 100644
> --- a/net/ipv6/route.c
> +++ b/net/ipv6/route.c
> @@ -124,7 +124,7 @@ struct uncached_list {
>  
>  static DEFINE_PER_CPU_ALIGNED(struct uncached_list, rt6_uncached_list);
>  
> -static void rt6_uncached_list_add(struct rt6_info *rt)
> +void rt6_uncached_list_add(struct rt6_info *rt)
>  {
>  	struct uncached_list *ul = raw_cpu_ptr(&rt6_uncached_list);
>  
> @@ -135,7 +135,7 @@ static void rt6_uncached_list_add(struct rt6_info *rt)
>  	spin_unlock_bh(&ul->lock);
>  }
>  
> -static void rt6_uncached_list_del(struct rt6_info *rt)
> +void rt6_uncached_list_del(struct rt6_info *rt)
>  {
>  	if (!list_empty(&rt->rt6i_uncached)) {
>  		struct uncached_list *ul = rt->rt6i_uncached_list;
> diff --git a/net/ipv6/xfrm6_policy.c b/net/ipv6/xfrm6_policy.c
> index 79651bc..dec840c 100644
> --- a/net/ipv6/xfrm6_policy.c
> +++ b/net/ipv6/xfrm6_policy.c
> @@ -109,6 +109,8 @@ static int xfrm6_fill_dst(struct xfrm_dst *xdst, struct net_device *dev,
>  	xdst->u.rt6.rt6i_gateway = rt->rt6i_gateway;
>  	xdst->u.rt6.rt6i_dst = rt->rt6i_dst;
>  	xdst->u.rt6.rt6i_src = rt->rt6i_src;
> +	INIT_LIST_HEAD(&xdst->u.rt6.rt6i_uncached);
> +	rt6_uncached_list_add(&xdst->u.rt6);

Any reason you've omitted the atomic_inc() call from here?

>  
>  	return 0;
>  }
> @@ -247,6 +249,8 @@ static void xfrm6_dst_destroy(struct dst_entry *dst)
>  	if (likely(xdst->u.rt6.rt6i_idev))
>  		in6_dev_put(xdst->u.rt6.rt6i_idev);
>  	dst_destroy_metrics_generic(dst);
> +	if (xdst->u.rt6.rt6i_uncached_list)
> +		rt6_uncached_list_del(&xdst->u.rt6);
>  	xfrm_dst_destroy(xdst);
>  }
>  
> -- 
> 2.7.4
> 
> 
> -- 
> kernel-team mailing list
> kernel-team@lists.ubuntu.com
> https://lists.ubuntu.com/mailman/listinfo/kernel-team
Joseph Salisbury May 2, 2018, 5:37 p.m. UTC | #2
On 05/02/2018 01:12 AM, Khaled Elmously wrote:
> On 2018-04-27 15:00:12 , Joseph Salisbury wrote:
>> From: Xin Long <lucien.xin@gmail.com>
>>
>> BugLink: http://bugs.launchpad.net/bugs/1746474
>>
>> In early time, when freeing a xdst, it would be inserted into
>> dst_garbage.list first. Then if it's refcnt was still held
>> somewhere, later it would be put into dst_busy_list in
>> dst_gc_task().
>>
>> When one dev was being unregistered, the dev of these dsts in
>> dst_busy_list would be set with loopback_dev and put this dev.
>> So that this dev's removal wouldn't get blocked, and avoid the
>> kmsg warning:
>>
>>   kernel:unregister_netdevice: waiting for veth0 to become \
>>   free. Usage count = 2
>>
>> However after Commit 52df157f17e5 ("xfrm: take refcnt of dst
>> when creating struct xfrm_dst bundle"), the xdst will not be
>> freed with dst gc, and this warning happens.
>>
>> To fix it, we need to find these xdsts that are still held by
>> others when removing the dev, and free xdst's dev and set it
>> with loopback_dev.
>>
>> But unfortunately after flow_cache for xfrm was deleted, no
>> list tracks them anymore. So we need to save these xdsts
>> somewhere to release the xdst's dev later.
>>
>> To make this easier, this patch is to reuse uncached_list to
>> track xdsts, so that the dev refcnt can be released in the
>> event NETDEV_UNREGISTER process of fib_netdev_notifier.
>>
>> Thanks to Florian, we could move forward this fix quickly.
>>
>> Fixes: 52df157f17e5 ("xfrm: take refcnt of dst when creating struct xfrm_dst bundle")
>> Reported-by: Jianlin Shi <jishi@redhat.com>
>> Reported-by: Hangbin Liu <liuhangbin@gmail.com>
>> Tested-by: Eyal Birger <eyal.birger@gmail.com>
>> Signed-off-by: Xin Long <lucien.xin@gmail.com>
>> Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
>> (back ported from commit 510c321b557121861601f9d259aadd65aa274f35)
>> Signed-off-by: Joseph Salisbury <joseph.salisbury@canonical.com>
>> ---
>>  include/net/ip6_route.h | 13 +++++++++++++
>>  include/net/route.h     |  3 +++
>>  net/ipv4/route.c        | 21 +++++++++++++--------
>>  net/ipv4/xfrm4_policy.c |  4 +++-
>>  net/ipv6/route.c        |  4 ++--
>>  net/ipv6/xfrm6_policy.c |  4 ++++
>>  6 files changed, 38 insertions(+), 11 deletions(-)
>>
>> diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h
>> index 1990569..f37ea3e 100644
>> --- a/include/net/ip6_route.h
>> +++ b/include/net/ip6_route.h
>> @@ -163,6 +163,19 @@ void rt6_mtu_change(struct net_device *dev, unsigned int mtu);
>>  void rt6_remove_prefsrc(struct inet6_ifaddr *ifp);
>>  void rt6_clean_tohost(struct net *net, struct in6_addr *gateway);
>>  
>> +void rt6_uncached_list_add(struct rt6_info *rt);
>> +void rt6_uncached_list_del(struct rt6_info *rt);
>> +
>> +static inline const struct rt6_info *skb_rt6_info(const struct sk_buff *skb)
>> +{
>> +	const struct dst_entry *dst = skb_dst(skb);
>> +	const struct rt6_info *rt6 = NULL;
>> +
>> +	if (dst)
>> +		rt6 = container_of(dst, struct rt6_info, dst);
>> +
>> +	return rt6;
>> +}
> Any reason you didn't just cherry-pick 1b70d792cf6775fb5d0737524387893daeb5374a ? It looks like it would apply cleanly and bring this in for you.
After reviewing again, I don't even think skb_rt6_info() is needed or
used.  It was pulled in by the 'git cherry-pick' to satisfy a context diff.
>
>
>
>>  
>>  /*
>>   *	Store a destination cache entry in a socket
>> diff --git a/include/net/route.h b/include/net/route.h
>> index 5845896..09a3507 100644
>> --- a/include/net/route.h
>> +++ b/include/net/route.h
>> @@ -226,6 +226,9 @@ struct in_ifaddr;
>>  void fib_add_ifaddr(struct in_ifaddr *);
>>  void fib_del_ifaddr(struct in_ifaddr *, struct in_ifaddr *);
>>  
>> +void rt_add_uncached_list(struct rtable *rt);
>> +void rt_del_uncached_list(struct rtable *rt);
>> +
>>  static inline void ip_rt_put(struct rtable *rt)
>>  {
>>  	/* dst_release() accepts a NULL parameter.
>> diff --git a/net/ipv4/route.c b/net/ipv4/route.c
>> index 83a2c494..33db041 100644
>> --- a/net/ipv4/route.c
>> +++ b/net/ipv4/route.c
>> @@ -1382,7 +1382,7 @@ struct uncached_list {
>>  
>>  static DEFINE_PER_CPU_ALIGNED(struct uncached_list, rt_uncached_list);
>>  
>> -static void rt_add_uncached_list(struct rtable *rt)
>> +void rt_add_uncached_list(struct rtable *rt)
>>  {
>>  	struct uncached_list *ul = raw_cpu_ptr(&rt_uncached_list);
>>  
>> @@ -1393,14 +1393,8 @@ static void rt_add_uncached_list(struct rtable *rt)
>>  	spin_unlock_bh(&ul->lock);
>>  }
>>  
>> -static void ipv4_dst_destroy(struct dst_entry *dst)
>> +void rt_del_uncached_list(struct rtable *rt)
>>  {
>> -	struct dst_metrics *p = (struct dst_metrics *)DST_METRICS_PTR(dst);
>> -	struct rtable *rt = (struct rtable *) dst;
>> -
>> -	if (p != &dst_default_metrics && refcount_dec_and_test(&p->refcnt))
>> -		kfree(p);
>> -
>>  	if (!list_empty(&rt->rt_uncached)) {
>>  		struct uncached_list *ul = rt->rt_uncached_list;
>>  
>> @@ -1410,6 +1404,17 @@ static void ipv4_dst_destroy(struct dst_entry *dst)
>>  	}
>>  }
>>  
>> +static void ipv4_dst_destroy(struct dst_entry *dst)
>> +{
>> +	struct dst_metrics *p = (struct dst_metrics *)DST_METRICS_PTR(dst);
>> +	struct rtable *rt = (struct rtable *)dst;
>> +
>> +	if (p != &dst_default_metrics && refcount_dec_and_test(&p->refcnt))
>> +		kfree(p);
>> +
>> +	rt_del_uncached_list(rt);
>> +}
>> +
>>  void rt_flush_dev(struct net_device *dev)
>>  {
>>  	struct net *net = dev_net(dev);
>> diff --git a/net/ipv4/xfrm4_policy.c b/net/ipv4/xfrm4_policy.c
>> index 71b4ecc1..cb9890a 100644
>> --- a/net/ipv4/xfrm4_policy.c
>> +++ b/net/ipv4/xfrm4_policy.c
>> @@ -97,6 +97,7 @@ static int xfrm4_fill_dst(struct xfrm_dst *xdst, struct net_device *dev,
>>  	xdst->u.rt.rt_pmtu = rt->rt_pmtu;
>>  	xdst->u.rt.rt_table_id = rt->rt_table_id;
>>  	INIT_LIST_HEAD(&xdst->u.rt.rt_uncached);
>> +	rt_add_uncached_list(&xdst->u.rt);
>>  
>>  	return 0;
>>  }
>> @@ -244,7 +245,8 @@ static void xfrm4_dst_destroy(struct dst_entry *dst)
>>  	struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
>>  
>>  	dst_destroy_metrics_generic(dst);
>> -
>> +	if (xdst->u.rt.rt_uncached_list)
>> +		rt_del_uncached_list(&xdst->u.rt);
>>  	xfrm_dst_destroy(xdst);
>>  }
>>  
>> diff --git a/net/ipv6/route.c b/net/ipv6/route.c
>> index d00e41c..6062c65 100644
>> --- a/net/ipv6/route.c
>> +++ b/net/ipv6/route.c
>> @@ -124,7 +124,7 @@ struct uncached_list {
>>  
>>  static DEFINE_PER_CPU_ALIGNED(struct uncached_list, rt6_uncached_list);
>>  
>> -static void rt6_uncached_list_add(struct rt6_info *rt)
>> +void rt6_uncached_list_add(struct rt6_info *rt)
>>  {
>>  	struct uncached_list *ul = raw_cpu_ptr(&rt6_uncached_list);
>>  
>> @@ -135,7 +135,7 @@ static void rt6_uncached_list_add(struct rt6_info *rt)
>>  	spin_unlock_bh(&ul->lock);
>>  }
>>  
>> -static void rt6_uncached_list_del(struct rt6_info *rt)
>> +void rt6_uncached_list_del(struct rt6_info *rt)
>>  {
>>  	if (!list_empty(&rt->rt6i_uncached)) {
>>  		struct uncached_list *ul = rt->rt6i_uncached_list;
>> diff --git a/net/ipv6/xfrm6_policy.c b/net/ipv6/xfrm6_policy.c
>> index 79651bc..dec840c 100644
>> --- a/net/ipv6/xfrm6_policy.c
>> +++ b/net/ipv6/xfrm6_policy.c
>> @@ -109,6 +109,8 @@ static int xfrm6_fill_dst(struct xfrm_dst *xdst, struct net_device *dev,
>>  	xdst->u.rt6.rt6i_gateway = rt->rt6i_gateway;
>>  	xdst->u.rt6.rt6i_dst = rt->rt6i_dst;
>>  	xdst->u.rt6.rt6i_src = rt->rt6i_src;
>> +	INIT_LIST_HEAD(&xdst->u.rt6.rt6i_uncached);
>> +	rt6_uncached_list_add(&xdst->u.rt6);
> Any reason you've omitted the atomic_inc() call from here?
This was per the suggestion by smb in the V1 version:
"I wonder whether it would not make more sense to just drop the
increment line in xfrm6_fill_dst()."

Thanks for the feedback.  I'll build a v3 and have it tested.  It will
not include skb_rt6_info() as mentioned above and it will also drop the
increment line in xfrm6_fill_dst() as suggested in v1.
>
>>  
>>  	return 0;
>>  }
>> @@ -247,6 +249,8 @@ static void xfrm6_dst_destroy(struct dst_entry *dst)
>>  	if (likely(xdst->u.rt6.rt6i_idev))
>>  		in6_dev_put(xdst->u.rt6.rt6i_idev);
>>  	dst_destroy_metrics_generic(dst);
>> +	if (xdst->u.rt6.rt6i_uncached_list)
>> +		rt6_uncached_list_del(&xdst->u.rt6);
>>  	xfrm_dst_destroy(xdst);
>>  }
>>  
>> -- 
>> 2.7.4
>>
>>
>> -- 
>> kernel-team mailing list
>> kernel-team@lists.ubuntu.com
>> https://lists.ubuntu.com/mailman/listinfo/kernel-team
diff mbox series

Patch

diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h
index 1990569..f37ea3e 100644
--- a/include/net/ip6_route.h
+++ b/include/net/ip6_route.h
@@ -163,6 +163,19 @@  void rt6_mtu_change(struct net_device *dev, unsigned int mtu);
 void rt6_remove_prefsrc(struct inet6_ifaddr *ifp);
 void rt6_clean_tohost(struct net *net, struct in6_addr *gateway);
 
+void rt6_uncached_list_add(struct rt6_info *rt);
+void rt6_uncached_list_del(struct rt6_info *rt);
+
+static inline const struct rt6_info *skb_rt6_info(const struct sk_buff *skb)
+{
+	const struct dst_entry *dst = skb_dst(skb);
+	const struct rt6_info *rt6 = NULL;
+
+	if (dst)
+		rt6 = container_of(dst, struct rt6_info, dst);
+
+	return rt6;
+}
 
 /*
  *	Store a destination cache entry in a socket
diff --git a/include/net/route.h b/include/net/route.h
index 5845896..09a3507 100644
--- a/include/net/route.h
+++ b/include/net/route.h
@@ -226,6 +226,9 @@  struct in_ifaddr;
 void fib_add_ifaddr(struct in_ifaddr *);
 void fib_del_ifaddr(struct in_ifaddr *, struct in_ifaddr *);
 
+void rt_add_uncached_list(struct rtable *rt);
+void rt_del_uncached_list(struct rtable *rt);
+
 static inline void ip_rt_put(struct rtable *rt)
 {
 	/* dst_release() accepts a NULL parameter.
diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index 83a2c494..33db041 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c
@@ -1382,7 +1382,7 @@  struct uncached_list {
 
 static DEFINE_PER_CPU_ALIGNED(struct uncached_list, rt_uncached_list);
 
-static void rt_add_uncached_list(struct rtable *rt)
+void rt_add_uncached_list(struct rtable *rt)
 {
 	struct uncached_list *ul = raw_cpu_ptr(&rt_uncached_list);
 
@@ -1393,14 +1393,8 @@  static void rt_add_uncached_list(struct rtable *rt)
 	spin_unlock_bh(&ul->lock);
 }
 
-static void ipv4_dst_destroy(struct dst_entry *dst)
+void rt_del_uncached_list(struct rtable *rt)
 {
-	struct dst_metrics *p = (struct dst_metrics *)DST_METRICS_PTR(dst);
-	struct rtable *rt = (struct rtable *) dst;
-
-	if (p != &dst_default_metrics && refcount_dec_and_test(&p->refcnt))
-		kfree(p);
-
 	if (!list_empty(&rt->rt_uncached)) {
 		struct uncached_list *ul = rt->rt_uncached_list;
 
@@ -1410,6 +1404,17 @@  static void ipv4_dst_destroy(struct dst_entry *dst)
 	}
 }
 
+static void ipv4_dst_destroy(struct dst_entry *dst)
+{
+	struct dst_metrics *p = (struct dst_metrics *)DST_METRICS_PTR(dst);
+	struct rtable *rt = (struct rtable *)dst;
+
+	if (p != &dst_default_metrics && refcount_dec_and_test(&p->refcnt))
+		kfree(p);
+
+	rt_del_uncached_list(rt);
+}
+
 void rt_flush_dev(struct net_device *dev)
 {
 	struct net *net = dev_net(dev);
diff --git a/net/ipv4/xfrm4_policy.c b/net/ipv4/xfrm4_policy.c
index 71b4ecc1..cb9890a 100644
--- a/net/ipv4/xfrm4_policy.c
+++ b/net/ipv4/xfrm4_policy.c
@@ -97,6 +97,7 @@  static int xfrm4_fill_dst(struct xfrm_dst *xdst, struct net_device *dev,
 	xdst->u.rt.rt_pmtu = rt->rt_pmtu;
 	xdst->u.rt.rt_table_id = rt->rt_table_id;
 	INIT_LIST_HEAD(&xdst->u.rt.rt_uncached);
+	rt_add_uncached_list(&xdst->u.rt);
 
 	return 0;
 }
@@ -244,7 +245,8 @@  static void xfrm4_dst_destroy(struct dst_entry *dst)
 	struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
 
 	dst_destroy_metrics_generic(dst);
-
+	if (xdst->u.rt.rt_uncached_list)
+		rt_del_uncached_list(&xdst->u.rt);
 	xfrm_dst_destroy(xdst);
 }
 
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index d00e41c..6062c65 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -124,7 +124,7 @@  struct uncached_list {
 
 static DEFINE_PER_CPU_ALIGNED(struct uncached_list, rt6_uncached_list);
 
-static void rt6_uncached_list_add(struct rt6_info *rt)
+void rt6_uncached_list_add(struct rt6_info *rt)
 {
 	struct uncached_list *ul = raw_cpu_ptr(&rt6_uncached_list);
 
@@ -135,7 +135,7 @@  static void rt6_uncached_list_add(struct rt6_info *rt)
 	spin_unlock_bh(&ul->lock);
 }
 
-static void rt6_uncached_list_del(struct rt6_info *rt)
+void rt6_uncached_list_del(struct rt6_info *rt)
 {
 	if (!list_empty(&rt->rt6i_uncached)) {
 		struct uncached_list *ul = rt->rt6i_uncached_list;
diff --git a/net/ipv6/xfrm6_policy.c b/net/ipv6/xfrm6_policy.c
index 79651bc..dec840c 100644
--- a/net/ipv6/xfrm6_policy.c
+++ b/net/ipv6/xfrm6_policy.c
@@ -109,6 +109,8 @@  static int xfrm6_fill_dst(struct xfrm_dst *xdst, struct net_device *dev,
 	xdst->u.rt6.rt6i_gateway = rt->rt6i_gateway;
 	xdst->u.rt6.rt6i_dst = rt->rt6i_dst;
 	xdst->u.rt6.rt6i_src = rt->rt6i_src;
+	INIT_LIST_HEAD(&xdst->u.rt6.rt6i_uncached);
+	rt6_uncached_list_add(&xdst->u.rt6);
 
 	return 0;
 }
@@ -247,6 +249,8 @@  static void xfrm6_dst_destroy(struct dst_entry *dst)
 	if (likely(xdst->u.rt6.rt6i_idev))
 		in6_dev_put(xdst->u.rt6.rt6i_idev);
 	dst_destroy_metrics_generic(dst);
+	if (xdst->u.rt6.rt6i_uncached_list)
+		rt6_uncached_list_del(&xdst->u.rt6);
 	xfrm_dst_destroy(xdst);
 }