summaryrefslogtreecommitdiffstats
path: root/net/core/neighbour.c
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>1999-06-17 13:25:08 +0000
committerRalf Baechle <ralf@linux-mips.org>1999-06-17 13:25:08 +0000
commit59223edaa18759982db0a8aced0e77457d10c68e (patch)
tree89354903b01fa0a447bffeefe00df3044495db2e /net/core/neighbour.c
parentdb7d4daea91e105e3859cf461d7e53b9b77454b2 (diff)
Merge with Linux 2.3.6. Sorry, this isn't tested on silicon, I don't
have a MIPS box at hand.
Diffstat (limited to 'net/core/neighbour.c')
-rw-r--r--net/core/neighbour.c334
1 files changed, 223 insertions, 111 deletions
diff --git a/net/core/neighbour.c b/net/core/neighbour.c
index b96650bcd..6124fcfc3 100644
--- a/net/core/neighbour.c
+++ b/net/core/neighbour.c
@@ -28,33 +28,6 @@
#include <net/sock.h>
#include <linux/rtnetlink.h>
-/*
- NOTE. The most unpleasent question is serialization of
- accesses to resolved addresses. The problem is that addresses
- are modified by bh, but they are referenced from normal
- kernel thread. Before today no locking was made.
- My reasoning was that corrupted address token will be copied
- to packet with cosmologically small probability
- (it is even difficult to estimate such small number)
- and it is very silly to waste cycles in fast path to lock them.
-
- But now I changed my mind, but not because previous statement
- is wrong. Actually, neigh->ha MAY BE not opaque byte array,
- but reference to some private data. In this case even neglibible
- corruption probability becomes bug.
-
- - hh cache is protected by rwlock. It assumes that
- hh cache update procedure is short and fast, and that
- read_lock is cheaper than start_bh_atomic().
- - ha tokens, saved in neighbour entries, are protected
- by bh_atomic().
- - no protection is made in /proc reading. It is OK, because
- /proc is broken by design in any case, and
- corrupted output is normal behaviour there.
-
- --ANK (981025)
- */
-
#define NEIGH_DEBUG 1
#define NEIGH_PRINTK(x...) printk(x)
@@ -81,6 +54,46 @@ static int pneigh_ifdown(struct neigh_table *tbl, struct device *dev);
static int neigh_glbl_allocs;
static struct neigh_table *neigh_tables;
+#if defined(__i386__) && defined(__SMP__)
+#define ASSERT_WL(n) if ((int)((n)->lock.lock) >= 0) { printk("WL assertion failed at " __FILE__ "(%d):" __FUNCTION__ "\n", __LINE__); }
+#else
+#define ASSERT_WL(n) do { } while(0)
+#endif
+
+/*
+ Neighbour hash table buckets are protected with rwlock tbl->lock.
+
+ - All the scans/updates to hash buckets MUST be made under this lock.
+ - NOTHING clever should be made under this lock: no callbacks
+ to protocol backends, no attempts to send something to network.
+ It will result in deadlocks, if backend/driver wants to use neighbour
+ cache.
+ - If the entry requires some non-trivial actions, increase
+ its reference count and release table lock.
+
+ Neighbour entries are protected:
+ - with reference count.
+ - with rwlock neigh->lock
+
+ Reference count prevents destruction.
+
+ neigh->lock mainly serializes ll address data and its validity state.
+ However, the same lock is used to protect another entry fields:
+ - timer
+ - resolution queue
+
+ Again, nothing clever shall be made under neigh->lock,
+ the most complicated procedure, which we allow is dev->hard_header.
+ It is supposed, that dev->hard_header is simplistic and does
+ not make callbacks to neighbour tables.
+
+ The last lock is neigh_tbl_lock. It is pure SMP lock, protecting
+ list of neighbour tables. This list is used only in process context,
+ so that this lock is useless with big kernel lock.
+ */
+
+static rwlock_t neigh_tbl_lock = RW_LOCK_UNLOCKED;
+
static int neigh_blackhole(struct sk_buff *skb)
{
kfree_skb(skb);
@@ -104,13 +117,11 @@ static int neigh_forced_gc(struct neigh_table *tbl)
int shrunk = 0;
int i;
- if (atomic_read(&tbl->lock))
- return 0;
-
for (i=0; i<=NEIGH_HASHMASK; i++) {
struct neighbour *n, **np;
np = &tbl->hash_buckets[i];
+ write_lock_bh(&tbl->lock);
while ((n = *np) != NULL) {
/* Neighbour record may be discarded if:
- nobody refers to it.
@@ -122,6 +133,7 @@ static int neigh_forced_gc(struct neigh_table *tbl)
It is not clear, what is better table overflow
or flooding.
*/
+ write_lock(&n->lock);
if (atomic_read(&n->refcnt) == 0 &&
!(n->nud_state&NUD_PERMANENT) &&
(n->nud_state != NUD_INCOMPLETE ||
@@ -130,11 +142,14 @@ static int neigh_forced_gc(struct neigh_table *tbl)
n->tbl = NULL;
tbl->entries--;
shrunk = 1;
+ write_unlock(&n->lock);
neigh_destroy(n);
continue;
}
+ write_unlock(&n->lock);
np = &n->next;
}
+ write_unlock_bh(&tbl->lock);
}
tbl->last_flush = jiffies;
@@ -145,12 +160,8 @@ int neigh_ifdown(struct neigh_table *tbl, struct device *dev)
{
int i;
- if (atomic_read(&tbl->lock)) {
- NEIGH_PRINTK1("neigh_ifdown: impossible event 1763\n");
- return -EBUSY;
- }
+ write_lock_bh(&tbl->lock);
- start_bh_atomic();
for (i=0; i<=NEIGH_HASHMASK; i++) {
struct neighbour *n, **np;
@@ -161,6 +172,7 @@ int neigh_ifdown(struct neigh_table *tbl, struct device *dev)
continue;
}
*np = n->next;
+ write_lock(&n->lock);
n->tbl = NULL;
tbl->entries--;
if (atomic_read(&n->refcnt)) {
@@ -183,33 +195,32 @@ int neigh_ifdown(struct neigh_table *tbl, struct device *dev)
else
n->nud_state = NUD_NONE;
NEIGH_PRINTK2("neigh %p is stray.\n", n);
- } else
+ write_unlock(&n->lock);
+ } else {
+ write_unlock(&n->lock);
neigh_destroy(n);
+ }
}
}
del_timer(&tbl->proxy_timer);
skb_queue_purge(&tbl->proxy_queue);
pneigh_ifdown(tbl, dev);
- end_bh_atomic();
+ write_unlock_bh(&tbl->lock);
return 0;
}
-static struct neighbour *neigh_alloc(struct neigh_table *tbl, int creat)
+static struct neighbour *neigh_alloc(struct neigh_table *tbl)
{
struct neighbour *n;
unsigned long now = jiffies;
- if (tbl->entries > tbl->gc_thresh1) {
- if (creat < 0)
+ if (tbl->entries > tbl->gc_thresh3 ||
+ (tbl->entries > tbl->gc_thresh2 &&
+ now - tbl->last_flush > 5*HZ)) {
+ if (neigh_forced_gc(tbl) == 0 &&
+ tbl->entries > tbl->gc_thresh3)
return NULL;
- if (tbl->entries > tbl->gc_thresh3 ||
- (tbl->entries > tbl->gc_thresh2 &&
- now - tbl->last_flush > 5*HZ)) {
- if (neigh_forced_gc(tbl) == 0 &&
- tbl->entries > tbl->gc_thresh3)
- return NULL;
- }
}
n = kmalloc(tbl->entry_size, GFP_ATOMIC);
@@ -219,6 +230,7 @@ static struct neighbour *neigh_alloc(struct neigh_table *tbl, int creat)
memset(n, 0, tbl->entry_size);
skb_queue_head_init(&n->arp_queue);
+ n->lock = RW_LOCK_UNLOCKED;
n->updated = n->used = now;
n->nud_state = NUD_NONE;
n->output = neigh_blackhole;
@@ -231,9 +243,8 @@ static struct neighbour *neigh_alloc(struct neigh_table *tbl, int creat)
return n;
}
-
-struct neighbour * __neigh_lookup(struct neigh_table *tbl, const void *pkey,
- struct device *dev, int creat)
+struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey,
+ struct device *dev)
{
struct neighbour *n;
u32 hash_val;
@@ -245,17 +256,26 @@ struct neighbour * __neigh_lookup(struct neigh_table *tbl, const void *pkey,
hash_val ^= hash_val>>3;
hash_val = (hash_val^dev->ifindex)&NEIGH_HASHMASK;
+ read_lock_bh(&tbl->lock);
for (n = tbl->hash_buckets[hash_val]; n; n = n->next) {
if (dev == n->dev &&
memcmp(n->primary_key, pkey, key_len) == 0) {
atomic_inc(&n->refcnt);
- return n;
+ break;
}
}
- if (!creat)
- return NULL;
+ read_unlock_bh(&tbl->lock);
+ return n;
+}
+
+struct neighbour * neigh_create(struct neigh_table *tbl, const void *pkey,
+ struct device *dev)
+{
+ struct neighbour *n, *n1;
+ u32 hash_val;
+ int key_len = tbl->key_len;
- n = neigh_alloc(tbl, creat);
+ n = neigh_alloc(tbl);
if (n == NULL)
return NULL;
@@ -275,11 +295,30 @@ struct neighbour * __neigh_lookup(struct neigh_table *tbl, const void *pkey,
}
n->confirmed = jiffies - (n->parms->base_reachable_time<<1);
- atomic_set(&n->refcnt, 1);
+
+ hash_val = *(u32*)(pkey + key_len - 4);
+ hash_val ^= (hash_val>>16);
+ hash_val ^= hash_val>>8;
+ hash_val ^= hash_val>>3;
+ hash_val = (hash_val^dev->ifindex)&NEIGH_HASHMASK;
+
+ write_lock_bh(&tbl->lock);
+ for (n1 = tbl->hash_buckets[hash_val]; n1; n1 = n1->next) {
+ if (dev == n1->dev &&
+ memcmp(n1->primary_key, pkey, key_len) == 0) {
+ atomic_inc(&n1->refcnt);
+ write_unlock_bh(&tbl->lock);
+ neigh_destroy(n);
+ return n1;
+ }
+ }
+
tbl->entries++;
+ n->tbl = tbl;
+ atomic_set(&n->refcnt, 1);
n->next = tbl->hash_buckets[hash_val];
tbl->hash_buckets[hash_val] = n;
- n->tbl = tbl;
+ write_unlock_bh(&tbl->lock);
NEIGH_PRINTK2("neigh %p is created.\n", n);
return n;
}
@@ -391,7 +430,9 @@ void neigh_destroy(struct neighbour *neigh)
while ((hh = neigh->hh) != NULL) {
neigh->hh = hh->hh_next;
hh->hh_next = NULL;
+ write_lock_bh(&hh->hh_lock);
hh->hh_output = neigh_blackhole;
+ write_unlock_bh(&hh->hh_lock);
if (atomic_dec_and_test(&hh->hh_refcnt))
kfree(hh);
}
@@ -409,6 +450,8 @@ void neigh_destroy(struct neighbour *neigh)
/* Neighbour state is suspicious;
disable fast path.
+
+ Called with write_locked neigh.
*/
static void neigh_suspect(struct neighbour *neigh)
{
@@ -416,6 +459,8 @@ static void neigh_suspect(struct neighbour *neigh)
NEIGH_PRINTK2("neigh %p is suspecteded.\n", neigh);
+ ASSERT_WL(neigh);
+
neigh->output = neigh->ops->output;
for (hh = neigh->hh; hh; hh = hh->hh_next)
@@ -424,6 +469,8 @@ static void neigh_suspect(struct neighbour *neigh)
/* Neighbour state is OK;
enable fast path.
+
+ Called with write_locked neigh.
*/
static void neigh_connect(struct neighbour *neigh)
{
@@ -431,6 +478,8 @@ static void neigh_connect(struct neighbour *neigh)
NEIGH_PRINTK2("neigh %p is connected.\n", neigh);
+ ASSERT_WL(neigh);
+
neigh->output = neigh->ops->connected_output;
for (hh = neigh->hh; hh; hh = hh->hh_next)
@@ -446,6 +495,8 @@ static void neigh_connect(struct neighbour *neigh)
If a routine wants to know TRUE entry state, it calls
neigh_sync before checking state.
+
+ Called with write_locked neigh.
*/
static void neigh_sync(struct neighbour *n)
@@ -453,6 +504,7 @@ static void neigh_sync(struct neighbour *n)
unsigned long now = jiffies;
u8 state = n->nud_state;
+ ASSERT_WL(n);
if (state&(NUD_NOARP|NUD_PERMANENT))
return;
if (state&NUD_REACHABLE) {
@@ -476,11 +528,8 @@ static void neigh_periodic_timer(unsigned long arg)
unsigned long now = jiffies;
int i;
- if (atomic_read(&tbl->lock)) {
- tbl->gc_timer.expires = now + 1*HZ;
- add_timer(&tbl->gc_timer);
- return;
- }
+
+ write_lock(&tbl->lock);
/*
* periodicly recompute ReachableTime from random function
@@ -498,10 +547,15 @@ static void neigh_periodic_timer(unsigned long arg)
np = &tbl->hash_buckets[i];
while ((n = *np) != NULL) {
- unsigned state = n->nud_state;
+ unsigned state;
- if (state&(NUD_PERMANENT|NUD_IN_TIMER))
+ write_lock(&n->lock);
+
+ state = n->nud_state;
+ if (state&(NUD_PERMANENT|NUD_IN_TIMER)) {
+ write_unlock(&n->lock);
goto next_elt;
+ }
if ((long)(n->used - n->confirmed) < 0)
n->used = n->confirmed;
@@ -512,6 +566,7 @@ static void neigh_periodic_timer(unsigned long arg)
n->tbl = NULL;
n->next = NULL;
tbl->entries--;
+ write_unlock(&n->lock);
neigh_destroy(n);
continue;
}
@@ -521,6 +576,7 @@ static void neigh_periodic_timer(unsigned long arg)
n->nud_state = NUD_STALE;
neigh_suspect(n);
}
+ write_unlock(&n->lock);
next_elt:
np = &n->next;
@@ -529,6 +585,7 @@ next_elt:
tbl->gc_timer.expires = now + tbl->gc_interval;
add_timer(&tbl->gc_timer);
+ write_unlock(&tbl->lock);
}
static __inline__ int neigh_max_probes(struct neighbour *n)
@@ -544,11 +601,17 @@ static void neigh_timer_handler(unsigned long arg)
{
unsigned long now = jiffies;
struct neighbour *neigh = (struct neighbour*)arg;
- unsigned state = neigh->nud_state;
+ unsigned state;
+ int notify = 0;
+
+ write_lock(&neigh->lock);
+ atomic_inc(&neigh->refcnt);
+
+ state = neigh->nud_state;
if (!(state&NUD_IN_TIMER)) {
NEIGH_PRINTK1("neigh: timer & !nud_in_timer\n");
- return;
+ goto out;
}
if ((state&NUD_VALID) &&
@@ -556,18 +619,19 @@ static void neigh_timer_handler(unsigned long arg)
neigh->nud_state = NUD_REACHABLE;
NEIGH_PRINTK2("neigh %p is still alive.\n", neigh);
neigh_connect(neigh);
- return;
+ goto out;
}
if (state == NUD_DELAY) {
NEIGH_PRINTK2("neigh %p is probed.\n", neigh);
neigh->nud_state = NUD_PROBE;
- neigh->probes = 0;
+ atomic_set(&neigh->probes, 0);
}
- if (neigh->probes >= neigh_max_probes(neigh)) {
+ if (atomic_read(&neigh->probes) >= neigh_max_probes(neigh)) {
struct sk_buff *skb;
neigh->nud_state = NUD_FAILED;
+ notify = 1;
neigh->tbl->stats.res_failed++;
NEIGH_PRINTK2("neigh %p is failed.\n", neigh);
@@ -576,44 +640,60 @@ static void neigh_timer_handler(unsigned long arg)
So that, we try to be accurate and avoid dead loop. --ANK
*/
- while(neigh->nud_state==NUD_FAILED && (skb=__skb_dequeue(&neigh->arp_queue)) != NULL)
+ while(neigh->nud_state==NUD_FAILED && (skb=__skb_dequeue(&neigh->arp_queue)) != NULL) {
+ write_unlock(&neigh->lock);
neigh->ops->error_report(neigh, skb);
+ write_lock(&neigh->lock);
+ }
skb_queue_purge(&neigh->arp_queue);
- return;
+ goto out;
}
neigh->timer.expires = now + neigh->parms->retrans_time;
add_timer(&neigh->timer);
+ write_unlock(&neigh->lock);
neigh->ops->solicit(neigh, skb_peek(&neigh->arp_queue));
- neigh->probes++;
+ atomic_inc(&neigh->probes);
+ neigh_release(neigh);
+ return;
+
+out:
+ write_unlock(&neigh->lock);
+#ifdef CONFIG_ARPD
+ if (notify && neigh->parms->app_probes)
+ neigh_app_notify(neigh);
+#endif
+ neigh_release(neigh);
}
int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
{
- start_bh_atomic();
+ write_lock_bh(&neigh->lock);
if (!(neigh->nud_state&(NUD_CONNECTED|NUD_DELAY|NUD_PROBE))) {
if (!(neigh->nud_state&(NUD_STALE|NUD_INCOMPLETE))) {
if (neigh->tbl == NULL) {
NEIGH_PRINTK2("neigh %p used after death.\n", neigh);
if (skb)
kfree_skb(skb);
- end_bh_atomic();
+ write_unlock_bh(&neigh->lock);
return 1;
}
if (neigh->parms->mcast_probes + neigh->parms->app_probes) {
- neigh->probes = neigh->parms->ucast_probes;
+ atomic_set(&neigh->probes, neigh->parms->ucast_probes);
neigh->nud_state = NUD_INCOMPLETE;
neigh->timer.expires = jiffies + neigh->parms->retrans_time;
add_timer(&neigh->timer);
-
+ write_unlock_bh(&neigh->lock);
neigh->ops->solicit(neigh, skb);
- neigh->probes++;
+ atomic_inc(&neigh->probes);
+ write_lock_bh(&neigh->lock);
} else {
neigh->nud_state = NUD_FAILED;
+ write_unlock_bh(&neigh->lock);
+
if (skb)
kfree_skb(skb);
- end_bh_atomic();
return 1;
}
}
@@ -627,7 +707,7 @@ int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
}
__skb_queue_head(&neigh->arp_queue, skb);
}
- end_bh_atomic();
+ write_unlock_bh(&neigh->lock);
return 1;
}
if (neigh->nud_state == NUD_STALE) {
@@ -637,7 +717,7 @@ int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
add_timer(&neigh->timer);
}
}
- end_bh_atomic();
+ write_unlock_bh(&neigh->lock);
return 0;
}
@@ -649,9 +729,9 @@ static __inline__ void neigh_update_hhs(struct neighbour *neigh)
if (update) {
for (hh=neigh->hh; hh; hh=hh->hh_next) {
- write_lock_irq(&hh->hh_lock);
+ write_lock_bh(&hh->hh_lock);
update(hh, neigh->dev, neigh->ha);
- write_unlock_irq(&hh->hh_lock);
+ write_unlock_bh(&hh->hh_lock);
}
}
}
@@ -663,15 +743,23 @@ static __inline__ void neigh_update_hhs(struct neighbour *neigh)
-- new is new state.
-- override==1 allows to override existing lladdr, if it is different.
-- arp==0 means that the change is administrative.
+
+ Caller MUST hold reference count on the entry.
*/
int neigh_update(struct neighbour *neigh, u8 *lladdr, u8 new, int override, int arp)
{
- u8 old = neigh->nud_state;
+ u8 old;
+ int err;
+ int notify = 0;
struct device *dev = neigh->dev;
+ write_lock_bh(&neigh->lock);
+ old = neigh->nud_state;
+
+ err = -EPERM;
if (arp && (old&(NUD_NOARP|NUD_PERMANENT)))
- return -EPERM;
+ goto out;
if (!(new&NUD_VALID)) {
if (old&NUD_IN_TIMER)
@@ -679,7 +767,9 @@ int neigh_update(struct neighbour *neigh, u8 *lladdr, u8 new, int override, int
if (old&NUD_CONNECTED)
neigh_suspect(neigh);
neigh->nud_state = new;
- return 0;
+ err = 0;
+ notify = old&NUD_VALID;
+ goto out;
}
/* Compare new lladdr with cached one */
@@ -696,14 +786,15 @@ int neigh_update(struct neighbour *neigh, u8 *lladdr, u8 new, int override, int
if (memcmp(lladdr, neigh->ha, dev->addr_len) == 0)
lladdr = neigh->ha;
else if (!override)
- return -EPERM;
+ goto out;
}
} else {
/* No address is supplied; if we know something,
use it, otherwise discard the request.
*/
+ err = -EINVAL;
if (!(old&NUD_VALID))
- return -EINVAL;
+ goto out;
lladdr = neigh->ha;
}
@@ -716,10 +807,11 @@ int neigh_update(struct neighbour *neigh, u8 *lladdr, u8 new, int override, int
/* If entry was valid and address is not changed,
do not change entry state, if new one is STALE.
*/
+ err = 0;
if (old&NUD_VALID) {
if (lladdr == neigh->ha)
if (new == old || (new == NUD_STALE && (old&NUD_CONNECTED)))
- return 0;
+ goto out;
}
if (old&NUD_IN_TIMER)
del_timer(&neigh->timer);
@@ -729,12 +821,11 @@ int neigh_update(struct neighbour *neigh, u8 *lladdr, u8 new, int override, int
neigh_update_hhs(neigh);
neigh->confirmed = jiffies - (neigh->parms->base_reachable_time<<1);
#ifdef CONFIG_ARPD
- if (neigh->parms->app_probes)
- neigh_app_notify(neigh);
+ notify = 1;
#endif
}
if (new == old)
- return 0;
+ goto out;
if (new&NUD_CONNECTED)
neigh_connect(neigh);
else
@@ -747,14 +838,22 @@ int neigh_update(struct neighbour *neigh, u8 *lladdr, u8 new, int override, int
while (neigh->nud_state&NUD_VALID &&
(skb=__skb_dequeue(&neigh->arp_queue)) != NULL) {
struct neighbour *n1 = neigh;
+ write_unlock_bh(&neigh->lock);
/* On shaper/eql skb->dst->neighbour != neigh :( */
if (skb->dst && skb->dst->neighbour)
n1 = skb->dst->neighbour;
n1->output(skb);
+ write_lock_bh(&neigh->lock);
}
skb_queue_purge(&neigh->arp_queue);
}
- return 0;
+out:
+ write_unlock_bh(&neigh->lock);
+#ifdef CONFIG_ARPD
+ if (notify && neigh->parms->app_probes)
+ neigh_app_notify(neigh);
+#endif
+ return err;
}
struct neighbour * neigh_event_ns(struct neigh_table *tbl,
@@ -837,15 +936,15 @@ int neigh_resolve_output(struct sk_buff *skb)
int err;
struct device *dev = neigh->dev;
if (dev->hard_header_cache && dst->hh == NULL) {
- start_bh_atomic();
+ write_lock_bh(&neigh->lock);
if (dst->hh == NULL)
neigh_hh_init(neigh, dst, dst->ops->protocol);
err = dev->hard_header(skb, dev, ntohs(skb->protocol), neigh->ha, NULL, skb->len);
- end_bh_atomic();
+ write_unlock_bh(&neigh->lock);
} else {
- start_bh_atomic();
+ read_lock_bh(&neigh->lock);
err = dev->hard_header(skb, dev, ntohs(skb->protocol), neigh->ha, NULL, skb->len);
- end_bh_atomic();
+ read_unlock_bh(&neigh->lock);
}
if (err >= 0)
return neigh->ops->queue_xmit(skb);
@@ -871,9 +970,9 @@ int neigh_connected_output(struct sk_buff *skb)
__skb_pull(skb, skb->nh.raw - skb->data);
- start_bh_atomic();
+ read_lock_bh(&neigh->lock);
err = dev->hard_header(skb, dev, ntohs(skb->protocol), neigh->ha, NULL, skb->len);
- end_bh_atomic();
+ read_unlock_bh(&neigh->lock);
if (err >= 0)
return neigh->ops->queue_xmit(skb);
kfree_skb(skb);
@@ -947,8 +1046,10 @@ struct neigh_parms *neigh_parms_alloc(struct device *dev, struct neigh_table *tb
return NULL;
}
}
+ write_lock_bh(&tbl->lock);
p->next = tbl->parms.next;
tbl->parms.next = p;
+ write_unlock_bh(&tbl->lock);
}
return p;
}
@@ -959,10 +1060,11 @@ void neigh_parms_release(struct neigh_table *tbl, struct neigh_parms *parms)
if (parms == NULL || parms == &tbl->parms)
return;
+ write_lock_bh(&tbl->lock);
for (p = &tbl->parms.next; *p; p = &(*p)->next) {
if (*p == parms) {
*p = parms->next;
- synchronize_bh();
+ write_unlock_bh(&tbl->lock);
#ifdef CONFIG_SYSCTL
neigh_sysctl_unregister(parms);
#endif
@@ -970,6 +1072,7 @@ void neigh_parms_release(struct neigh_table *tbl, struct neigh_parms *parms)
return;
}
}
+ write_unlock_bh(&tbl->lock);
NEIGH_PRINTK1("neigh_release_parms: not found\n");
}
@@ -981,6 +1084,7 @@ void neigh_table_init(struct neigh_table *tbl)
tbl->parms.reachable_time = neigh_rand_reach_time(tbl->parms.base_reachable_time);
init_timer(&tbl->gc_timer);
+ tbl->lock = RW_LOCK_UNLOCKED;
tbl->gc_timer.data = (unsigned long)tbl;
tbl->gc_timer.function = neigh_periodic_timer;
tbl->gc_timer.expires = now + tbl->gc_interval + tbl->parms.reachable_time;
@@ -993,29 +1097,30 @@ void neigh_table_init(struct neigh_table *tbl)
tbl->last_flush = now;
tbl->last_rand = now + tbl->parms.reachable_time*20;
+ write_lock(&neigh_tbl_lock);
tbl->next = neigh_tables;
neigh_tables = tbl;
+ write_unlock(&neigh_tbl_lock);
}
int neigh_table_clear(struct neigh_table *tbl)
{
struct neigh_table **tp;
- start_bh_atomic();
del_timer(&tbl->gc_timer);
del_timer(&tbl->proxy_timer);
skb_queue_purge(&tbl->proxy_queue);
neigh_ifdown(tbl, NULL);
- end_bh_atomic();
if (tbl->entries)
printk(KERN_CRIT "neighbour leakage\n");
+ write_lock(&neigh_tbl_lock);
for (tp = &neigh_tables; *tp; tp = &(*tp)->next) {
if (*tp == tbl) {
*tp = tbl->next;
- synchronize_bh();
break;
}
}
+ write_unlock(&neigh_tbl_lock);
#ifdef CONFIG_SYSCTL
neigh_sysctl_unregister(&tbl->parms);
#endif
@@ -1037,12 +1142,14 @@ int neigh_delete(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
return -ENODEV;
}
+ read_lock(&neigh_tbl_lock);
for (tbl=neigh_tables; tbl; tbl = tbl->next) {
int err = 0;
struct neighbour *n;
if (tbl->family != ndm->ndm_family)
continue;
+ read_unlock(&neigh_tbl_lock);
if (nda[NDA_DST-1] == NULL ||
nda[NDA_DST-1]->rta_len != RTA_LENGTH(tbl->key_len))
@@ -1054,15 +1161,14 @@ int neigh_delete(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
if (dev == NULL)
return -EINVAL;
- start_bh_atomic();
- n = __neigh_lookup(tbl, RTA_DATA(nda[NDA_DST-1]), dev, 0);
+ n = neigh_lookup(tbl, RTA_DATA(nda[NDA_DST-1]), dev);
if (n) {
err = neigh_update(n, NULL, NUD_FAILED, 1, 0);
neigh_release(n);
}
- end_bh_atomic();
return err;
}
+ read_unlock(&neigh_tbl_lock);
return -EADDRNOTAVAIL;
}
@@ -1079,12 +1185,15 @@ int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
return -ENODEV;
}
+ read_lock(&neigh_tbl_lock);
for (tbl=neigh_tables; tbl; tbl = tbl->next) {
int err = 0;
struct neighbour *n;
if (tbl->family != ndm->ndm_family)
continue;
+ read_unlock(&neigh_tbl_lock);
+
if (nda[NDA_DST-1] == NULL ||
nda[NDA_DST-1]->rta_len != RTA_LENGTH(tbl->key_len))
return -EINVAL;
@@ -1098,8 +1207,7 @@ int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
if (nda[NDA_LLADDR-1] != NULL &&
nda[NDA_LLADDR-1]->rta_len != RTA_LENGTH(dev->addr_len))
return -EINVAL;
- start_bh_atomic();
- n = __neigh_lookup(tbl, RTA_DATA(nda[NDA_DST-1]), dev, 0);
+ n = neigh_lookup(tbl, RTA_DATA(nda[NDA_DST-1]), dev);
if (n) {
if (nlh->nlmsg_flags&NLM_F_EXCL)
err = -EEXIST;
@@ -1117,9 +1225,9 @@ int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
}
if (n)
neigh_release(n);
- end_bh_atomic();
return err;
}
+ read_unlock(&neigh_tbl_lock);
return -EADDRNOTAVAIL;
}
@@ -1139,15 +1247,17 @@ static int neigh_fill_info(struct sk_buff *skb, struct neighbour *n,
ndm->ndm_family = n->ops->family;
ndm->ndm_flags = n->flags;
ndm->ndm_type = n->type;
- ndm->ndm_state = n->nud_state;
ndm->ndm_ifindex = n->dev->ifindex;
RTA_PUT(skb, NDA_DST, n->tbl->key_len, n->primary_key);
+ read_lock_bh(&n->lock);
+ ndm->ndm_state = n->nud_state;
if (n->nud_state&NUD_VALID)
RTA_PUT(skb, NDA_LLADDR, n->dev->addr_len, n->ha);
ci.ndm_used = now - n->used;
ci.ndm_confirmed = now - n->confirmed;
ci.ndm_updated = now - n->updated;
ci.ndm_refcnt = atomic_read(&n->refcnt);
+ read_unlock_bh(&n->lock);
RTA_PUT(skb, NDA_CACHEINFO, sizeof(ci), &ci);
nlh->nlmsg_len = skb->tail - b;
return skb->len;
@@ -1171,20 +1281,20 @@ static int neigh_dump_table(struct neigh_table *tbl, struct sk_buff *skb, struct
if (h < s_h) continue;
if (h > s_h)
s_idx = 0;
- start_bh_atomic();
+ read_lock_bh(&tbl->lock);
for (n = tbl->hash_buckets[h], idx = 0; n;
n = n->next, idx++) {
if (idx < s_idx)
continue;
if (neigh_fill_info(skb, n, NETLINK_CB(cb->skb).pid,
cb->nlh->nlmsg_seq, RTM_NEWNEIGH) <= 0) {
- end_bh_atomic();
+ read_unlock_bh(&tbl->lock);
cb->args[1] = h;
cb->args[2] = idx;
return -1;
}
}
- end_bh_atomic();
+ read_unlock_bh(&tbl->lock);
}
cb->args[1] = h;
@@ -1201,6 +1311,7 @@ int neigh_dump_info(struct sk_buff *skb, struct netlink_callback *cb)
s_t = cb->args[0];
+ read_lock(&neigh_tbl_lock);
for (tbl=neigh_tables, t=0; tbl; tbl = tbl->next, t++) {
if (t < s_t) continue;
if (family && tbl->family != family)
@@ -1210,6 +1321,7 @@ int neigh_dump_info(struct sk_buff *skb, struct netlink_callback *cb)
if (neigh_dump_table(tbl, skb, cb) < 0)
break;
}
+ read_unlock(&neigh_tbl_lock);
cb->args[0] = t;