diff options
Diffstat (limited to 'net/ipv4/route.c')
| -rw-r--r-- | net/ipv4/route.c | 118 | 
1 files changed, 65 insertions, 53 deletions
diff --git a/net/ipv4/route.c b/net/ipv4/route.c index 155138d8ec8..0c74da8a047 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -1304,16 +1304,42 @@ static void rt_del(unsigned hash, struct rtable *rt)  	spin_unlock_bh(rt_hash_lock_addr(hash));  } +static int check_peer_redir(struct dst_entry *dst, struct inet_peer *peer) +{ +	struct rtable *rt = (struct rtable *) dst; +	__be32 orig_gw = rt->rt_gateway; +	struct neighbour *n, *old_n; + +	dst_confirm(&rt->dst); + +	rt->rt_gateway = peer->redirect_learned.a4; + +	n = ipv4_neigh_lookup(&rt->dst, &rt->rt_gateway); +	if (IS_ERR(n)) +		return PTR_ERR(n); +	old_n = xchg(&rt->dst._neighbour, n); +	if (old_n) +		neigh_release(old_n); +	if (!n || !(n->nud_state & NUD_VALID)) { +		if (n) +			neigh_event_send(n, NULL); +		rt->rt_gateway = orig_gw; +		return -EAGAIN; +	} else { +		rt->rt_flags |= RTCF_REDIRECTED; +		call_netevent_notifiers(NETEVENT_NEIGH_UPDATE, n); +	} +	return 0; +} +  /* called in rcu_read_lock() section */  void ip_rt_redirect(__be32 old_gw, __be32 daddr, __be32 new_gw,  		    __be32 saddr, struct net_device *dev)  {  	int s, i;  	struct in_device *in_dev = __in_dev_get_rcu(dev); -	struct rtable *rt;  	__be32 skeys[2] = { saddr, 0 };  	int    ikeys[2] = { dev->ifindex, 0 }; -	struct flowi4 fl4;  	struct inet_peer *peer;  	struct net *net; @@ -1336,33 +1362,42 @@ void ip_rt_redirect(__be32 old_gw, __be32 daddr, __be32 new_gw,  			goto reject_redirect;  	} -	memset(&fl4, 0, sizeof(fl4)); -	fl4.daddr = daddr;  	for (s = 0; s < 2; s++) {  		for (i = 0; i < 2; i++) { -			fl4.flowi4_oif = ikeys[i]; -			fl4.saddr = skeys[s]; -			rt = __ip_route_output_key(net, &fl4); -			if (IS_ERR(rt)) -				continue; +			unsigned int hash; +			struct rtable __rcu **rthp; +			struct rtable *rt; -			if (rt->dst.error || rt->dst.dev != dev || -			    rt->rt_gateway != old_gw) { -				ip_rt_put(rt); -				continue; -			} +			hash = rt_hash(daddr, skeys[s], ikeys[i], rt_genid(net)); -			if (!rt->peer) -				rt_bind_peer(rt, rt->rt_dst, 1); +			rthp = &rt_hash_table[hash].chain; -			peer = rt->peer; -			if (peer) { -				peer->redirect_learned.a4 = new_gw; -				atomic_inc(&__rt_peer_genid); -			} +			while ((rt = rcu_dereference(*rthp)) != NULL) { +				rthp = &rt->dst.rt_next; -			ip_rt_put(rt); -			return; +				if (rt->rt_key_dst != daddr || +				    rt->rt_key_src != skeys[s] || +				    rt->rt_oif != ikeys[i] || +				    rt_is_input_route(rt) || +				    rt_is_expired(rt) || +				    !net_eq(dev_net(rt->dst.dev), net) || +				    rt->dst.error || +				    rt->dst.dev != dev || +				    rt->rt_gateway != old_gw) +					continue; + +				if (!rt->peer) +					rt_bind_peer(rt, rt->rt_dst, 1); + +				peer = rt->peer; +				if (peer) { +					if (peer->redirect_learned.a4 != new_gw) { +						peer->redirect_learned.a4 = new_gw; +						atomic_inc(&__rt_peer_genid); +					} +					check_peer_redir(&rt->dst, peer); +				} +			}  		}  	}  	return; @@ -1649,33 +1684,6 @@ static void ip_rt_update_pmtu(struct dst_entry *dst, u32 mtu)  	}  } -static int check_peer_redir(struct dst_entry *dst, struct inet_peer *peer) -{ -	struct rtable *rt = (struct rtable *) dst; -	__be32 orig_gw = rt->rt_gateway; -	struct neighbour *n, *old_n; - -	dst_confirm(&rt->dst); - -	rt->rt_gateway = peer->redirect_learned.a4; - -	n = ipv4_neigh_lookup(&rt->dst, &rt->rt_gateway); -	if (IS_ERR(n)) -		return PTR_ERR(n); -	old_n = xchg(&rt->dst._neighbour, n); -	if (old_n) -		neigh_release(old_n); -	if (!n || !(n->nud_state & NUD_VALID)) { -		if (n) -			neigh_event_send(n, NULL); -		rt->rt_gateway = orig_gw; -		return -EAGAIN; -	} else { -		rt->rt_flags |= RTCF_REDIRECTED; -		call_netevent_notifiers(NETEVENT_NEIGH_UPDATE, n); -	} -	return 0; -}  static struct dst_entry *ipv4_dst_check(struct dst_entry *dst, u32 cookie)  { @@ -2845,7 +2853,7 @@ static int rt_fill_info(struct net *net,  	struct rtable *rt = skb_rtable(skb);  	struct rtmsg *r;  	struct nlmsghdr *nlh; -	long expires = 0; +	unsigned long expires = 0;  	const struct inet_peer *peer = rt->peer;  	u32 id = 0, ts = 0, tsage = 0, error; @@ -2902,8 +2910,12 @@ static int rt_fill_info(struct net *net,  			tsage = get_seconds() - peer->tcp_ts_stamp;  		}  		expires = ACCESS_ONCE(peer->pmtu_expires); -		if (expires) -			expires -= jiffies; +		if (expires) { +			if (time_before(jiffies, expires)) +				expires -= jiffies; +			else +				expires = 0; +		}  	}  	if (rt_is_input_route(rt)) {  |