aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSylvain Rochet <gradator@gradator.net>2010-04-07 22:23:39 +0200
committerWilly Tarreau <w@1wt.eu>2010-08-28 13:00:37 +0200
commit3aa199850f00529b4ec4126c2ef89ee682e28830 (patch)
treed701df87d6d91277726be6763bbd9f533e42b282
parent68f98fe730acff8d8488cc1cc1e0d9c3549788a9 (diff)
downloadlinux-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.h1
-rw-r--r--net/atm/clip.c2
-rw-r--r--net/core/neighbour.c23
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)