diff --git a/include/net/dst_ops.h b/include/net/dst_ops.h index b93799fe46e8..443863c7b8da 100644 --- a/include/net/dst_ops.h +++ b/include/net/dst_ops.h @@ -24,7 +24,7 @@ struct dst_ops { void (*destroy)(struct dst_entry *); void (*ifdown)(struct dst_entry *, struct net_device *dev, int how); - void (*negative_advice)(struct sock *sk, struct dst_entry *); + struct dst_entry * (*negative_advice)(struct dst_entry *); void (*link_failure)(struct sk_buff *); void (*update_pmtu)(struct dst_entry *dst, struct sock *sk, struct sk_buff *skb, u32 mtu, diff --git a/include/net/sock.h b/include/net/sock.h index f37ddce89483..125b16200ca4 100644 --- a/include/net/sock.h +++ b/include/net/sock.h @@ -1936,12 +1936,19 @@ sk_dst_get(struct sock *sk) static inline void dst_negative_advice(struct sock *sk) { - struct dst_entry *dst = __sk_dst_get(sk); + struct dst_entry *ndst, *dst = __sk_dst_get(sk); sk_rethink_txhash(sk); - if (dst && dst->ops->negative_advice) - dst->ops->negative_advice(sk, dst); + if (dst && dst->ops->negative_advice) { + ndst = dst->ops->negative_advice(dst); + + if (ndst != dst) { + rcu_assign_pointer(sk->sk_dst_cache, ndst); + sk_tx_queue_clear(sk); + sk->sk_dst_pending_confirm = 0; + } + } } static inline void diff --git a/net/ipv4/route.c b/net/ipv4/route.c index 3a876eacb07d..f82d456afd0e 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -137,8 +137,7 @@ static int ip_rt_gc_timeout __read_mostly = RT_GC_TIMEOUT; static struct dst_entry *ipv4_dst_check(struct dst_entry *dst, u32 cookie); static unsigned int ipv4_default_advmss(const struct dst_entry *dst); static unsigned int ipv4_mtu(const struct dst_entry *dst); -static void ipv4_negative_advice(struct sock *sk, - struct dst_entry *dst); +static struct dst_entry *ipv4_negative_advice(struct dst_entry *dst); static void ipv4_link_failure(struct sk_buff *skb); static void ip_rt_update_pmtu(struct dst_entry *dst, struct sock *sk, struct sk_buff *skb, u32 mtu, @@ -857,15 +856,22 @@ static void ip_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_buf __ip_do_redirect(rt, skb, &fl4, true); } -static void ipv4_negative_advice(struct sock *sk, - struct dst_entry *dst) +static struct dst_entry *ipv4_negative_advice(struct dst_entry *dst) { struct rtable *rt = (struct rtable *)dst; + struct dst_entry *ret = dst; - if ((dst->obsolete > 0) || - (rt->rt_flags & RTCF_REDIRECTED) || - rt->dst.expires) - sk_dst_reset(sk); + if (rt) { + if (dst->obsolete > 0) { + ip_rt_put(rt); + ret = NULL; + } else if ((rt->rt_flags & RTCF_REDIRECTED) || + rt->dst.expires) { + ip_rt_put(rt); + ret = NULL; + } + } + return ret; } /* diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 3731d74e2b28..defd581ae9d6 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -84,8 +84,7 @@ enum rt6_nud_state { static struct dst_entry *ip6_dst_check(struct dst_entry *dst, u32 cookie); static unsigned int ip6_default_advmss(const struct dst_entry *dst); static unsigned int ip6_mtu(const struct dst_entry *dst); -static void ip6_negative_advice(struct sock *sk, - struct dst_entry *dst); +static struct dst_entry *ip6_negative_advice(struct dst_entry *); static void ip6_dst_destroy(struct dst_entry *); static void ip6_dst_ifdown(struct dst_entry *, struct net_device *dev, int how); @@ -2659,24 +2658,24 @@ static struct dst_entry *ip6_dst_check(struct dst_entry *dst, u32 cookie) return dst_ret; } -static void ip6_negative_advice(struct sock *sk, - struct dst_entry *dst) +static struct dst_entry *ip6_negative_advice(struct dst_entry *dst) { struct rt6_info *rt = (struct rt6_info *) dst; - if (rt->rt6i_flags & RTF_CACHE) { - rcu_read_lock(); - if (rt6_check_expired(rt)) { - /* counteract the dst_release() in sk_dst_reset() */ - dst_hold(dst); - sk_dst_reset(sk); - - rt6_remove_exception_rt(rt); + if (rt) { + if (rt->rt6i_flags & RTF_CACHE) { + rcu_read_lock(); + if (rt6_check_expired(rt)) { + rt6_remove_exception_rt(rt); + dst = NULL; + } + rcu_read_unlock(); + } else { + dst_release(dst); + dst = NULL; } - rcu_read_unlock(); - return; } - sk_dst_reset(sk); + return dst; } static void ip6_link_failure(struct sk_buff *skb) diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c index cbf9387b1650..8632776ae4da 100644 --- a/net/xfrm/xfrm_policy.c +++ b/net/xfrm/xfrm_policy.c @@ -3778,10 +3778,15 @@ static void xfrm_link_failure(struct sk_buff *skb) /* Impossible. Such dst must be popped before reaches point of failure. */ } -static void xfrm_negative_advice(struct sock *sk, struct dst_entry *dst) +static struct dst_entry *xfrm_negative_advice(struct dst_entry *dst) { - if (dst->obsolete) - sk_dst_reset(sk); + if (dst) { + if (dst->obsolete) { + dst_release(dst); + dst = NULL; + } + } + return dst; } static void xfrm_init_pmtu(struct xfrm_dst **bundle, int nr)