Commit 859bd2ef authored by David Ahern's avatar David Ahern Committed by David S. Miller

net: Evict neighbor entries on carrier down

When a link's carrier goes down it could be a sign of the port changing
networks. If the new network has overlapping addresses with the old one,
then the kernel will continue trying to use neighbor entries established
based on the old network until the entries finally age out - meaning a
potentially long delay with communications not working.

This patch evicts neighbor entries on carrier down with the exception of
those marked permanent. Permanent entries are managed by userspace (either
an admin or a routing daemon such as FRR).
Signed-off-by: default avatarDavid Ahern <dsahern@gmail.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 7c6bb7d2
...@@ -323,6 +323,7 @@ void __neigh_set_probe_once(struct neighbour *neigh); ...@@ -323,6 +323,7 @@ void __neigh_set_probe_once(struct neighbour *neigh);
bool neigh_remove_one(struct neighbour *ndel, struct neigh_table *tbl); bool neigh_remove_one(struct neighbour *ndel, struct neigh_table *tbl);
void neigh_changeaddr(struct neigh_table *tbl, struct net_device *dev); void neigh_changeaddr(struct neigh_table *tbl, struct net_device *dev);
int neigh_ifdown(struct neigh_table *tbl, struct net_device *dev); int neigh_ifdown(struct neigh_table *tbl, struct net_device *dev);
int neigh_carrier_down(struct neigh_table *tbl, struct net_device *dev);
int neigh_resolve_output(struct neighbour *neigh, struct sk_buff *skb); int neigh_resolve_output(struct neighbour *neigh, struct sk_buff *skb);
int neigh_connected_output(struct neighbour *neigh, struct sk_buff *skb); int neigh_connected_output(struct neighbour *neigh, struct sk_buff *skb);
int neigh_direct_output(struct neighbour *neigh, struct sk_buff *skb); int neigh_direct_output(struct neighbour *neigh, struct sk_buff *skb);
......
...@@ -232,7 +232,8 @@ static void pneigh_queue_purge(struct sk_buff_head *list) ...@@ -232,7 +232,8 @@ static void pneigh_queue_purge(struct sk_buff_head *list)
} }
} }
static void neigh_flush_dev(struct neigh_table *tbl, struct net_device *dev) static void neigh_flush_dev(struct neigh_table *tbl, struct net_device *dev,
bool skip_perm)
{ {
int i; int i;
struct neigh_hash_table *nht; struct neigh_hash_table *nht;
...@@ -250,6 +251,10 @@ static void neigh_flush_dev(struct neigh_table *tbl, struct net_device *dev) ...@@ -250,6 +251,10 @@ static void neigh_flush_dev(struct neigh_table *tbl, struct net_device *dev)
np = &n->next; np = &n->next;
continue; continue;
} }
if (skip_perm && n->nud_state & NUD_PERMANENT) {
np = &n->next;
continue;
}
rcu_assign_pointer(*np, rcu_assign_pointer(*np,
rcu_dereference_protected(n->next, rcu_dereference_protected(n->next,
lockdep_is_held(&tbl->lock))); lockdep_is_held(&tbl->lock)));
...@@ -285,21 +290,35 @@ static void neigh_flush_dev(struct neigh_table *tbl, struct net_device *dev) ...@@ -285,21 +290,35 @@ static void neigh_flush_dev(struct neigh_table *tbl, struct net_device *dev)
void neigh_changeaddr(struct neigh_table *tbl, struct net_device *dev) void neigh_changeaddr(struct neigh_table *tbl, struct net_device *dev)
{ {
write_lock_bh(&tbl->lock); write_lock_bh(&tbl->lock);
neigh_flush_dev(tbl, dev); neigh_flush_dev(tbl, dev, false);
write_unlock_bh(&tbl->lock); write_unlock_bh(&tbl->lock);
} }
EXPORT_SYMBOL(neigh_changeaddr); EXPORT_SYMBOL(neigh_changeaddr);
int neigh_ifdown(struct neigh_table *tbl, struct net_device *dev) static int __neigh_ifdown(struct neigh_table *tbl, struct net_device *dev,
bool skip_perm)
{ {
write_lock_bh(&tbl->lock); write_lock_bh(&tbl->lock);
neigh_flush_dev(tbl, dev); neigh_flush_dev(tbl, dev, skip_perm);
pneigh_ifdown_and_unlock(tbl, dev); pneigh_ifdown_and_unlock(tbl, dev);
del_timer_sync(&tbl->proxy_timer); del_timer_sync(&tbl->proxy_timer);
pneigh_queue_purge(&tbl->proxy_queue); pneigh_queue_purge(&tbl->proxy_queue);
return 0; return 0;
} }
int neigh_carrier_down(struct neigh_table *tbl, struct net_device *dev)
{
__neigh_ifdown(tbl, dev, true);
return 0;
}
EXPORT_SYMBOL(neigh_carrier_down);
int neigh_ifdown(struct neigh_table *tbl, struct net_device *dev)
{
__neigh_ifdown(tbl, dev, false);
return 0;
}
EXPORT_SYMBOL(neigh_ifdown); EXPORT_SYMBOL(neigh_ifdown);
static struct neighbour *neigh_alloc(struct neigh_table *tbl, struct net_device *dev) static struct neighbour *neigh_alloc(struct neigh_table *tbl, struct net_device *dev)
......
...@@ -1255,6 +1255,8 @@ static int arp_netdev_event(struct notifier_block *this, unsigned long event, ...@@ -1255,6 +1255,8 @@ static int arp_netdev_event(struct notifier_block *this, unsigned long event,
change_info = ptr; change_info = ptr;
if (change_info->flags_changed & IFF_NOARP) if (change_info->flags_changed & IFF_NOARP)
neigh_changeaddr(&arp_tbl, dev); neigh_changeaddr(&arp_tbl, dev);
if (!netif_carrier_ok(dev))
neigh_carrier_down(&arp_tbl, dev);
break; break;
default: default:
break; break;
......
...@@ -1784,6 +1784,8 @@ static int ndisc_netdev_event(struct notifier_block *this, unsigned long event, ...@@ -1784,6 +1784,8 @@ static int ndisc_netdev_event(struct notifier_block *this, unsigned long event,
change_info = ptr; change_info = ptr;
if (change_info->flags_changed & IFF_NOARP) if (change_info->flags_changed & IFF_NOARP)
neigh_changeaddr(&nd_tbl, dev); neigh_changeaddr(&nd_tbl, dev);
if (!netif_carrier_ok(dev))
neigh_carrier_down(&nd_tbl, dev);
break; break;
case NETDEV_DOWN: case NETDEV_DOWN:
neigh_ifdown(&nd_tbl, dev); neigh_ifdown(&nd_tbl, dev);
......
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