Commit 410e9a9b authored by David S. Miller's avatar David S. Miller

Merge http://kernel-acme.bkbits.net:8080/net-cleanups-2.5-neighbour

into nuts.ninka.net:/home/davem/src/BK/net-2.5
parents 3da9cf28 3e978a40
...@@ -101,7 +101,7 @@ static int neigh_blackhole(struct sk_buff *skb) ...@@ -101,7 +101,7 @@ static int neigh_blackhole(struct sk_buff *skb)
unsigned long neigh_rand_reach_time(unsigned long base) unsigned long neigh_rand_reach_time(unsigned long base)
{ {
return (net_random() % base) + (base>>1); return (net_random() % base) + (base >> 1);
} }
...@@ -110,7 +110,7 @@ static int neigh_forced_gc(struct neigh_table *tbl) ...@@ -110,7 +110,7 @@ static int neigh_forced_gc(struct neigh_table *tbl)
int shrunk = 0; int shrunk = 0;
int i; int i;
for (i=0; i<=NEIGH_HASHMASK; i++) { for (i = 0; i <= NEIGH_HASHMASK; i++) {
struct neighbour *n, **np; struct neighbour *n, **np;
np = &tbl->hash_buckets[i]; np = &tbl->hash_buckets[i];
...@@ -128,7 +128,7 @@ static int neigh_forced_gc(struct neigh_table *tbl) ...@@ -128,7 +128,7 @@ static int neigh_forced_gc(struct neigh_table *tbl)
*/ */
write_lock(&n->lock); write_lock(&n->lock);
if (atomic_read(&n->refcnt) == 1 && if (atomic_read(&n->refcnt) == 1 &&
!(n->nud_state&NUD_PERMANENT) && !(n->nud_state & NUD_PERMANENT) &&
(n->nud_state != NUD_INCOMPLETE || (n->nud_state != NUD_INCOMPLETE ||
jiffies - n->used > n->parms->retrans_time)) { jiffies - n->used > n->parms->retrans_time)) {
*np = n->next; *np = n->next;
...@@ -150,12 +150,11 @@ static int neigh_forced_gc(struct neigh_table *tbl) ...@@ -150,12 +150,11 @@ static int neigh_forced_gc(struct neigh_table *tbl)
static int neigh_del_timer(struct neighbour *n) static int neigh_del_timer(struct neighbour *n)
{ {
if (n->nud_state & NUD_IN_TIMER) { if ((n->nud_state & NUD_IN_TIMER) &&
if (del_timer(&n->timer)) { del_timer(&n->timer)) {
neigh_release(n); neigh_release(n);
return 1; return 1;
} }
}
return 0; return 0;
} }
...@@ -175,10 +174,9 @@ int neigh_ifdown(struct neigh_table *tbl, struct net_device *dev) ...@@ -175,10 +174,9 @@ int neigh_ifdown(struct neigh_table *tbl, struct net_device *dev)
write_lock_bh(&tbl->lock); write_lock_bh(&tbl->lock);
for (i=0; i<=NEIGH_HASHMASK; i++) { for (i = 0; i <= NEIGH_HASHMASK; i++) {
struct neighbour *n, **np; struct neighbour *n, **np = &tbl->hash_buckets[i];
np = &tbl->hash_buckets[i];
while ((n = *np) != NULL) { while ((n = *np) != NULL) {
if (dev && n->dev != dev) { if (dev && n->dev != dev) {
np = &n->next; np = &n->next;
...@@ -202,7 +200,7 @@ int neigh_ifdown(struct neigh_table *tbl, struct net_device *dev) ...@@ -202,7 +200,7 @@ int neigh_ifdown(struct neigh_table *tbl, struct net_device *dev)
n->parms = &tbl->parms; n->parms = &tbl->parms;
skb_queue_purge(&n->arp_queue); skb_queue_purge(&n->arp_queue);
n->output = neigh_blackhole; n->output = neigh_blackhole;
if (n->nud_state&NUD_VALID) if (n->nud_state & NUD_VALID)
n->nud_state = NUD_NOARP; n->nud_state = NUD_NOARP;
else else
n->nud_state = NUD_NONE; n->nud_state = NUD_NONE;
...@@ -223,20 +221,20 @@ int neigh_ifdown(struct neigh_table *tbl, struct net_device *dev) ...@@ -223,20 +221,20 @@ int neigh_ifdown(struct neigh_table *tbl, struct net_device *dev)
static struct neighbour *neigh_alloc(struct neigh_table *tbl) static struct neighbour *neigh_alloc(struct neigh_table *tbl)
{ {
struct neighbour *n; struct neighbour *n = NULL;
unsigned long now = jiffies; unsigned long now = jiffies;
if (tbl->entries > tbl->gc_thresh3 || if (tbl->entries > tbl->gc_thresh3 ||
(tbl->entries > tbl->gc_thresh2 && (tbl->entries > tbl->gc_thresh2 &&
now - tbl->last_flush > 5*HZ)) { now - tbl->last_flush > 5 * HZ)) {
if (neigh_forced_gc(tbl) == 0 && if (!neigh_forced_gc(tbl) &&
tbl->entries > tbl->gc_thresh3) tbl->entries > tbl->gc_thresh3)
return NULL; goto out;
} }
n = kmem_cache_alloc(tbl->kmem_cachep, SLAB_ATOMIC); n = kmem_cache_alloc(tbl->kmem_cachep, SLAB_ATOMIC);
if (n == NULL) if (!n)
return NULL; goto out;
memset(n, 0, tbl->entry_size); memset(n, 0, tbl->entry_size);
...@@ -255,6 +253,7 @@ static struct neighbour *neigh_alloc(struct neigh_table *tbl) ...@@ -255,6 +253,7 @@ static struct neighbour *neigh_alloc(struct neigh_table *tbl)
n->tbl = tbl; n->tbl = tbl;
atomic_set(&n->refcnt, 1); atomic_set(&n->refcnt, 1);
n->dead = 1; n->dead = 1;
out:
return n; return n;
} }
...@@ -262,15 +261,12 @@ struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey, ...@@ -262,15 +261,12 @@ struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey,
struct net_device *dev) struct net_device *dev)
{ {
struct neighbour *n; struct neighbour *n;
u32 hash_val;
int key_len = tbl->key_len; int key_len = tbl->key_len;
u32 hash_val = tbl->hash(pkey, dev);
hash_val = tbl->hash(pkey, dev);
read_lock_bh(&tbl->lock); read_lock_bh(&tbl->lock);
for (n = tbl->hash_buckets[hash_val]; n; n = n->next) { for (n = tbl->hash_buckets[hash_val]; n; n = n->next) {
if (dev == n->dev && if (dev == n->dev && !memcmp(n->primary_key, pkey, key_len)) {
memcmp(n->primary_key, pkey, key_len) == 0) {
neigh_hold(n); neigh_hold(n);
break; break;
} }
...@@ -279,17 +275,18 @@ struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey, ...@@ -279,17 +275,18 @@ struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey,
return n; return n;
} }
struct neighbour * neigh_create(struct neigh_table *tbl, const void *pkey, struct neighbour *neigh_create(struct neigh_table *tbl, const void *pkey,
struct net_device *dev) struct net_device *dev)
{ {
struct neighbour *n, *n1;
u32 hash_val; u32 hash_val;
int key_len = tbl->key_len; int key_len = tbl->key_len;
int error; int error;
struct neighbour *n1, *rc, *n = neigh_alloc(tbl);
n = neigh_alloc(tbl); if (!n) {
if (n == NULL) rc = ERR_PTR(-ENOBUFS);
return ERR_PTR(-ENOBUFS); goto out;
}
memcpy(n->primary_key, pkey, key_len); memcpy(n->primary_key, pkey, key_len);
n->dev = dev; n->dev = dev;
...@@ -297,29 +294,28 @@ struct neighbour * neigh_create(struct neigh_table *tbl, const void *pkey, ...@@ -297,29 +294,28 @@ struct neighbour * neigh_create(struct neigh_table *tbl, const void *pkey,
/* Protocol specific setup. */ /* Protocol specific setup. */
if (tbl->constructor && (error = tbl->constructor(n)) < 0) { if (tbl->constructor && (error = tbl->constructor(n)) < 0) {
neigh_release(n); rc = ERR_PTR(error);
return ERR_PTR(error); goto out_neigh_release;
} }
/* Device specific setup. */ /* Device specific setup. */
if (n->parms->neigh_setup && if (n->parms->neigh_setup &&
(error = n->parms->neigh_setup(n)) < 0) { (error = n->parms->neigh_setup(n)) < 0) {
neigh_release(n); rc = ERR_PTR(error);
return ERR_PTR(error); goto out_neigh_release;
} }
n->confirmed = jiffies - (n->parms->base_reachable_time<<1); n->confirmed = jiffies - (n->parms->base_reachable_time << 1);
hash_val = tbl->hash(pkey, dev); hash_val = tbl->hash(pkey, dev);
write_lock_bh(&tbl->lock); write_lock_bh(&tbl->lock);
for (n1 = tbl->hash_buckets[hash_val]; n1; n1 = n1->next) { for (n1 = tbl->hash_buckets[hash_val]; n1; n1 = n1->next) {
if (dev == n1->dev && if (dev == n1->dev && !memcmp(n1->primary_key, pkey, key_len)) {
memcmp(n1->primary_key, pkey, key_len) == 0) {
neigh_hold(n1); neigh_hold(n1);
write_unlock_bh(&tbl->lock); write_unlock_bh(&tbl->lock);
neigh_release(n); rc = n1;
return n1; goto out_neigh_release;
} }
} }
...@@ -329,69 +325,77 @@ struct neighbour * neigh_create(struct neigh_table *tbl, const void *pkey, ...@@ -329,69 +325,77 @@ struct neighbour * neigh_create(struct neigh_table *tbl, const void *pkey,
neigh_hold(n); neigh_hold(n);
write_unlock_bh(&tbl->lock); write_unlock_bh(&tbl->lock);
NEIGH_PRINTK2("neigh %p is created.\n", n); NEIGH_PRINTK2("neigh %p is created.\n", n);
return n; rc = n;
out:
return rc;
out_neigh_release:
neigh_release(n);
goto out;
} }
struct pneigh_entry * pneigh_lookup(struct neigh_table *tbl, const void *pkey, struct pneigh_entry * pneigh_lookup(struct neigh_table *tbl, const void *pkey,
struct net_device *dev, int creat) struct net_device *dev, int creat)
{ {
struct pneigh_entry *n; struct pneigh_entry *n;
u32 hash_val;
int key_len = tbl->key_len; int key_len = tbl->key_len;
u32 hash_val = *(u32 *)(pkey + key_len - 4);
hash_val = *(u32*)(pkey + key_len - 4); hash_val ^= (hash_val >> 16);
hash_val ^= (hash_val>>16); hash_val ^= hash_val >> 8;
hash_val ^= hash_val>>8; hash_val ^= hash_val >> 4;
hash_val ^= hash_val>>4;
hash_val &= PNEIGH_HASHMASK; hash_val &= PNEIGH_HASHMASK;
read_lock_bh(&tbl->lock); read_lock_bh(&tbl->lock);
for (n = tbl->phash_buckets[hash_val]; n; n = n->next) { for (n = tbl->phash_buckets[hash_val]; n; n = n->next) {
if (memcmp(n->key, pkey, key_len) == 0 && if (!memcmp(n->key, pkey, key_len) &&
(n->dev == dev || !n->dev)) { (n->dev == dev || !n->dev)) {
read_unlock_bh(&tbl->lock); read_unlock_bh(&tbl->lock);
return n; goto out;
} }
} }
read_unlock_bh(&tbl->lock); read_unlock_bh(&tbl->lock);
n = NULL;
if (!creat) if (!creat)
return NULL; goto out;
n = kmalloc(sizeof(*n) + key_len, GFP_KERNEL); n = kmalloc(sizeof(*n) + key_len, GFP_KERNEL);
if (n == NULL) if (!n)
return NULL; goto out;
memcpy(n->key, pkey, key_len); memcpy(n->key, pkey, key_len);
n->dev = dev; n->dev = dev;
if (tbl->pconstructor && tbl->pconstructor(n)) { if (tbl->pconstructor && tbl->pconstructor(n)) {
kfree(n); kfree(n);
return NULL; n = NULL;
goto out;
} }
write_lock_bh(&tbl->lock); write_lock_bh(&tbl->lock);
n->next = tbl->phash_buckets[hash_val]; n->next = tbl->phash_buckets[hash_val];
tbl->phash_buckets[hash_val] = n; tbl->phash_buckets[hash_val] = n;
write_unlock_bh(&tbl->lock); write_unlock_bh(&tbl->lock);
out:
return n; return n;
} }
int pneigh_delete(struct neigh_table *tbl, const void *pkey, struct net_device *dev) int pneigh_delete(struct neigh_table *tbl, const void *pkey,
struct net_device *dev)
{ {
struct pneigh_entry *n, **np; struct pneigh_entry *n, **np;
u32 hash_val;
int key_len = tbl->key_len; int key_len = tbl->key_len;
u32 hash_val = *(u32 *)(pkey + key_len - 4);
hash_val = *(u32*)(pkey + key_len - 4); hash_val ^= (hash_val >> 16);
hash_val ^= (hash_val>>16); hash_val ^= hash_val >> 8;
hash_val ^= hash_val>>8; hash_val ^= hash_val >> 4;
hash_val ^= hash_val>>4;
hash_val &= PNEIGH_HASHMASK; hash_val &= PNEIGH_HASHMASK;
for (np = &tbl->phash_buckets[hash_val]; (n=*np) != NULL; np = &n->next) { for (np = &tbl->phash_buckets[hash_val]; (n = *np) != NULL;
if (memcmp(n->key, pkey, key_len) == 0 && n->dev == dev) { np = &n->next) {
if (!memcmp(n->key, pkey, key_len) && n->dev == dev) {
write_lock_bh(&tbl->lock); write_lock_bh(&tbl->lock);
*np = n->next; *np = n->next;
write_unlock_bh(&tbl->lock); write_unlock_bh(&tbl->lock);
...@@ -409,10 +413,10 @@ static int pneigh_ifdown(struct neigh_table *tbl, struct net_device *dev) ...@@ -409,10 +413,10 @@ static int pneigh_ifdown(struct neigh_table *tbl, struct net_device *dev)
struct pneigh_entry *n, **np; struct pneigh_entry *n, **np;
u32 h; u32 h;
for (h=0; h<=PNEIGH_HASHMASK; h++) { for (h = 0; h <= PNEIGH_HASHMASK; h++) {
np = &tbl->phash_buckets[h]; np = &tbl->phash_buckets[h];
while ((n=*np) != NULL) { while ((n = *np) != NULL) {
if (n->dev == dev || dev == NULL) { if (!dev || n->dev == dev) {
*np = n->next; *np = n->next;
if (tbl->pdestructor) if (tbl->pdestructor)
tbl->pdestructor(n); tbl->pdestructor(n);
...@@ -435,13 +439,14 @@ void neigh_destroy(struct neighbour *neigh) ...@@ -435,13 +439,14 @@ void neigh_destroy(struct neighbour *neigh)
struct hh_cache *hh; struct hh_cache *hh;
if (!neigh->dead) { if (!neigh->dead) {
printk("Destroying alive neighbour %p from %08lx\n", neigh, printk(KERN_WARNING
*(((unsigned long*)&neigh)-1)); "Destroying alive neighbour %p from %08lx\n", neigh,
*(((unsigned long *)&neigh) - 1));
return; return;
} }
if (neigh_del_timer(neigh)) if (neigh_del_timer(neigh))
printk("Impossible event.\n"); printk(KERN_WARNING "Impossible event.\n");
while ((hh = neigh->hh) != NULL) { while ((hh = neigh->hh) != NULL) {
neigh->hh = hh->hh_next; neigh->hh = hh->hh_next;
...@@ -519,14 +524,14 @@ static void neigh_sync(struct neighbour *n) ...@@ -519,14 +524,14 @@ static void neigh_sync(struct neighbour *n)
unsigned long now = jiffies; unsigned long now = jiffies;
u8 state = n->nud_state; u8 state = n->nud_state;
if (state&(NUD_NOARP|NUD_PERMANENT)) if (state & (NUD_NOARP | NUD_PERMANENT))
return; return;
if (state&NUD_REACHABLE) { if (state & NUD_REACHABLE) {
if (now - n->confirmed > n->parms->reachable_time) { if (now - n->confirmed > n->parms->reachable_time) {
n->nud_state = NUD_STALE; n->nud_state = NUD_STALE;
neigh_suspect(n); neigh_suspect(n);
} }
} else if (state&NUD_VALID) { } else if (state & NUD_VALID) {
if (now - n->confirmed < n->parms->reachable_time) { if (now - n->confirmed < n->parms->reachable_time) {
neigh_del_timer(n); neigh_del_timer(n);
n->nud_state = NUD_REACHABLE; n->nud_state = NUD_REACHABLE;
...@@ -537,7 +542,7 @@ static void neigh_sync(struct neighbour *n) ...@@ -537,7 +542,7 @@ static void neigh_sync(struct neighbour *n)
static void SMP_TIMER_NAME(neigh_periodic_timer)(unsigned long arg) static void SMP_TIMER_NAME(neigh_periodic_timer)(unsigned long arg)
{ {
struct neigh_table *tbl = (struct neigh_table*)arg; struct neigh_table *tbl = (struct neigh_table *)arg;
unsigned long now = jiffies; unsigned long now = jiffies;
int i; int i;
...@@ -548,14 +553,15 @@ static void SMP_TIMER_NAME(neigh_periodic_timer)(unsigned long arg) ...@@ -548,14 +553,15 @@ static void SMP_TIMER_NAME(neigh_periodic_timer)(unsigned long arg)
* periodicly recompute ReachableTime from random function * periodicly recompute ReachableTime from random function
*/ */
if (now - tbl->last_rand > 300*HZ) { if (now - tbl->last_rand > 300 * HZ) {
struct neigh_parms *p; struct neigh_parms *p;
tbl->last_rand = now; tbl->last_rand = now;
for (p=&tbl->parms; p; p = p->next) for (p = &tbl->parms; p; p = p->next)
p->reachable_time = neigh_rand_reach_time(p->base_reachable_time); p->reachable_time =
neigh_rand_reach_time(p->base_reachable_time);
} }
for (i=0; i <= NEIGH_HASHMASK; i++) { for (i = 0; i <= NEIGH_HASHMASK; i++) {
struct neighbour *n, **np; struct neighbour *n, **np;
np = &tbl->hash_buckets[i]; np = &tbl->hash_buckets[i];
...@@ -565,7 +571,7 @@ static void SMP_TIMER_NAME(neigh_periodic_timer)(unsigned long arg) ...@@ -565,7 +571,7 @@ static void SMP_TIMER_NAME(neigh_periodic_timer)(unsigned long arg)
write_lock(&n->lock); write_lock(&n->lock);
state = n->nud_state; state = n->nud_state;
if (state&(NUD_PERMANENT|NUD_IN_TIMER)) { if (state & (NUD_PERMANENT | NUD_IN_TIMER)) {
write_unlock(&n->lock); write_unlock(&n->lock);
goto next_elt; goto next_elt;
} }
...@@ -574,7 +580,8 @@ static void SMP_TIMER_NAME(neigh_periodic_timer)(unsigned long arg) ...@@ -574,7 +580,8 @@ static void SMP_TIMER_NAME(neigh_periodic_timer)(unsigned long arg)
n->used = n->confirmed; n->used = n->confirmed;
if (atomic_read(&n->refcnt) == 1 && if (atomic_read(&n->refcnt) == 1 &&
(state == NUD_FAILED || now - n->used > n->parms->gc_staletime)) { (state == NUD_FAILED ||
now - n->used > n->parms->gc_staletime)) {
*np = n->next; *np = n->next;
n->dead = 1; n->dead = 1;
write_unlock(&n->lock); write_unlock(&n->lock);
...@@ -582,7 +589,7 @@ static void SMP_TIMER_NAME(neigh_periodic_timer)(unsigned long arg) ...@@ -582,7 +589,7 @@ static void SMP_TIMER_NAME(neigh_periodic_timer)(unsigned long arg)
continue; continue;
} }
if (n->nud_state&NUD_REACHABLE && if (n->nud_state & NUD_REACHABLE &&
now - n->confirmed > n->parms->reachable_time) { now - n->confirmed > n->parms->reachable_time) {
n->nud_state = NUD_STALE; n->nud_state = NUD_STALE;
neigh_suspect(n); neigh_suspect(n);
...@@ -601,7 +608,7 @@ static void SMP_TIMER_NAME(neigh_periodic_timer)(unsigned long arg) ...@@ -601,7 +608,7 @@ static void SMP_TIMER_NAME(neigh_periodic_timer)(unsigned long arg)
#ifdef CONFIG_SMP #ifdef CONFIG_SMP
static void neigh_periodic_timer(unsigned long arg) static void neigh_periodic_timer(unsigned long arg)
{ {
struct neigh_table *tbl = (struct neigh_table*)arg; struct neigh_table *tbl = (struct neigh_table *)arg;
tasklet_schedule(&tbl->gc_task); tasklet_schedule(&tbl->gc_task);
} }
...@@ -619,7 +626,7 @@ static __inline__ int neigh_max_probes(struct neighbour *n) ...@@ -619,7 +626,7 @@ static __inline__ int neigh_max_probes(struct neighbour *n)
static void neigh_timer_handler(unsigned long arg) static void neigh_timer_handler(unsigned long arg)
{ {
unsigned long now = jiffies; unsigned long now = jiffies;
struct neighbour *neigh = (struct neighbour*)arg; struct neighbour *neigh = (struct neighbour *)arg;
unsigned state; unsigned state;
int notify = 0; int notify = 0;
...@@ -627,14 +634,14 @@ static void neigh_timer_handler(unsigned long arg) ...@@ -627,14 +634,14 @@ static void neigh_timer_handler(unsigned long arg)
state = neigh->nud_state; state = neigh->nud_state;
if (!(state&NUD_IN_TIMER)) { if (!(state & NUD_IN_TIMER)) {
#ifndef CONFIG_SMP #ifndef CONFIG_SMP
printk("neigh: timer & !nud_in_timer\n"); printk(KERN_WARNING "neigh: timer & !nud_in_timer\n");
#endif #endif
goto out; goto out;
} }
if ((state&NUD_VALID) && if ((state & NUD_VALID) &&
now - neigh->confirmed < neigh->parms->reachable_time) { now - neigh->confirmed < neigh->parms->reachable_time) {
neigh->nud_state = NUD_REACHABLE; neigh->nud_state = NUD_REACHABLE;
NEIGH_PRINTK2("neigh %p is still alive.\n", neigh); NEIGH_PRINTK2("neigh %p is still alive.\n", neigh);
...@@ -660,7 +667,8 @@ static void neigh_timer_handler(unsigned long arg) ...@@ -660,7 +667,8 @@ static void neigh_timer_handler(unsigned long arg)
So that, we try to be accurate and avoid dead loop. --ANK 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); write_unlock(&neigh->lock);
neigh->ops->error_report(neigh, skb); neigh->ops->error_report(neigh, skb);
write_lock(&neigh->lock); write_lock(&neigh->lock);
...@@ -688,14 +696,21 @@ static void neigh_timer_handler(unsigned long arg) ...@@ -688,14 +696,21 @@ static void neigh_timer_handler(unsigned long arg)
int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb) int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
{ {
int rc;
write_lock_bh(&neigh->lock); write_lock_bh(&neigh->lock);
if (!(neigh->nud_state&(NUD_CONNECTED|NUD_DELAY|NUD_PROBE))) {
if (!(neigh->nud_state&(NUD_STALE|NUD_INCOMPLETE))) { rc = 0;
if (neigh->nud_state & (NUD_CONNECTED | NUD_DELAY | NUD_PROBE))
goto out_unlock_bh;
if (!(neigh->nud_state & (NUD_STALE | NUD_INCOMPLETE))) {
if (neigh->parms->mcast_probes + neigh->parms->app_probes) { if (neigh->parms->mcast_probes + neigh->parms->app_probes) {
atomic_set(&neigh->probes, neigh->parms->ucast_probes); atomic_set(&neigh->probes, neigh->parms->ucast_probes);
neigh->nud_state = NUD_INCOMPLETE; neigh->nud_state = NUD_INCOMPLETE;
neigh_hold(neigh); neigh_hold(neigh);
neigh->timer.expires = jiffies + neigh->parms->retrans_time; neigh->timer.expires = jiffies +
neigh->parms->retrans_time;
add_timer(&neigh->timer); add_timer(&neigh->timer);
write_unlock_bh(&neigh->lock); write_unlock_bh(&neigh->lock);
neigh->ops->solicit(neigh, skb); neigh->ops->solicit(neigh, skb);
...@@ -710,9 +725,11 @@ int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb) ...@@ -710,9 +725,11 @@ int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
return 1; return 1;
} }
} }
if (neigh->nud_state == NUD_INCOMPLETE) { if (neigh->nud_state == NUD_INCOMPLETE) {
if (skb) { if (skb) {
if (skb_queue_len(&neigh->arp_queue) >= neigh->parms->queue_len) { if (skb_queue_len(&neigh->arp_queue) >=
neigh->parms->queue_len) {
struct sk_buff *buff; struct sk_buff *buff;
buff = neigh->arp_queue.next; buff = neigh->arp_queue.next;
__skb_unlink(buff, &neigh->arp_queue); __skb_unlink(buff, &neigh->arp_queue);
...@@ -720,29 +737,28 @@ int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb) ...@@ -720,29 +737,28 @@ int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
} }
__skb_queue_tail(&neigh->arp_queue, skb); __skb_queue_tail(&neigh->arp_queue, skb);
} }
write_unlock_bh(&neigh->lock); rc = 1;
return 1; } else if (neigh->nud_state == NUD_STALE) {
}
if (neigh->nud_state == NUD_STALE) {
NEIGH_PRINTK2("neigh %p is delayed.\n", neigh); NEIGH_PRINTK2("neigh %p is delayed.\n", neigh);
neigh_hold(neigh); neigh_hold(neigh);
neigh->nud_state = NUD_DELAY; neigh->nud_state = NUD_DELAY;
neigh->timer.expires = jiffies + neigh->parms->delay_probe_time; neigh->timer.expires = jiffies + neigh->parms->delay_probe_time;
add_timer(&neigh->timer); add_timer(&neigh->timer);
rc = 0;
} }
} out_unlock_bh:
write_unlock_bh(&neigh->lock); write_unlock_bh(&neigh->lock);
return 0; return rc;
} }
static __inline__ void neigh_update_hhs(struct neighbour *neigh) static __inline__ void neigh_update_hhs(struct neighbour *neigh)
{ {
struct hh_cache *hh; struct hh_cache *hh;
void (*update)(struct hh_cache*, struct net_device*, unsigned char*) = void (*update)(struct hh_cache*, struct net_device*, unsigned char *) =
neigh->dev->header_cache_update; neigh->dev->header_cache_update;
if (update) { if (update) {
for (hh=neigh->hh; hh; hh=hh->hh_next) { for (hh = neigh->hh; hh; hh = hh->hh_next) {
write_lock_bh(&hh->hh_lock); write_lock_bh(&hh->hh_lock);
update(hh, neigh->dev, neigh->ha); update(hh, neigh->dev, neigh->ha);
write_unlock_bh(&hh->hh_lock); write_unlock_bh(&hh->hh_lock);
...@@ -755,38 +771,45 @@ static __inline__ void neigh_update_hhs(struct neighbour *neigh) ...@@ -755,38 +771,45 @@ static __inline__ void neigh_update_hhs(struct neighbour *neigh)
/* Generic update routine. /* Generic update routine.
-- lladdr is new lladdr or NULL, if it is not supplied. -- lladdr is new lladdr or NULL, if it is not supplied.
-- new is new state. -- new is new state.
-- override==1 allows to override existing lladdr, if it is different. -- override == 1 allows to override existing lladdr, if it is different.
-- arp==0 means that the change is administrative. -- arp == 0 means that the change is administrative.
Caller MUST hold reference count on the entry. Caller MUST hold reference count on the entry.
*/ */
int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new, int override, int arp) int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new,
int override, int arp)
{ {
u8 old; u8 old;
int err; int err;
#ifdef CONFIG_ARPD
int notify = 0; int notify = 0;
struct net_device *dev = neigh->dev; #endif
struct net_device *dev;
write_lock_bh(&neigh->lock); write_lock_bh(&neigh->lock);
old = neigh->nud_state;
dev = neigh->dev;
old = neigh->nud_state;
err = -EPERM; err = -EPERM;
if (arp && (old&(NUD_NOARP|NUD_PERMANENT)))
if (arp && (old & (NUD_NOARP | NUD_PERMANENT)))
goto out; goto out;
if (!(new&NUD_VALID)) { if (!(new & NUD_VALID)) {
neigh_del_timer(neigh); neigh_del_timer(neigh);
if (old&NUD_CONNECTED) if (old & NUD_CONNECTED)
neigh_suspect(neigh); neigh_suspect(neigh);
neigh->nud_state = new; neigh->nud_state = new;
err = 0; err = 0;
notify = old&NUD_VALID; #ifdef CONFIG_ARPD
notify = old & NUD_VALID;
#endif
goto out; goto out;
} }
/* Compare new lladdr with cached one */ /* Compare new lladdr with cached one */
if (dev->addr_len == 0) { if (!dev->addr_len) {
/* First case: device needs no address. */ /* First case: device needs no address. */
lladdr = neigh->ha; lladdr = neigh->ha;
} else if (lladdr) { } else if (lladdr) {
...@@ -795,8 +818,8 @@ int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new, int override ...@@ -795,8 +818,8 @@ int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new, int override
- compare new & old - compare new & old
- if they are different, check override flag - if they are different, check override flag
*/ */
if (old&NUD_VALID) { if (old & NUD_VALID) {
if (memcmp(lladdr, neigh->ha, dev->addr_len) == 0) if (!memcmp(lladdr, neigh->ha, dev->addr_len))
lladdr = neigh->ha; lladdr = neigh->ha;
else if (!override) else if (!override)
goto out; goto out;
...@@ -806,14 +829,14 @@ int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new, int override ...@@ -806,14 +829,14 @@ int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new, int override
use it, otherwise discard the request. use it, otherwise discard the request.
*/ */
err = -EINVAL; err = -EINVAL;
if (!(old&NUD_VALID)) if (!(old & NUD_VALID))
goto out; goto out;
lladdr = neigh->ha; lladdr = neigh->ha;
} }
neigh_sync(neigh); neigh_sync(neigh);
old = neigh->nud_state; old = neigh->nud_state;
if (new&NUD_CONNECTED) if (new & NUD_CONNECTED)
neigh->confirmed = jiffies; neigh->confirmed = jiffies;
neigh->updated = jiffies; neigh->updated = jiffies;
...@@ -821,35 +844,35 @@ int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new, int override ...@@ -821,35 +844,35 @@ int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new, int override
do not change entry state, if new one is STALE. do not change entry state, if new one is STALE.
*/ */
err = 0; err = 0;
if (old&NUD_VALID) { if ((old & NUD_VALID) && lladdr == neigh->ha &&
if (lladdr == neigh->ha) (new == old || (new == NUD_STALE && (old & NUD_CONNECTED))))
if (new == old || (new == NUD_STALE && (old&NUD_CONNECTED)))
goto out; goto out;
}
neigh_del_timer(neigh); neigh_del_timer(neigh);
neigh->nud_state = new; neigh->nud_state = new;
if (lladdr != neigh->ha) { if (lladdr != neigh->ha) {
memcpy(&neigh->ha, lladdr, dev->addr_len); memcpy(&neigh->ha, lladdr, dev->addr_len);
neigh_update_hhs(neigh); neigh_update_hhs(neigh);
if (!(new&NUD_CONNECTED)) if (!(new & NUD_CONNECTED))
neigh->confirmed = jiffies - (neigh->parms->base_reachable_time<<1); neigh->confirmed = jiffies -
(neigh->parms->base_reachable_time << 1);
#ifdef CONFIG_ARPD #ifdef CONFIG_ARPD
notify = 1; notify = 1;
#endif #endif
} }
if (new == old) if (new == old)
goto out; goto out;
if (new&NUD_CONNECTED) if (new & NUD_CONNECTED)
neigh_connect(neigh); neigh_connect(neigh);
else else
neigh_suspect(neigh); neigh_suspect(neigh);
if (!(old&NUD_VALID)) { if (!(old & NUD_VALID)) {
struct sk_buff *skb; struct sk_buff *skb;
/* Again: avoid dead loop if something went wrong */ /* Again: avoid dead loop if something went wrong */
while (neigh->nud_state&NUD_VALID && while (neigh->nud_state & NUD_VALID &&
(skb=__skb_dequeue(&neigh->arp_queue)) != NULL) { (skb = __skb_dequeue(&neigh->arp_queue)) != NULL) {
struct neighbour *n1 = neigh; struct neighbour *n1 = neigh;
write_unlock_bh(&neigh->lock); write_unlock_bh(&neigh->lock);
/* On shaper/eql skb->dst->neighbour != neigh :( */ /* On shaper/eql skb->dst->neighbour != neigh :( */
...@@ -869,24 +892,24 @@ int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new, int override ...@@ -869,24 +892,24 @@ int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new, int override
return err; return err;
} }
struct neighbour * neigh_event_ns(struct neigh_table *tbl, struct neighbour *neigh_event_ns(struct neigh_table *tbl,
u8 *lladdr, void *saddr, u8 *lladdr, void *saddr,
struct net_device *dev) struct net_device *dev)
{ {
struct neighbour *neigh; struct neighbour *neigh = __neigh_lookup(tbl, saddr, dev,
lladdr || !dev->addr_len);
neigh = __neigh_lookup(tbl, saddr, dev, lladdr || !dev->addr_len);
if (neigh) if (neigh)
neigh_update(neigh, lladdr, NUD_STALE, 1, 1); neigh_update(neigh, lladdr, NUD_STALE, 1, 1);
return neigh; return neigh;
} }
static void neigh_hh_init(struct neighbour *n, struct dst_entry *dst, u16 protocol) static void neigh_hh_init(struct neighbour *n, struct dst_entry *dst,
u16 protocol)
{ {
struct hh_cache *hh = NULL; struct hh_cache *hh;
struct net_device *dev = dst->dev; struct net_device *dev = dst->dev;
for (hh=n->hh; hh; hh = hh->hh_next) for (hh = n->hh; hh; hh = hh->hh_next)
if (hh->hh_type == protocol) if (hh->hh_type == protocol)
break; break;
...@@ -903,7 +926,7 @@ static void neigh_hh_init(struct neighbour *n, struct dst_entry *dst, u16 protoc ...@@ -903,7 +926,7 @@ static void neigh_hh_init(struct neighbour *n, struct dst_entry *dst, u16 protoc
atomic_inc(&hh->hh_refcnt); atomic_inc(&hh->hh_refcnt);
hh->hh_next = n->hh; hh->hh_next = n->hh;
n->hh = hh; n->hh = hh;
if (n->nud_state&NUD_CONNECTED) if (n->nud_state & NUD_CONNECTED)
hh->hh_output = n->ops->hh_output; hh->hh_output = n->ops->hh_output;
else else
hh->hh_output = n->ops->output; hh->hh_output = n->ops->output;
...@@ -927,7 +950,8 @@ int neigh_compat_output(struct sk_buff *skb) ...@@ -927,7 +950,8 @@ int neigh_compat_output(struct sk_buff *skb)
__skb_pull(skb, skb->nh.raw - skb->data); __skb_pull(skb, skb->nh.raw - skb->data);
if (dev->hard_header && if (dev->hard_header &&
dev->hard_header(skb, dev, ntohs(skb->protocol), NULL, NULL, skb->len) < 0 && dev->hard_header(skb, dev, ntohs(skb->protocol), NULL, NULL,
skb->len) < 0 &&
dev->rebuild_header(skb)) dev->rebuild_header(skb))
return 0; return 0;
...@@ -940,37 +964,43 @@ int neigh_resolve_output(struct sk_buff *skb) ...@@ -940,37 +964,43 @@ int neigh_resolve_output(struct sk_buff *skb)
{ {
struct dst_entry *dst = skb->dst; struct dst_entry *dst = skb->dst;
struct neighbour *neigh; struct neighbour *neigh;
int rc = 0;
if (!dst || !(neigh = dst->neighbour)) if (!dst || !(neigh = dst->neighbour))
goto discard; goto discard;
__skb_pull(skb, skb->nh.raw - skb->data); __skb_pull(skb, skb->nh.raw - skb->data);
if (neigh_event_send(neigh, skb) == 0) { if (!neigh_event_send(neigh, skb)) {
int err; int err;
struct net_device *dev = neigh->dev; struct net_device *dev = neigh->dev;
if (dev->hard_header_cache && dst->hh == NULL) { if (dev->hard_header_cache && !dst->hh) {
write_lock_bh(&neigh->lock); write_lock_bh(&neigh->lock);
if (dst->hh == NULL) if (!dst->hh)
neigh_hh_init(neigh, dst, dst->ops->protocol); neigh_hh_init(neigh, dst, dst->ops->protocol);
err = dev->hard_header(skb, dev, ntohs(skb->protocol), neigh->ha, NULL, skb->len); err = dev->hard_header(skb, dev, ntohs(skb->protocol),
neigh->ha, NULL, skb->len);
write_unlock_bh(&neigh->lock); write_unlock_bh(&neigh->lock);
} else { } else {
read_lock_bh(&neigh->lock); read_lock_bh(&neigh->lock);
err = dev->hard_header(skb, dev, ntohs(skb->protocol), neigh->ha, NULL, skb->len); err = dev->hard_header(skb, dev, ntohs(skb->protocol),
neigh->ha, NULL, skb->len);
read_unlock_bh(&neigh->lock); read_unlock_bh(&neigh->lock);
} }
if (err >= 0) if (err >= 0)
return neigh->ops->queue_xmit(skb); rc = neigh->ops->queue_xmit(skb);
kfree_skb(skb); else
return -EINVAL; goto out_kfree_skb;
} }
return 0; out:
return rc;
discard: discard:
NEIGH_PRINTK1("neigh_resolve_output: dst=%p neigh=%p\n", dst, dst ? dst->neighbour : NULL); NEIGH_PRINTK1("neigh_resolve_output: dst=%p neigh=%p\n",
dst, dst ? dst->neighbour : NULL);
out_kfree_skb:
rc = -EINVAL;
kfree_skb(skb); kfree_skb(skb);
return -EINVAL; goto out;
} }
/* As fast as possible without hh cache */ /* As fast as possible without hh cache */
...@@ -985,12 +1015,16 @@ int neigh_connected_output(struct sk_buff *skb) ...@@ -985,12 +1015,16 @@ int neigh_connected_output(struct sk_buff *skb)
__skb_pull(skb, skb->nh.raw - skb->data); __skb_pull(skb, skb->nh.raw - skb->data);
read_lock_bh(&neigh->lock); read_lock_bh(&neigh->lock);
err = dev->hard_header(skb, dev, ntohs(skb->protocol), neigh->ha, NULL, skb->len); err = dev->hard_header(skb, dev, ntohs(skb->protocol),
neigh->ha, NULL, skb->len);
read_unlock_bh(&neigh->lock); read_unlock_bh(&neigh->lock);
if (err >= 0) if (err >= 0)
return neigh->ops->queue_xmit(skb); err = neigh->ops->queue_xmit(skb);
else {
err = -EINVAL;
kfree_skb(skb); kfree_skb(skb);
return -EINVAL; }
return err;
} }
static void neigh_proxy_process(unsigned long arg) static void neigh_proxy_process(unsigned long arg)
...@@ -1004,7 +1038,7 @@ static void neigh_proxy_process(unsigned long arg) ...@@ -1004,7 +1038,7 @@ static void neigh_proxy_process(unsigned long arg)
skb = tbl->proxy_queue.next; skb = tbl->proxy_queue.next;
while (skb != (struct sk_buff*)&tbl->proxy_queue) { while (skb != (struct sk_buff *)&tbl->proxy_queue) {
struct sk_buff *back = skb; struct sk_buff *back = skb;
long tdif = back->stamp.tv_usec - now; long tdif = back->stamp.tv_usec - now;
...@@ -1031,7 +1065,7 @@ void pneigh_enqueue(struct neigh_table *tbl, struct neigh_parms *p, ...@@ -1031,7 +1065,7 @@ void pneigh_enqueue(struct neigh_table *tbl, struct neigh_parms *p,
struct sk_buff *skb) struct sk_buff *skb)
{ {
unsigned long now = jiffies; unsigned long now = jiffies;
long sched_next = net_random()%p->proxy_delay; long sched_next = net_random() % p->proxy_delay;
if (tbl->proxy_queue.qlen > p->proxy_qlen) { if (tbl->proxy_queue.qlen > p->proxy_qlen) {
kfree_skb(skb); kfree_skb(skb);
...@@ -1055,20 +1089,20 @@ void pneigh_enqueue(struct neigh_table *tbl, struct neigh_parms *p, ...@@ -1055,20 +1089,20 @@ void pneigh_enqueue(struct neigh_table *tbl, struct neigh_parms *p,
} }
struct neigh_parms *neigh_parms_alloc(struct net_device *dev, struct neigh_table *tbl) struct neigh_parms *neigh_parms_alloc(struct net_device *dev,
struct neigh_table *tbl)
{ {
struct neigh_parms *p; struct neigh_parms *p = kmalloc(sizeof(*p), GFP_KERNEL);
p = kmalloc(sizeof(*p), GFP_KERNEL);
if (p) { if (p) {
memcpy(p, &tbl->parms, sizeof(*p)); memcpy(p, &tbl->parms, sizeof(*p));
p->tbl = tbl; p->tbl = tbl;
p->reachable_time = neigh_rand_reach_time(p->base_reachable_time); p->reachable_time =
if (dev && dev->neigh_setup) { neigh_rand_reach_time(p->base_reachable_time);
if (dev->neigh_setup(dev, p)) { if (dev && dev->neigh_setup && dev->neigh_setup(dev, p)) {
kfree(p); kfree(p);
return NULL; return NULL;
} }
}
write_lock_bh(&tbl->lock); write_lock_bh(&tbl->lock);
p->next = tbl->parms.next; p->next = tbl->parms.next;
tbl->parms.next = p; tbl->parms.next = p;
...@@ -1081,7 +1115,7 @@ void neigh_parms_release(struct neigh_table *tbl, struct neigh_parms *parms) ...@@ -1081,7 +1115,7 @@ void neigh_parms_release(struct neigh_table *tbl, struct neigh_parms *parms)
{ {
struct neigh_parms **p; struct neigh_parms **p;
if (parms == NULL || parms == &tbl->parms) if (!parms || parms == &tbl->parms)
return; return;
write_lock_bh(&tbl->lock); write_lock_bh(&tbl->lock);
for (p = &tbl->parms.next; *p; p = &(*p)->next) { for (p = &tbl->parms.next; *p; p = &(*p)->next) {
...@@ -1104,22 +1138,25 @@ void neigh_table_init(struct neigh_table *tbl) ...@@ -1104,22 +1138,25 @@ void neigh_table_init(struct neigh_table *tbl)
{ {
unsigned long now = jiffies; unsigned long now = jiffies;
tbl->parms.reachable_time = neigh_rand_reach_time(tbl->parms.base_reachable_time); tbl->parms.reachable_time =
neigh_rand_reach_time(tbl->parms.base_reachable_time);
if (tbl->kmem_cachep == NULL) if (!tbl->kmem_cachep)
tbl->kmem_cachep = kmem_cache_create(tbl->id, tbl->kmem_cachep = kmem_cache_create(tbl->id,
(tbl->entry_size+15)&~15, (tbl->entry_size +
15) & ~15,
0, SLAB_HWCACHE_ALIGN, 0, SLAB_HWCACHE_ALIGN,
NULL, NULL); NULL, NULL);
#ifdef CONFIG_SMP #ifdef CONFIG_SMP
tasklet_init(&tbl->gc_task, SMP_TIMER_NAME(neigh_periodic_timer), (unsigned long)tbl); tasklet_init(&tbl->gc_task, SMP_TIMER_NAME(neigh_periodic_timer),
(unsigned long)tbl);
#endif #endif
init_timer(&tbl->gc_timer); init_timer(&tbl->gc_timer);
tbl->lock = RW_LOCK_UNLOCKED; tbl->lock = RW_LOCK_UNLOCKED;
tbl->gc_timer.data = (unsigned long)tbl; tbl->gc_timer.data = (unsigned long)tbl;
tbl->gc_timer.function = neigh_periodic_timer; tbl->gc_timer.function = neigh_periodic_timer;
tbl->gc_timer.expires = now + tbl->gc_interval + tbl->parms.reachable_time; tbl->gc_timer.expires = now + tbl->gc_interval +
tbl->parms.reachable_time;
add_timer(&tbl->gc_timer); add_timer(&tbl->gc_timer);
init_timer(&tbl->proxy_timer); init_timer(&tbl->proxy_timer);
...@@ -1128,7 +1165,7 @@ void neigh_table_init(struct neigh_table *tbl) ...@@ -1128,7 +1165,7 @@ void neigh_table_init(struct neigh_table *tbl)
skb_queue_head_init(&tbl->proxy_queue); skb_queue_head_init(&tbl->proxy_queue);
tbl->last_flush = now; tbl->last_flush = now;
tbl->last_rand = now + tbl->parms.reachable_time*20; tbl->last_rand = now + tbl->parms.reachable_time * 20;
write_lock(&neigh_tbl_lock); write_lock(&neigh_tbl_lock);
tbl->next = neigh_tables; tbl->next = neigh_tables;
neigh_tables = tbl; neigh_tables = tbl;
...@@ -1167,15 +1204,14 @@ int neigh_delete(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg) ...@@ -1167,15 +1204,14 @@ int neigh_delete(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
struct rtattr **nda = arg; struct rtattr **nda = arg;
struct neigh_table *tbl; struct neigh_table *tbl;
struct net_device *dev = NULL; struct net_device *dev = NULL;
int err = 0; int err = -ENODEV;
if (ndm->ndm_ifindex) { if (ndm->ndm_ifindex &&
if ((dev = dev_get_by_index(ndm->ndm_ifindex)) == NULL) (dev = dev_get_by_index(ndm->ndm_ifindex)) == NULL)
return -ENODEV; goto out;
}
read_lock(&neigh_tbl_lock); read_lock(&neigh_tbl_lock);
for (tbl=neigh_tables; tbl; tbl = tbl->next) { for (tbl = neigh_tables; tbl; tbl = tbl->next) {
struct neighbour *n; struct neighbour *n;
if (tbl->family != ndm->ndm_family) if (tbl->family != ndm->ndm_family)
...@@ -1183,34 +1219,33 @@ int neigh_delete(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg) ...@@ -1183,34 +1219,33 @@ int neigh_delete(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
read_unlock(&neigh_tbl_lock); read_unlock(&neigh_tbl_lock);
err = -EINVAL; err = -EINVAL;
if (nda[NDA_DST-1] == NULL || if (!nda[NDA_DST - 1] ||
nda[NDA_DST-1]->rta_len != RTA_LENGTH(tbl->key_len)) nda[NDA_DST - 1]->rta_len != RTA_LENGTH(tbl->key_len))
goto out; goto out_dev_put;
if (ndm->ndm_flags&NTF_PROXY) { if (ndm->ndm_flags & NTF_PROXY) {
err = pneigh_delete(tbl, RTA_DATA(nda[NDA_DST-1]), dev); err = pneigh_delete(tbl,
goto out; RTA_DATA(nda[NDA_DST - 1]), dev);
goto out_dev_put;
} }
if (dev == NULL) if (!dev)
return -EINVAL; goto out;
n = neigh_lookup(tbl, RTA_DATA(nda[NDA_DST-1]), dev); n = neigh_lookup(tbl, RTA_DATA(nda[NDA_DST - 1]), dev);
if (n) { if (n) {
err = neigh_update(n, NULL, NUD_FAILED, 1, 0); err = neigh_update(n, NULL, NUD_FAILED, 1, 0);
neigh_release(n); neigh_release(n);
} }
out: goto out_dev_put;
if (dev)
dev_put(dev);
return err;
} }
read_unlock(&neigh_tbl_lock); read_unlock(&neigh_tbl_lock);
err = -EADDRNOTAVAIL;
out_dev_put:
if (dev) if (dev)
dev_put(dev); dev_put(dev);
out:
return -EADDRNOTAVAIL; return err;
} }
int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg) int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
...@@ -1219,15 +1254,14 @@ int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg) ...@@ -1219,15 +1254,14 @@ int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
struct rtattr **nda = arg; struct rtattr **nda = arg;
struct neigh_table *tbl; struct neigh_table *tbl;
struct net_device *dev = NULL; struct net_device *dev = NULL;
int err = -ENODEV;
if (ndm->ndm_ifindex) { if (ndm->ndm_ifindex &&
if ((dev = dev_get_by_index(ndm->ndm_ifindex)) == NULL) (dev = dev_get_by_index(ndm->ndm_ifindex)) == NULL)
return -ENODEV; goto out;
}
read_lock(&neigh_tbl_lock); read_lock(&neigh_tbl_lock);
for (tbl=neigh_tables; tbl; tbl = tbl->next) { for (tbl = neigh_tables; tbl; tbl = tbl->next) {
int err = 0;
int override = 1; int override = 1;
struct neighbour *n; struct neighbour *n;
...@@ -1236,53 +1270,57 @@ int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg) ...@@ -1236,53 +1270,57 @@ int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
read_unlock(&neigh_tbl_lock); read_unlock(&neigh_tbl_lock);
err = -EINVAL; err = -EINVAL;
if (nda[NDA_DST-1] == NULL || if (!nda[NDA_DST - 1] ||
nda[NDA_DST-1]->rta_len != RTA_LENGTH(tbl->key_len)) nda[NDA_DST - 1]->rta_len != RTA_LENGTH(tbl->key_len))
goto out; goto out_dev_put;
if (ndm->ndm_flags&NTF_PROXY) { if (ndm->ndm_flags & NTF_PROXY) {
err = -ENOBUFS; err = -ENOBUFS;
if (pneigh_lookup(tbl, RTA_DATA(nda[NDA_DST-1]), dev, 1)) if (pneigh_lookup(tbl,
RTA_DATA(nda[NDA_DST - 1]), dev, 1))
err = 0; err = 0;
goto out; goto out_dev_put;
} }
if (dev == NULL)
return -EINVAL;
err = -EINVAL; err = -EINVAL;
if (nda[NDA_LLADDR-1] != NULL && if (!dev)
nda[NDA_LLADDR-1]->rta_len != RTA_LENGTH(dev->addr_len))
goto out; goto out;
if (nda[NDA_LLADDR - 1] &&
nda[NDA_LLADDR - 1]->rta_len != RTA_LENGTH(dev->addr_len))
goto out_dev_put;
err = 0; err = 0;
n = neigh_lookup(tbl, RTA_DATA(nda[NDA_DST-1]), dev); n = neigh_lookup(tbl, RTA_DATA(nda[NDA_DST - 1]), dev);
if (n) { if (n) {
if (nlh->nlmsg_flags&NLM_F_EXCL) if (nlh->nlmsg_flags & NLM_F_EXCL)
err = -EEXIST; err = -EEXIST;
override = nlh->nlmsg_flags&NLM_F_REPLACE; override = nlh->nlmsg_flags & NLM_F_REPLACE;
} else if (!(nlh->nlmsg_flags&NLM_F_CREATE)) } else if (!(nlh->nlmsg_flags & NLM_F_CREATE))
err = -ENOENT; err = -ENOENT;
else { else {
n = __neigh_lookup_errno(tbl, RTA_DATA(nda[NDA_DST-1]), dev); n = __neigh_lookup_errno(tbl, RTA_DATA(nda[NDA_DST - 1]),
dev);
if (IS_ERR(n)) { if (IS_ERR(n)) {
err = PTR_ERR(n); err = PTR_ERR(n);
n = NULL; n = NULL;
} }
} }
if (err == 0) { if (!err) {
err = neigh_update(n, nda[NDA_LLADDR-1] ? RTA_DATA(nda[NDA_LLADDR-1]) : NULL, err = neigh_update(n, nda[NDA_LLADDR - 1] ?
RTA_DATA(nda[NDA_LLADDR - 1]) :
NULL,
ndm->ndm_state, ndm->ndm_state,
override, 0); override, 0);
} }
if (n) if (n)
neigh_release(n); neigh_release(n);
out: goto out_dev_put;
if (dev)
dev_put(dev);
return err;
} }
read_unlock(&neigh_tbl_lock);
read_unlock(&neigh_tbl_lock);
err = -EADDRNOTAVAIL;
out_dev_put:
if (dev) if (dev)
dev_put(dev); dev_put(dev);
return -EADDRNOTAVAIL; out:
return err;
} }
...@@ -1290,30 +1328,29 @@ static int neigh_fill_info(struct sk_buff *skb, struct neighbour *n, ...@@ -1290,30 +1328,29 @@ static int neigh_fill_info(struct sk_buff *skb, struct neighbour *n,
u32 pid, u32 seq, int event) u32 pid, u32 seq, int event)
{ {
unsigned long now = jiffies; unsigned long now = jiffies;
struct ndmsg *ndm;
struct nlmsghdr *nlh;
unsigned char *b = skb->tail; unsigned char *b = skb->tail;
struct nda_cacheinfo ci; struct nda_cacheinfo ci;
int locked = 0; int locked = 0;
struct nlmsghdr *nlh = NLMSG_PUT(skb, pid, seq, event,
sizeof(struct ndmsg));
struct ndmsg *ndm = NLMSG_DATA(nlh);
nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*ndm));
ndm = NLMSG_DATA(nlh);
ndm->ndm_family = n->ops->family; ndm->ndm_family = n->ops->family;
ndm->ndm_flags = n->flags; ndm->ndm_flags = n->flags;
ndm->ndm_type = n->type; ndm->ndm_type = n->type;
ndm->ndm_ifindex = n->dev->ifindex; ndm->ndm_ifindex = n->dev->ifindex;
RTA_PUT(skb, NDA_DST, n->tbl->key_len, n->primary_key); RTA_PUT(skb, NDA_DST, n->tbl->key_len, n->primary_key);
read_lock_bh(&n->lock); read_lock_bh(&n->lock);
locked=1; locked = 1;
ndm->ndm_state = n->nud_state; ndm->ndm_state = n->nud_state;
if (n->nud_state&NUD_VALID) if (n->nud_state & NUD_VALID)
RTA_PUT(skb, NDA_LLADDR, n->dev->addr_len, n->ha); RTA_PUT(skb, NDA_LLADDR, n->dev->addr_len, n->ha);
ci.ndm_used = now - n->used; ci.ndm_used = now - n->used;
ci.ndm_confirmed = now - n->confirmed; ci.ndm_confirmed = now - n->confirmed;
ci.ndm_updated = now - n->updated; ci.ndm_updated = now - n->updated;
ci.ndm_refcnt = atomic_read(&n->refcnt) - 1; ci.ndm_refcnt = atomic_read(&n->refcnt) - 1;
read_unlock_bh(&n->lock); read_unlock_bh(&n->lock);
locked=0; locked = 0;
RTA_PUT(skb, NDA_CACHEINFO, sizeof(ci), &ci); RTA_PUT(skb, NDA_CACHEINFO, sizeof(ci), &ci);
nlh->nlmsg_len = skb->tail - b; nlh->nlmsg_len = skb->tail - b;
return skb->len; return skb->len;
...@@ -1327,73 +1364,70 @@ static int neigh_fill_info(struct sk_buff *skb, struct neighbour *n, ...@@ -1327,73 +1364,70 @@ static int neigh_fill_info(struct sk_buff *skb, struct neighbour *n,
} }
static int neigh_dump_table(struct neigh_table *tbl, struct sk_buff *skb, struct netlink_callback *cb) static int neigh_dump_table(struct neigh_table *tbl, struct sk_buff *skb,
struct netlink_callback *cb)
{ {
struct neighbour *n; struct neighbour *n;
int h, s_h; int rc, h, s_h = cb->args[1];
int idx, s_idx; int idx, s_idx = idx = cb->args[2];
s_h = cb->args[1]; for (h = 0; h <= NEIGH_HASHMASK; h++) {
s_idx = idx = cb->args[2]; if (h < s_h)
for (h=0; h <= NEIGH_HASHMASK; h++) { continue;
if (h < s_h) continue;
if (h > s_h) if (h > s_h)
s_idx = 0; s_idx = 0;
read_lock_bh(&tbl->lock); read_lock_bh(&tbl->lock);
for (n = tbl->hash_buckets[h], idx = 0; n; for (n = tbl->hash_buckets[h], idx = 0; n; n = n->next, idx++) {
n = n->next, idx++) {
if (idx < s_idx) if (idx < s_idx)
continue; continue;
if (neigh_fill_info(skb, n, NETLINK_CB(cb->skb).pid, if (neigh_fill_info(skb, n, NETLINK_CB(cb->skb).pid,
cb->nlh->nlmsg_seq, RTM_NEWNEIGH) <= 0) { cb->nlh->nlmsg_seq,
RTM_NEWNEIGH) <= 0) {
read_unlock_bh(&tbl->lock); read_unlock_bh(&tbl->lock);
cb->args[1] = h; rc = -1;
cb->args[2] = idx; goto out;
return -1;
} }
} }
read_unlock_bh(&tbl->lock); read_unlock_bh(&tbl->lock);
} }
rc = skb->len;
out:
cb->args[1] = h; cb->args[1] = h;
cb->args[2] = idx; cb->args[2] = idx;
return skb->len; return rc;
} }
int neigh_dump_info(struct sk_buff *skb, struct netlink_callback *cb) int neigh_dump_info(struct sk_buff *skb, struct netlink_callback *cb)
{ {
int t;
int s_t;
struct neigh_table *tbl; struct neigh_table *tbl;
int family = ((struct rtgenmsg*)NLMSG_DATA(cb->nlh))->rtgen_family; int t, family, s_t;
read_lock(&neigh_tbl_lock);
family = ((struct rtgenmsg *)NLMSG_DATA(cb->nlh))->rtgen_family;
s_t = cb->args[0]; s_t = cb->args[0];
read_lock(&neigh_tbl_lock); for (tbl = neigh_tables, t = 0; tbl; tbl = tbl->next, t++) {
for (tbl=neigh_tables, t=0; tbl; tbl = tbl->next, t++) { if (t < s_t || (family && tbl->family != family))
if (t < s_t) continue;
if (family && tbl->family != family)
continue; continue;
if (t > s_t) if (t > s_t)
memset(&cb->args[1], 0, sizeof(cb->args)-sizeof(cb->args[0])); memset(&cb->args[1], 0, sizeof(cb->args) -
sizeof(cb->args[0]));
if (neigh_dump_table(tbl, skb, cb) < 0) if (neigh_dump_table(tbl, skb, cb) < 0)
break; break;
} }
read_unlock(&neigh_tbl_lock); read_unlock(&neigh_tbl_lock);
cb->args[0] = t; cb->args[0] = t;
return skb->len; return skb->len;
} }
#ifdef CONFIG_ARPD #ifdef CONFIG_ARPD
void neigh_app_ns(struct neighbour *n) void neigh_app_ns(struct neighbour *n)
{ {
struct sk_buff *skb;
struct nlmsghdr *nlh; struct nlmsghdr *nlh;
int size = NLMSG_SPACE(sizeof(struct ndmsg)+256); int size = NLMSG_SPACE(sizeof(struct ndmsg) + 256);
struct sk_buff *skb = alloc_skb(size, GFP_ATOMIC);
skb = alloc_skb(size, GFP_ATOMIC);
if (!skb) if (!skb)
return; return;
...@@ -1401,7 +1435,7 @@ void neigh_app_ns(struct neighbour *n) ...@@ -1401,7 +1435,7 @@ void neigh_app_ns(struct neighbour *n)
kfree_skb(skb); kfree_skb(skb);
return; return;
} }
nlh = (struct nlmsghdr*)skb->data; nlh = (struct nlmsghdr *)skb->data;
nlh->nlmsg_flags = NLM_F_REQUEST; nlh->nlmsg_flags = NLM_F_REQUEST;
NETLINK_CB(skb).dst_groups = RTMGRP_NEIGH; NETLINK_CB(skb).dst_groups = RTMGRP_NEIGH;
netlink_broadcast(rtnl, skb, 0, RTMGRP_NEIGH, GFP_ATOMIC); netlink_broadcast(rtnl, skb, 0, RTMGRP_NEIGH, GFP_ATOMIC);
...@@ -1409,11 +1443,10 @@ void neigh_app_ns(struct neighbour *n) ...@@ -1409,11 +1443,10 @@ void neigh_app_ns(struct neighbour *n)
static void neigh_app_notify(struct neighbour *n) static void neigh_app_notify(struct neighbour *n)
{ {
struct sk_buff *skb;
struct nlmsghdr *nlh; struct nlmsghdr *nlh;
int size = NLMSG_SPACE(sizeof(struct ndmsg)+256); int size = NLMSG_SPACE(sizeof(struct ndmsg) + 256);
struct sk_buff *skb = alloc_skb(size, GFP_ATOMIC);
skb = alloc_skb(size, GFP_ATOMIC);
if (!skb) if (!skb)
return; return;
...@@ -1421,7 +1454,7 @@ static void neigh_app_notify(struct neighbour *n) ...@@ -1421,7 +1454,7 @@ static void neigh_app_notify(struct neighbour *n)
kfree_skb(skb); kfree_skb(skb);
return; return;
} }
nlh = (struct nlmsghdr*)skb->data; nlh = (struct nlmsghdr *)skb->data;
NETLINK_CB(skb).dst_groups = RTMGRP_NEIGH; NETLINK_CB(skb).dst_groups = RTMGRP_NEIGH;
netlink_broadcast(rtnl, skb, 0, RTMGRP_NEIGH, GFP_ATOMIC); netlink_broadcast(rtnl, skb, 0, RTMGRP_NEIGH, GFP_ATOMIC);
} }
...@@ -1430,8 +1463,7 @@ static void neigh_app_notify(struct neighbour *n) ...@@ -1430,8 +1463,7 @@ static void neigh_app_notify(struct neighbour *n)
#ifdef CONFIG_SYSCTL #ifdef CONFIG_SYSCTL
struct neigh_sysctl_table struct neigh_sysctl_table {
{
struct ctl_table_header *sysctl_header; struct ctl_table_header *sysctl_header;
ctl_table neigh_vars[17]; ctl_table neigh_vars[17];
ctl_table neigh_dev[2]; ctl_table neigh_dev[2];
...@@ -1439,70 +1471,153 @@ struct neigh_sysctl_table ...@@ -1439,70 +1471,153 @@ struct neigh_sysctl_table
ctl_table neigh_proto_dir[2]; ctl_table neigh_proto_dir[2];
ctl_table neigh_root_dir[2]; ctl_table neigh_root_dir[2];
} neigh_sysctl_template = { } neigh_sysctl_template = {
NULL, neigh_vars: {
{{NET_NEIGH_MCAST_SOLICIT, "mcast_solicit", {
NULL, sizeof(int), 0644, NULL, ctl_name: NET_NEIGH_MCAST_SOLICIT,
&proc_dointvec}, procname: "mcast_solicit",
{NET_NEIGH_UCAST_SOLICIT, "ucast_solicit", maxlen: sizeof(int),
NULL, sizeof(int), 0644, NULL, mode: 0644,
&proc_dointvec}, proc_handler: &proc_dointvec,
{NET_NEIGH_APP_SOLICIT, "app_solicit", },
NULL, sizeof(int), 0644, NULL, {
&proc_dointvec}, ctl_name: NET_NEIGH_UCAST_SOLICIT,
{NET_NEIGH_RETRANS_TIME, "retrans_time", procname: "ucast_solicit",
NULL, sizeof(int), 0644, NULL, maxlen: sizeof(int),
&proc_dointvec}, mode: 0644,
{NET_NEIGH_REACHABLE_TIME, "base_reachable_time", proc_handler: &proc_dointvec,
NULL, sizeof(int), 0644, NULL, },
&proc_dointvec_jiffies}, {
{NET_NEIGH_DELAY_PROBE_TIME, "delay_first_probe_time", ctl_name: NET_NEIGH_APP_SOLICIT,
NULL, sizeof(int), 0644, NULL, procname: "app_solicit",
&proc_dointvec_jiffies}, maxlen: sizeof(int),
{NET_NEIGH_GC_STALE_TIME, "gc_stale_time", mode: 0644,
NULL, sizeof(int), 0644, NULL, proc_handler: &proc_dointvec,
&proc_dointvec_jiffies}, },
{NET_NEIGH_UNRES_QLEN, "unres_qlen", {
NULL, sizeof(int), 0644, NULL, ctl_name: NET_NEIGH_RETRANS_TIME,
&proc_dointvec}, procname: "retrans_time",
{NET_NEIGH_PROXY_QLEN, "proxy_qlen", maxlen: sizeof(int),
NULL, sizeof(int), 0644, NULL, mode: 0644,
&proc_dointvec}, proc_handler: &proc_dointvec,
{NET_NEIGH_ANYCAST_DELAY, "anycast_delay", },
NULL, sizeof(int), 0644, NULL, {
&proc_dointvec}, ctl_name: NET_NEIGH_REACHABLE_TIME,
{NET_NEIGH_PROXY_DELAY, "proxy_delay", procname: "base_reachable_time",
NULL, sizeof(int), 0644, NULL, maxlen: sizeof(int),
&proc_dointvec}, mode: 0644,
{NET_NEIGH_LOCKTIME, "locktime", proc_handler: &proc_dointvec_jiffies,
NULL, sizeof(int), 0644, NULL, },
&proc_dointvec}, {
{NET_NEIGH_GC_INTERVAL, "gc_interval", ctl_name: NET_NEIGH_DELAY_PROBE_TIME,
NULL, sizeof(int), 0644, NULL, procname: "delay_first_probe_time",
&proc_dointvec_jiffies}, maxlen: sizeof(int),
{NET_NEIGH_GC_THRESH1, "gc_thresh1", mode: 0644,
NULL, sizeof(int), 0644, NULL, proc_handler: &proc_dointvec_jiffies,
&proc_dointvec}, },
{NET_NEIGH_GC_THRESH2, "gc_thresh2", {
NULL, sizeof(int), 0644, NULL, ctl_name: NET_NEIGH_GC_STALE_TIME,
&proc_dointvec}, procname: "gc_stale_time",
{NET_NEIGH_GC_THRESH3, "gc_thresh3", maxlen: sizeof(int),
NULL, sizeof(int), 0644, NULL, mode: 0644,
&proc_dointvec}, proc_handler: &proc_dointvec_jiffies,
{0}}, },
{
{{NET_PROTO_CONF_DEFAULT, "default", NULL, 0, 0555, NULL},{0}}, ctl_name: NET_NEIGH_UNRES_QLEN,
{{0, "neigh", NULL, 0, 0555, NULL},{0}}, procname: "unres_qlen",
{{0, NULL, NULL, 0, 0555, NULL},{0}}, maxlen: sizeof(int),
{{CTL_NET, "net", NULL, 0, 0555, NULL},{0}} mode: 0644,
proc_handler: &proc_dointvec,
},
{
ctl_name: NET_NEIGH_PROXY_QLEN,
procname: "proxy_qlen",
maxlen: sizeof(int),
mode: 0644,
proc_handler: &proc_dointvec,
},
{
ctl_name: NET_NEIGH_ANYCAST_DELAY,
procname: "anycast_delay",
maxlen: sizeof(int),
mode: 0644,
proc_handler: &proc_dointvec,
},
{
ctl_name: NET_NEIGH_PROXY_DELAY,
procname: "proxy_delay",
maxlen: sizeof(int),
mode: 0644,
proc_handler: &proc_dointvec,
},
{
ctl_name: NET_NEIGH_LOCKTIME,
procname: "locktime",
maxlen: sizeof(int),
mode: 0644,
proc_handler: &proc_dointvec,
},
{
ctl_name: NET_NEIGH_GC_INTERVAL,
procname: "gc_interval",
maxlen: sizeof(int),
mode: 0644,
proc_handler: &proc_dointvec_jiffies,
},
{
ctl_name: NET_NEIGH_GC_THRESH1,
procname: "gc_thresh1",
maxlen: sizeof(int),
mode: 0644,
proc_handler: &proc_dointvec,
},
{
ctl_name: NET_NEIGH_GC_THRESH2,
procname: "gc_thresh2",
maxlen: sizeof(int),
mode: 0644,
proc_handler: &proc_dointvec,
},
{
ctl_name: NET_NEIGH_GC_THRESH3,
procname: "gc_thresh3",
maxlen: sizeof(int),
mode: 0644,
proc_handler: &proc_dointvec,
},
},
neigh_dev: {
{
ctl_name: NET_PROTO_CONF_DEFAULT,
procname: "default",
mode: 0555,
},
},
neigh_neigh_dir: {
{
procname: "neigh",
mode: 0555,
},
},
neigh_proto_dir: {
{
mode: 0555,
},
},
neigh_root_dir: {
{
ctl_name: CTL_NET,
procname: "net",
mode: 0555,
},
},
}; };
int neigh_sysctl_register(struct net_device *dev, struct neigh_parms *p, int neigh_sysctl_register(struct net_device *dev, struct neigh_parms *p,
int p_id, int pdev_id, char *p_name) int p_id, int pdev_id, char *p_name)
{ {
struct neigh_sysctl_table *t; struct neigh_sysctl_table *t = kmalloc(sizeof(*t), GFP_KERNEL);
t = kmalloc(sizeof(*t), GFP_KERNEL); if (!t)
if (t == NULL)
return -ENOBUFS; return -ENOBUFS;
memcpy(t, &neigh_sysctl_template, sizeof(*t)); memcpy(t, &neigh_sysctl_template, sizeof(*t));
t->neigh_vars[0].data = &p->mcast_probes; t->neigh_vars[0].data = &p->mcast_probes;
...@@ -1522,10 +1637,10 @@ int neigh_sysctl_register(struct net_device *dev, struct neigh_parms *p, ...@@ -1522,10 +1637,10 @@ int neigh_sysctl_register(struct net_device *dev, struct neigh_parms *p,
t->neigh_dev[0].ctl_name = dev->ifindex; t->neigh_dev[0].ctl_name = dev->ifindex;
memset(&t->neigh_vars[12], 0, sizeof(ctl_table)); memset(&t->neigh_vars[12], 0, sizeof(ctl_table));
} else { } else {
t->neigh_vars[12].data = (int*)(p+1); t->neigh_vars[12].data = (int *)(p + 1);
t->neigh_vars[13].data = (int*)(p+1) + 1; t->neigh_vars[13].data = (int *)(p + 1) + 1;
t->neigh_vars[14].data = (int*)(p+1) + 2; t->neigh_vars[14].data = (int *)(p + 1) + 2;
t->neigh_vars[15].data = (int*)(p+1) + 3; t->neigh_vars[15].data = (int *)(p + 1) + 3;
} }
t->neigh_neigh_dir[0].ctl_name = pdev_id; t->neigh_neigh_dir[0].ctl_name = pdev_id;
...@@ -1538,7 +1653,7 @@ int neigh_sysctl_register(struct net_device *dev, struct neigh_parms *p, ...@@ -1538,7 +1653,7 @@ int neigh_sysctl_register(struct net_device *dev, struct neigh_parms *p,
t->neigh_root_dir[0].child = t->neigh_proto_dir; t->neigh_root_dir[0].child = t->neigh_proto_dir;
t->sysctl_header = register_sysctl_table(t->neigh_root_dir, 0); t->sysctl_header = register_sysctl_table(t->neigh_root_dir, 0);
if (t->sysctl_header == NULL) { if (!t->sysctl_header) {
kfree(t); kfree(t);
return -ENOBUFS; return -ENOBUFS;
} }
......
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