diff options
author | Sylvain Rochet <gradator@gradator.net> | 2010-04-07 22:23:39 +0200 |
---|---|---|
committer | Willy Tarreau <w@1wt.eu> | 2010-08-28 13:00:37 +0200 |
commit | 3aa199850f00529b4ec4126c2ef89ee682e28830 (patch) | |
tree | d701df87d6d91277726be6763bbd9f533e42b282 | |
parent | 68f98fe730acff8d8488cc1cc1e0d9c3549788a9 (diff) | |
download | linux-2.4-3aa199850f00529b4ec4126c2ef89ee682e28830.tar.gz |
net: permanent NUD pins ethernet interfaces when ATM is compiled in.
When ATM and Ethernet are compiled in, ATM and Ethernet create their
NEIGH/ARP tables, they are both assigned to family AF_INET.
int neigh_add(....) {
...
for (tbl=neigh_tables; tbl; tbl = tbl->next) {
if (tbl->family != ndm->ndm_family)
continue;
...
}
As ATM table is created before Ethernet(main?) table,
net/core/neighbour.c::neigh_add() function add all permanent IP ARP
Ethernet NUD to the IP ATM table, which is wrong.
Therefore, when net/core/neighbour.c::neigh_ifdown() is called ARP
entries are not cleared, leaving dev->refcnt to a value that will never
be able to reach 0 anymore.
So, when net/core/dev.c::unregister_netdevice() is called it stalls
without being able to destroy the interface leaving the system with no
network tools working anymore.
This is really easy to reproduce:
openvpn --mktun --dev tap10
ip addr add 10.20.30.20/24 dev tap10
ip link set up dev tap10
ip neighbour add 10.20.30.40 lladdr 01:02:03:04:05:06 nud permanent dev tap10
ip link set down dev tap10
openvpn --rmtun --dev tap10
and then kernel log starts being filled by:
unregister_netdevice: waiting for tap10 to become free. Usage count = 2
unregister_netdevice: waiting for tap10 to become free. Usage count = 2
unregister_netdevice: waiting for tap10 to become free. Usage count = 2
unregister_netdevice: waiting for tap10 to become free. Usage count = 2
Finally made a patch that follows what Linux 2.6 does, which consists of
having "netlink" and "no-netlink" tables.
Diagnosed-by: Sylvain Rochet <gradator@gradator.net>
Signed-off-by: Willy Tarreau <w@1wt.eu>
-rw-r--r-- | include/net/neighbour.h | 1 | ||||
-rw-r--r-- | net/atm/clip.c | 2 | ||||
-rw-r--r-- | net/core/neighbour.c | 23 |
3 files changed, 22 insertions, 4 deletions
diff --git a/include/net/neighbour.h b/include/net/neighbour.h index 7ee3810cb68857..f05cc2169fc44d 100644 --- a/include/net/neighbour.h +++ b/include/net/neighbour.h @@ -192,6 +192,7 @@ struct neigh_table }; extern void neigh_table_init(struct neigh_table *tbl); +extern void neigh_table_init_no_netlink(struct neigh_table *tbl); extern int neigh_table_clear(struct neigh_table *tbl); extern struct neighbour * neigh_lookup(struct neigh_table *tbl, const void *pkey, diff --git a/net/atm/clip.c b/net/atm/clip.c index 5905d30034d38c..977649410753a4 100644 --- a/net/atm/clip.c +++ b/net/atm/clip.c @@ -752,7 +752,7 @@ static struct atm_clip_ops __atm_clip_ops = { static int __init atm_clip_init(void) { - neigh_table_init(&clip_tbl); + neigh_table_init_no_netlink(&clip_tbl); clip_tbl_hook = &clip_tbl; atm_clip_ops_set(&__atm_clip_ops); diff --git a/net/core/neighbour.c b/net/core/neighbour.c index 7630a627a2904a..27cf5b491f4538 100644 --- a/net/core/neighbour.c +++ b/net/core/neighbour.c @@ -1248,7 +1248,7 @@ void neigh_parms_release(struct neigh_table *tbl, struct neigh_parms *parms) } -void neigh_table_init(struct neigh_table *tbl) +void neigh_table_init_no_netlink(struct neigh_table *tbl) { unsigned long now = jiffies; unsigned long phsize; @@ -1302,10 +1302,27 @@ void neigh_table_init(struct neigh_table *tbl) tbl->last_flush = now; tbl->last_rand = now + tbl->parms.reachable_time*20; +} + +void neigh_table_init(struct neigh_table *tbl) +{ + struct neigh_table *tmp; + + neigh_table_init_no_netlink(tbl); write_lock(&neigh_tbl_lock); - tbl->next = neigh_tables; - neigh_tables = tbl; + for (tmp = neigh_tables; tmp; tmp = tmp->next) { + if (tmp->family == tbl->family) + break; + } + tbl->next = neigh_tables; + neigh_tables = tbl; write_unlock(&neigh_tbl_lock); + + if (unlikely(tmp)) { + printk(KERN_ERR "NEIGH: Registering multiple tables for " + "family %d\n", tbl->family); + dump_stack(); + } } int neigh_table_clear(struct neigh_table *tbl) |