[NEIGH]: Fix IP-over-ATM and ARP interaction.
Simon Kelley [Fri, 12 May 2006 21:56:08 +0000 (14:56 -0700)]
The classical IP over ATM code maintains its own IPv4 <-> <ATM stuff>
ARP table, using the standard neighbour-table code. The
neigh_table_init function adds this neighbour table to a linked list
of all neighbor tables which is used by the functions neigh_delete()
neigh_add() and neightbl_set(), all called by the netlink code.

Once the ATM neighbour table is added to the list, there are two
tables with family == AF_INET there, and ARP entries sent via netlink
go into the first table with matching family. This is indeterminate
and often wrong.

To see the bug, on a kernel with CLIP enabled, create a standard IPv4
ARP entry by pinging an unused address on a local subnet. Then attempt
to complete that entry by doing

ip neigh replace <ip address> lladdr <some mac address> nud reachable

Looking at the ARP tables by using

ip neigh show

will reveal two ARP entries for the same address. One of these can be
found in /proc/net/arp, and the other in /proc/net/atm/arp.

This patch adds a new function, neigh_table_init_no_netlink() which
does everything the neigh_table_init() does, except add the table to
the netlink all-arp-tables chain. In addition neigh_table_init() has a
check that all tables on the chain have a distinct address family.
The init call in clip.c is changed to call
neigh_table_init_no_netlink().

Since ATM ARP tables are rather more complicated than can currently be
handled by the available rtattrs in the netlink protocol, no
functionality is lost by this patch, and non-ATM ARP manipulation via
netlink is rescued. A more complete solution would involve a rtattr
for ATM ARP entries and some way for the netlink code to give
neigh_add and friends more information than just address family with
which to find the correct ARP table.

[ I've changed the assertion checking in neigh_table_init() to not
  use BUG_ON() while holding neigh_tbl_lock.  Instead we remember that
  we found an existing tbl with the same family, and after dropping
  the lock we'll give a diagnostic kernel log message and a stack dump.
  -DaveM ]

Signed-off-by: Simon Kelley <simon@thekelleys.org.uk>
Signed-off-by: David S. Miller <davem@davemloft.net>

include/net/neighbour.h
net/atm/clip.c
net/core/neighbour.c

index b0666d6..4901ee4 100644 (file)
@@ -211,6 +211,7 @@ struct neigh_table
 #define NEIGH_UPDATE_F_ADMIN                   0x80000000
 
 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,
index 1a786bf..72d8529 100644 (file)
@@ -963,7 +963,7 @@ static struct file_operations arp_seq_fops = {
 static int __init atm_clip_init(void)
 {
        struct proc_dir_entry *p;
-       neigh_table_init(&clip_tbl);
+       neigh_table_init_no_netlink(&clip_tbl);
 
        clip_tbl_hook = &clip_tbl;
        register_atm_ioctl(&clip_ioctl_ops);
index 4cf878e..50a8c73 100644 (file)
@@ -1326,8 +1326,7 @@ void neigh_parms_destroy(struct neigh_parms *parms)
        kfree(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;
@@ -1383,10 +1382,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);
+       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)
@@ -2657,6 +2673,7 @@ EXPORT_SYMBOL(neigh_rand_reach_time);
 EXPORT_SYMBOL(neigh_resolve_output);
 EXPORT_SYMBOL(neigh_table_clear);
 EXPORT_SYMBOL(neigh_table_init);
+EXPORT_SYMBOL(neigh_table_init_no_netlink);
 EXPORT_SYMBOL(neigh_update);
 EXPORT_SYMBOL(neigh_update_hhs);
 EXPORT_SYMBOL(pneigh_enqueue);