Commit 9cc20b26 authored by Eric Dumazet's avatar Eric Dumazet Committed by David S. Miller

ipv4: fix redirect handling

commit f39925db (ipv4: Cache learned redirect information in
inetpeer.) introduced a regression in ICMP redirect handling.

It assumed ipv4_dst_check() would be called because all possible routes
were attached to the inetpeer we modify in ip_rt_redirect(), but thats
not true.

commit 7cc9150e (route: fix ICMP redirect validation) tried to fix
this but solution was not complete. (It fixed only one route)

So we must lookup existing routes (including different TOS values) and
call check_peer_redir() on them.
Reported-by: default avatarIvan Zahariev <famzah@icdsoft.com>
Signed-off-by: default avatarEric Dumazet <eric.dumazet@gmail.com>
CC: Flavio Leitner <fbl@redhat.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent fb120c0a
...@@ -1304,16 +1304,42 @@ static void rt_del(unsigned hash, struct rtable *rt) ...@@ -1304,16 +1304,42 @@ static void rt_del(unsigned hash, struct rtable *rt)
spin_unlock_bh(rt_hash_lock_addr(hash)); 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 */ /* called in rcu_read_lock() section */
void ip_rt_redirect(__be32 old_gw, __be32 daddr, __be32 new_gw, void ip_rt_redirect(__be32 old_gw, __be32 daddr, __be32 new_gw,
__be32 saddr, struct net_device *dev) __be32 saddr, struct net_device *dev)
{ {
int s, i; int s, i;
struct in_device *in_dev = __in_dev_get_rcu(dev); struct in_device *in_dev = __in_dev_get_rcu(dev);
struct rtable *rt;
__be32 skeys[2] = { saddr, 0 }; __be32 skeys[2] = { saddr, 0 };
int ikeys[2] = { dev->ifindex, 0 }; int ikeys[2] = { dev->ifindex, 0 };
struct flowi4 fl4;
struct inet_peer *peer; struct inet_peer *peer;
struct net *net; struct net *net;
...@@ -1336,33 +1362,42 @@ void ip_rt_redirect(__be32 old_gw, __be32 daddr, __be32 new_gw, ...@@ -1336,33 +1362,42 @@ void ip_rt_redirect(__be32 old_gw, __be32 daddr, __be32 new_gw,
goto reject_redirect; goto reject_redirect;
} }
memset(&fl4, 0, sizeof(fl4));
fl4.daddr = daddr;
for (s = 0; s < 2; s++) { for (s = 0; s < 2; s++) {
for (i = 0; i < 2; i++) { for (i = 0; i < 2; i++) {
fl4.flowi4_oif = ikeys[i]; unsigned int hash;
fl4.saddr = skeys[s]; struct rtable __rcu **rthp;
rt = __ip_route_output_key(net, &fl4); struct rtable *rt;
if (IS_ERR(rt))
continue; hash = rt_hash(daddr, skeys[s], ikeys[i], rt_genid(net));
if (rt->dst.error || rt->dst.dev != dev || rthp = &rt_hash_table[hash].chain;
rt->rt_gateway != old_gw) {
ip_rt_put(rt); while ((rt = rcu_dereference(*rthp)) != NULL) {
continue; rthp = &rt->dst.rt_next;
}
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) if (!rt->peer)
rt_bind_peer(rt, rt->rt_dst, 1); rt_bind_peer(rt, rt->rt_dst, 1);
peer = rt->peer; peer = rt->peer;
if (peer) { if (peer) {
peer->redirect_learned.a4 = new_gw; if (peer->redirect_learned.a4 != new_gw) {
atomic_inc(&__rt_peer_genid); peer->redirect_learned.a4 = new_gw;
atomic_inc(&__rt_peer_genid);
}
check_peer_redir(&rt->dst, peer);
}
} }
ip_rt_put(rt);
return;
} }
} }
return; return;
...@@ -1649,33 +1684,6 @@ static void ip_rt_update_pmtu(struct dst_entry *dst, u32 mtu) ...@@ -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) static struct dst_entry *ipv4_dst_check(struct dst_entry *dst, u32 cookie)
{ {
......
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