Lockless neighbour table
Subsystems (6)
cover Lockless neighbour table
This is an updated version of the changes to make the neighbour (ARP) table lockless for normal accesses. It uses RCU and seqlock in several places. I separated the hlist conversions from RCU changes (in case we need to bisect for some reason).
--
Stephen Hemminger <[email protected]>
PATCH 1/6 net neighbor: convert top level list to RCU
Change the top level list of neighbor tables to use RCU. Minor change to BUG() if a neighbor table is registered twice.
--- include/net/neighbour.h | 2 - net/core/neighbour.c | 89 ++++++++++++++++++++++-------------------------- 2 files changed, 42 insertions(+), 49 deletions(-)
net/core/neighbour.c +41 -48
@@ -61,7 +61,7 @@ static void neigh_app_notify(struct neigstatic int pneigh_ifdown (struct neigh_table *tbl,struct net_device *dev);void neigh_changeaddr (struct neigh_table *tbl,struct net_device *dev); -static struct neigh_table *neigh_tables; +static LIST_HEAD (neigh_tables);#ifdef CONFIG_PROC_FS static struct file_operations neigh_stat_seq_fops;#endif
@@ -93,11 +93,11 @@ static struct file_operations neigh_statIt 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, +The last lock is neigh_tbl_lock. It controls update to the +set of neighbor tables. Typically used during initialization */ -static DEFINE_RWLOCK (neigh_tbl_lock); +static DEFINE_SPINLOCK (neigh_tbl_lock);static int neigh_blackhole (struct sk_buff *skb) {
@@ -1387,26 +1387,18 @@ void neigh_table_init(struct neigh_tablestruct 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 (); + +spin_lock (&neigh_tbl_lock); +list_for_each_entry (tmp, &neigh_tables, list) { +BUG_ON (tmp->family == tbl->family); } + +list_add_rcu (&tbl->list, &neigh_tables); +spin_unlock (&neigh_tbl_lock); }int neigh_table_clear (struct neigh_table *tbl) { -struct neigh_table **tp; -/* It is not clean... Fix it to unload IPv6 module safely */ del_timer_sync (&tbl->gc_timer);del_timer_sync (&tbl->proxy_timer);
@@ -1414,14 +1406,12 @@ int neigh_table_clear(struct neigh_tableneigh_ifdown (tbl,NULL );if (atomic_read (&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; -break ; - } - } -write_unlock (&neigh_tbl_lock); + +spin_lock (&neigh_tbl_lock); +list_del_rcu (&tbl->list); +spin_unlock (&neigh_tbl_lock); + +synchronize_rcu ();neigh_hash_free (tbl->hash_buckets, tbl->hash_mask +1 ); tbl->hash_buckets =NULL ;
@@ -1456,13 +1446,12 @@ int neigh_delete(struct sk_buff *skb, st } } -read_lock (&neigh_tbl_lock); -for (tbl = neigh_tables; tbl; tbl = tbl->next) { +rcu_read_lock (); +list_for_each_entry_rcu (tbl, &neigh_tables, list) {struct neighbour *neigh;if (tbl->family != ndm->ndm_family)continue ; -read_unlock (&neigh_tbl_lock);if (nla_len (dst_attr) < tbl->key_len) goto out_dev_put;
@@ -1487,12 +1476,12 @@ int neigh_delete(struct sk_buff *skb, stneigh_release (neigh); goto out_dev_put; } -read_unlock (&neigh_tbl_lock); err = -EAFNOSUPPORT ; out_dev_put:if (dev)dev_put (dev); +rcu_read_unlock (); out:return err; }
@@ -1525,15 +1514,14 @@ int neigh_add(struct sk_buff *skb, struc goto out_dev_put; } -read_lock (&neigh_tbl_lock); -for (tbl = neigh_tables; tbl; tbl = tbl->next) { +rcu_read_lock (); +list_for_each_entry_rcu (tbl, &neigh_tables, list) {int flags =NEIGH_UPDATE_F_ADMIN |NEIGH_UPDATE_F_OVERRIDE ;struct neighbour *neigh;void *dst, *lladdr;if (tbl->family != ndm->ndm_family)continue ; -read_unlock (&neigh_tbl_lock);if (nla_len (tb[NDA_DST ]) < tbl->key_len) goto out_dev_put;
@@ -1578,12 +1566,12 @@ int neigh_add(struct sk_buff *skb, struc goto out_dev_put; } -read_unlock (&neigh_tbl_lock); err = -EAFNOSUPPORT ; out_dev_put:if (dev)dev_put (dev); +rcu_read_unlock (); out:return err; }
@@ -1788,8 +1776,8 @@ int neightbl_set(struct sk_buff *skb, st } ndtmsg =nlmsg_data (nlh); -read_lock (&neigh_tbl_lock); -for (tbl = neigh_tables; tbl; tbl = tbl->next) { +rcu_read_lock (); +list_for_each_entry_rcu (tbl, &neigh_tables, list) {if (ndtmsg->ndtm_family && tbl->family != ndtmsg->ndtm_family)continue ;
@@ -1889,26 +1877,26 @@ int neightbl_set(struct sk_buff *skb, st errout_tbl_lock:write_unlock_bh (&tbl->lock); errout_locked: -read_unlock (&neigh_tbl_lock); +rcu_read_unlock (); errout:return err; }int neightbl_dump_info (struct sk_buff *skb,struct netlink_callback *cb) { -int family, tidx, nidx =0 ; +int family, tidx =0 , nidx =0 ;int tbl_skip = cb->args[0 ];int neigh_skip = cb->args[1 ];struct neigh_table *tbl; family = ((struct rtgenmsg *)nlmsg_data (cb->nlh))->rtgen_family; -read_lock (&neigh_tbl_lock); -for (tbl = neigh_tables, tidx =0 ; tbl; tbl = tbl->next, tidx++) { +rcu_read_lock (); +list_for_each_entry_rcu (tbl, &neigh_tables, list) {struct neigh_parms *p;if (tidx < tbl_skip || (family && tbl->family != family)) -continue ; + goto next_tbl; if (neightbl_fill_info(skb, tbl, NETLINK_CB(cb->skb).pid, cb->nlh->nlmsg_seq, RTM_NEWNEIGHTBL,
@@ -1928,9 +1916,11 @@ int neightbl_dump_info(struct sk_buff *s } neigh_skip =0 ; +next_tbl: + ++tidx; } out: -read_unlock (&neigh_tbl_lock); +rcu_read_unlock (); cb->args[0 ] = tidx; cb->args[1 ] = nidx;
@@ -2020,22 +2010,25 @@ out:int neigh_dump_info (struct sk_buff *skb,struct netlink_callback *cb) {struct neigh_table *tbl; -int t, family, s_t; +int t =0 , family, s_t; + -read_lock (&neigh_tbl_lock); family = ((struct rtgenmsg *)nlmsg_data (cb->nlh))->rtgen_family; s_t = cb->args[0 ]; -for (tbl = neigh_tables, t =0 ; tbl; tbl = tbl->next, t++) { +rcu_read_lock (); +list_for_each_entry_rcu (tbl, &neigh_tables, list) {if (t < s_t || (family && tbl->family != family)) -continue ; + goto next;if (t > s_t)memset (&cb->args[1 ],0 ,sizeof (cb->args) -sizeof (cb->args[0 ]));if (neigh_dump_table (tbl, skb, cb) <0 )break ; +next: + ++t; } -read_unlock (&neigh_tbl_lock); +rcu_read_unlock (); cb->args[0 ] = t;return skb->len;
include/net/neighbour.h +1 -1
@@ -136,7 +136,7 @@ struct pneigh_entrystruct neigh_table { -struct neigh_table *next; +struct list_head list;int family;int entry_size;int key_len;
--
Stephen Hemminger <[email protected]>
PATCH 2/6 neighbour: convert neighbour hash table to hlist
In reply to Stephen HemmingerChange the neighbour table hash list to hlist from list.h to allow for easier later conversion to RCU.
--- include/net/neighbour.h | 6 - net/core/neighbour.c | 160 +++++++++++++++++++++++++----------------------- 2 files changed, 88 insertions(+), 78 deletions(-)
include/net/neighbour.h +3 -3
@@ -88,10 +88,10 @@ struct neigh_statisticsstruct neighbour { -struct neighbour *next; +struct hlist_node hlist;struct neigh_table *tbl;struct neigh_parms *parms; -struct net_device *dev; +struct net_device *dev;unsigned long used;unsigned long confirmed;unsigned long updated;
@@ -161,7 +161,7 @@ struct neigh_tableunsigned long last_rand;kmem_cache_t *kmem_cachep;struct neigh_statistics *stats; -struct neighbour **hash_buckets; +struct hlist_head *hash_buckets;unsigned int hash_mask;__u32 hash_rnd;unsigned int hash_chain_gc;
net/core/neighbour.c +85 -75
380 lines not on this page · raw message
--
Stephen Hemminger <[email protected]>
PATCH 3/6 neighbour: convert pneigh hash table to hlist
In reply to Stephen HemmingerChange the pneigh_entry table to hlist from list.h to allow for easier later conversion to RCU.
--- include/net/neighbour.h | 6 ++-- net/core/neighbour.c | 58 ++++++++++++++++++++++++------------------------ 2 files changed, 33 insertions(+), 31 deletions(-)
include/net/neighbour.h +3 -3
@@ -124,8 +124,8 @@ struct neigh_opsstruct pneigh_entry { -struct pneigh_entry *next; -struct net_device *dev; +struct hlist_node hlist; +struct net_device *dev;u8 key[0 ]; };
@@ -165,7 +165,7 @@ struct neigh_tableunsigned int hash_mask;__u32 hash_rnd;unsigned int hash_chain_gc; -struct pneigh_entry **phash_buckets; +struct hlist_head *phash_buckets;#ifdef CONFIG_PROC_FS struct proc_dir_entry *pde;#endif
net/core/neighbour.c +30 -28
143 lines not on this page · raw message
--
Stephen Hemminger <[email protected]>
PATCH 4/6 net neighbour: convert to RCU
In reply to Stephen HemmingerUse RCU to allow for lock less access to the neighbour table. This should speedup the send path because no atomic operations will be needed to lookup ARP entries, etc.
--- include/net/neighbour.h | 4 - net/core/neighbour.c | 158 +++++++++++++++++++++++++----------------------- 2 files changed, 87 insertions(+), 75 deletions(-)
include/net/neighbour.h +3 -1
@@ -108,6 +108,7 @@ struct neighbourstruct sk_buff_head arp_queue;struct timer_list timer;struct neigh_ops *ops; +struct rcu_head rcu;u8 primary_key[0 ]; };
@@ -126,6 +127,7 @@ struct pneigh_entry {struct hlist_node hlist;struct net_device *dev; +struct rcu_head rcu;u8 key[0 ]; };
@@ -157,7 +159,7 @@ struct neigh_tablestruct timer_list proxy_timer;struct sk_buff_head proxy_queue;atomic_t entries; -rwlock_t lock; +spinlock_t lock;unsigned long last_rand;kmem_cache_t *kmem_cachep;struct neigh_statistics *stats;
net/core/neighbour.c +84 -74
472 lines not on this page · raw message
--
Stephen Hemminger <[email protected]>
Re: [PATCH 4/6] net neighbour: convert to RCU
Hello!
net/core/neighbour.c struct neighbour *neigh_lookup(struct ne · line 361
Quoted 11 lines
net/core/neighbour.c +2
-2@@ -346,8 +354,8 @@ struct neighbour *neigh_lookup(struct neNEIGH_CACHE_STAT_INC (tbl, lookups); -read_lock_bh (&tbl->lock); -hlist_for_each_entry (n, tmp, &tbl->hash_buckets[hash_val], hlist) { +rcu_read_lock (); +hlist_for_each_entry_rcu (n, tmp, &tbl->hash_buckets[hash_val], hlist) {if (dev == n->dev && !memcmp (n->primary_key, pkey, key_len)) {neigh_hold (n);NEIGH_CACHE_STAT_INC (tbl, hits);
Sure? Seems, you cannot grab refcnt here, the entry can be already released.
Probably, you should do atomic_inc_and_test() here and restart lookup, if it fails.
Alexey
Re: [PATCH 4/6] net neighbour: convert to RCU
Quoted 17 lines · On Tue, 29 Aug 2006 19:28:16 +0400 Alexey Kuznetsov <[email protected]>…
Hello!
net/core/neighbour.c struct neighbour *neigh_lookup(struct ne · line 361
Quoted 11 lines
net/core/neighbour.c +2
-2@@ -346,8 +354,8 @@ struct neighbour *neigh_lookup(struct neNEIGH_CACHE_STAT_INC (tbl, lookups); -read_lock_bh (&tbl->lock); -hlist_for_each_entry (n, tmp, &tbl->hash_buckets[hash_val], hlist) { +rcu_read_lock (); +hlist_for_each_entry_rcu (n, tmp, &tbl->hash_buckets[hash_val], hlist) {if (dev == n->dev && !memcmp (n->primary_key, pkey, key_len)) {neigh_hold (n);NEIGH_CACHE_STAT_INC (tbl, hits);Sure? Seems, you cannot grab refcnt here, the entry can be already released.
Quoted 4 lines
Probably, you should do
atomic_inc_and_test()here and restart lookup, if it fails.Alexey
atomic_inc_and_test is true iff result is zero, so that won't work. But the following should work:
hlist_for_each_entry_rcu(n, tmp, &tbl->hash_buckets[hash_val], hlist) {
if (dev == n->dev && !memcmp(n->primary_key, pkey, key_len)) {
if (unlikely(&atomic_inc_return(&n->refcnt) == 1)) {
neigh_release(n);
continue;
} NEIGH_CACHE_STAT_INC(tbl, hits);
I'll wrap the atomic_inc_return inside neigh_hold_return()
Signature · 1 line
Stephen Hemminger <[email protected]>
Re: [PATCH 4/6] net neighbour: convert to RCU
Quoted 6 lines · Stephen Hemminger
Quoted 4 lines
Probably, you should do
atomic_inc_and_test()here and restart lookup, if it fails.Alexey
atomic_inc_and_testis true iff result is zero, so that won't work.
Wouldn't atomic_inc_not_zero() do what you want?
Signature · 1 line
/Martin
Re: [PATCH 4/6] net neighbour: convert to RCU
Quoted 11 lines · On Tue, 29 Aug 2006 20:34:01 +0200 Martin Josefsson <[email protected]>…
Quoted 6 lines · Stephen Hemminger
Quoted 4 lines
Probably, you should do
atomic_inc_and_test()here and restart lookup, if it fails.Alexey
atomic_inc_and_testis true iff result is zero, so that won't work.Wouldn't
atomic_inc_not_zero()do what you want?
Yes, but that code looks more complex for the common case. It ends up doing a compare exchange and retrying.
Signature · 1 line
Stephen Hemminger <[email protected]>
Re: [PATCH 4/6] net neighbour: convert to RCU
In reply to Stephen HemmingerHello!
Quoted 1 line
atomic_inc_and_testis true iff result is zero, so that won't work.
I meant atomic_inc_not_zero(), as Martin noticed.
Quoted 6 lines
But the following should work:
hlist_for_each_entry_rcu(n, tmp, &tbl->hash_buckets[hash_val], hlist) { if (dev == n->dev && !memcmp(n->primary_key, pkey, key_len)) { if (unlikely(&atomic_inc_return(&n->refcnt) == 1)) { neigh_release(n);
I do not think it will work. It has exactly the same race condition.
Yes, atomic_inc_not_zero() is expensive. But it looks like it is the cheapest variant, which works correctly without more work.
Another variant would be rework use of refcnt. It can be done like rt cache:
when release of the last reference does not mean anything.
Also, probably, it makes sense to add neigh_lookup_light(), which does not take refcnt, but required to callneigh_release_light() (which is just rcu_read_unlock_bh()).
Alexey
Re: [PATCH 4/6] net neighbour: convert to RCU
Quoted 19 lines · On Wed, 30 Aug 2006 01:17:22 +0400 Alexey Kuznetsov <[email protected]>…
Hello!
Quoted 1 line
atomic_inc_and_testis true iff result is zero, so that won't work.I meant
atomic_inc_not_zero(), as Martin noticed.Quoted 6 lines
But the following should work:
hlist_for_each_entry_rcu(n, tmp, &tbl->hash_buckets[hash_val], hlist) { if (dev == n->dev && !memcmp(n->primary_key, pkey, key_len)) { if (unlikely(&atomic_inc_return(&n->refcnt) == 1)) { neigh_release(n);I do not think it will work. It has exactly the same race condition.
Yes,
atomic_inc_not_zero()is expensive. But it looks like it is the cheapest variant, which works correctly without more work.
Quoted 6 lines
Another variant would be rework use of refcnt. It can be done like rt cache:
when release of the last reference does not mean anything.Also, probably, it makes sense to add
neigh_lookup_light(), which does not take refcnt, but required to callneigh_release_light()(which is justrcu_read_unlock_bh()).
Which code paths would that make sense on?
fib_detect_death (ok)
infiniband (ok)
wireless/strip (ok) -- hey, this code is crap it has
a refcount leak already!
arp_req_get (ok)
ndisc (ok)
Perhaps killing the refcount all together, and just changing everybody to neigh_lookup_rcu(). Nobody holds a long term reference to the entries.
Quoted 2 lines
Alexey
Or using the existing dead flag?
@@ -346,17 +359,24 @@ struct neighbour *neigh_lookup(struct ne
NEIGH_CACHE_STAT_INC(tbl, lookups);
- read_lock_bh(&tbl->lock);
- hlist_for_each_entry(n, tmp, &tbl->hash_buckets[hash_val], hlist) {
+ rcu_read_lock();
+ hlist_for_each_entry_rcu(n, tmp, &tbl->hash_buckets[hash_val], hlist) {
if (dev == n->dev && !memcmp(n->primary_key, pkey, key_len)) {
neigh_hold(n);
+ /* Don't rescuitate dead entries */
+ if (unlikely(n->dead)) {
+ neigh_release(n);
+ continue;
+ }
+
NEIGH_CACHE_STAT_INC(tbl, hits);
goto found;
}
}
n = NULL;
found:
- read_unlock_bh(&tbl->lock);
+ rcu_read_unlock();
return n;
}
Re: [PATCH 4/6] net neighbour: convert to RCU
Hello!
Quoted 12 lines
Quoted 3 lines
Also, probably, it makes sense to add
neigh_lookup_light(), which does not take refcnt, but required to callneigh_release_light()(which is justrcu_read_unlock_bh()).Which code paths would that make sense on?
fib_detect_death(ok)
infiniband (ok)
wireless/strip (ok) -- hey, this code is crap it has
a refcount leak already!
arp_req_get(ok)
ndisc (ok)
Quoted 3 lines
Perhaps killing the refcount all together, and just changing everybody to
neigh_lookup_rcu(). Nobody holds a long term reference to the entries.
The only real user of refcnt is destination cache.
It uses __neigh_lookup/__neigh_lookup_errno, which could also use neigh_lookup_rcu(), then do atomic_inc_not_zero() and, if it fails fall to neigh_create().
Alexey
Re: [PATCH 4/6] net neighbour: convert to RCU
Here is the full version of neigh_lookup_rcu().
The race of interest is what happens when neigh_lookup_rcu() returns a table entry and it gets deleted at the same time. See Documentation/RCU/listRCU.txt for more info.
There are a couple different scenario's.
1) update or reader looking at dead entry. The neighbour entry is queued on the rcu callback list, and will not be invoked until all cpu's have cleared the barrier. The reader's do see stale data but it is harmless. After reader has cleared critical section, the neigh_rcu_release is run and that calls neigh_release which sees last reference and calls neigh_destroy.
neigh_lookup_rcu checks for dead entries, but this is racy and can miss. It is just an attempt to close the window slightly.
2) caller wants to create or update entry (__neigh_lookup). caller gets just deleted entry (refcount still 1), then grabs refcount (now 2). Similar to above, but the actual neigh_destroy() happens either from rcu callback or neigh_release() in caller, which ever is last.
This should not be any more racy than the existing code.
--- drivers/infiniband/core/addr.c | 7 - drivers/net/wireless/strip.c | 11 +- include/net/neighbour.h | 43 +++++--- net/core/neighbour.c | 197 ++++++++++++++++++++++------------------- net/ipv4/arp.c | 10 +- net/ipv6/ndisc.c | 7 - 6 files changed, 155 insertions(+), 120 deletions(-)
include/net/neighbour.h +28 -15
@@ -108,6 +108,7 @@ struct neighbourstruct sk_buff_head arp_queue;struct timer_list timer;struct neigh_ops *ops; +struct rcu_head rcu;u8 primary_key[0 ]; };
@@ -126,6 +127,7 @@ struct pneigh_entry {struct hlist_node hlist;struct net_device *dev; +struct rcu_head rcu;u8 key[0 ]; };
@@ -157,7 +159,7 @@ struct neigh_tablestruct timer_list proxy_timer;struct sk_buff_head proxy_queue;atomic_t entries; -rwlock_t lock; +spinlock_t lock;unsigned long last_rand;kmem_cache_t *kmem_cachep;struct neigh_statistics *stats;
@@ -181,9 +183,9 @@ struct neigh_tableextern 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, -struct net_device *dev); +extern struct neighbour *neigh_lookup_rcu (struct neigh_table *tbl, +const void *pkey, +const struct net_device *dev);extern struct neighbour *neigh_lookup_nodev (struct neigh_table *tbl,const void *pkey); extern struct neighbour * neigh_create(struct neigh_table *tbl,
@@ -313,27 +315,38 @@ static inline int neigh_event_send(strucstatic inline struct neighbour *__neigh_lookup (struct neigh_table *tbl,const void *pkey, -struct net_device *dev,int creat) +struct net_device *dev, +int creat) { -struct neighbour *n =neigh_lookup (tbl, pkey, dev); - -if (n || !creat) -return n; +struct neighbour *n; - n =neigh_create (tbl, pkey, dev); -return IS_ERR (n) ?NULL : n; +rcu_read_lock (); + n =neigh_lookup_rcu (tbl, pkey, dev); +if (n) +neigh_hold (n); +else if (creat) { + n =neigh_create (tbl, pkey, dev); +if (IS_ERR (n)) + n =NULL ; + } +rcu_read_unlock (); +return n; }static inline struct neighbour *__neigh_lookup_errno (struct neigh_table *tbl,const void *pkey,struct net_device *dev) { -struct neighbour *n =neigh_lookup (tbl, pkey, dev); +struct neighbour *n; +rcu_read_lock (); + n =neigh_lookup_rcu (tbl, pkey, dev);if (n) -return n; - -return neigh_create (tbl, pkey, dev); +neigh_hold (n); +else + n =neigh_create (tbl, pkey, dev); +rcu_read_unlock (); +return n; }struct neighbour_cb {
net/core/neighbour.c +106 -91
560 lines not on this page · raw message
drivers/infiniband/core/addr.c +4 -3
@@ -165,10 +165,11 @@ static int addr_resolve_remote(struct so goto put; } - neigh =neigh_lookup (&arp_tbl, &rt->rt_gateway, rt->idev->dev); +rcu_read_lock (); + neigh =neigh_lookup_rcu (&arp_tbl, &rt->rt_gateway, rt->idev->dev);if (!neigh) { ret = -ENODATA ; - goto put; + goto release; }if (!(neigh->nud_state &NUD_VALID )) {
@@ -183,7 +184,7 @@ static int addr_resolve_remote(struct so ret =copy_addr (addr, neigh->dev, neigh->ha); release: -neigh_release (neigh); +rcu_read_unlock (); put:ip_rt_put (rt); out:
drivers/net/wireless/strip.c +7 -4
@@ -467,17 +467,20 @@ static int arp_query(unsigned char *hadd struct net_device *dev) {struct neighbour *neighbor_entry; +int ret =0 ; - neighbor_entry =neigh_lookup (&arp_tbl, &paddr, dev); +rcu_read_lock (); + neighbor_entry =neigh_lookup_rcu (&arp_tbl, &paddr, dev); -if (neighbor_entry !=NULL ) { +if (neighbor_entry) { neighbor_entry->used = jiffies;if (neighbor_entry->nud_state &NUD_VALID ) {memcpy (haddr, neighbor_entry->ha, dev->addr_len); -return 1 ; + ret =1 ; } } -return 0 ; +rcu_read_unlock (); +return ret; } static void DumpData(char *msg, struct strip *strip_info, __u8 * ptr,
net/ipv4/arp.c +6 -4
@@ -1067,7 +1067,8 @@ static int arp_req_get(struct arpreq *r,struct neighbour *neigh;int err = -ENXIO ; - neigh =neigh_lookup (&arp_tbl, &ip, dev); +rcu_read_lock (); + neigh =neigh_lookup_rcu (&arp_tbl, &ip, dev);if (neigh) {read_lock_bh (&neigh->lock);memcpy (r->arp_ha.sa_data, neigh->ha, dev->addr_len);
@@ -1075,9 +1076,9 @@ static int arp_req_get(struct arpreq *r,read_unlock_bh (&neigh->lock); r->arp_ha.sa_family = dev->type;strlcpy (r->arp_dev, dev->name,sizeof (r->arp_dev)); -neigh_release (neigh); err =0 ; } +rcu_read_unlock ();return err; }
@@ -1118,14 +1119,15 @@ static int arp_req_delete(struct arpreqreturn -EINVAL ; } err = -ENXIO ; - neigh =neigh_lookup (&arp_tbl, &ip, dev); +rcu_read_lock (); + neigh =neigh_lookup_rcu (&arp_tbl, &ip, dev);if (neigh) {if (neigh->nud_state&~NUD_NOARP ) err =neigh_update (neigh,NULL ,NUD_FAILED ,NEIGH_UPDATE_F_OVERRIDE |NEIGH_UPDATE_F_ADMIN ); -neigh_release (neigh); } +rcu_read_unlock ();return err; }
net/ipv6/ndisc.c +4 -3
@@ -944,8 +944,9 @@ static void ndisc_recv_na(struct sk_buffin6_ifa_put (ifp);return ; } - neigh =neigh_lookup (&nd_tbl, &msg->target, dev); +rcu_read_lock (); + neigh =neigh_lookup_rcu (&nd_tbl, &msg->target, dev);if (neigh) {u8 old_flags = neigh->flags;
@@ -969,9 +970,9 @@ static void ndisc_recv_na(struct sk_buffip6_del_rt (rt); } -out: -neigh_release (neigh); } +out: +rcu_read_unlock (); }static void ndisc_recv_rs (struct sk_buff *skb)
Re: [PATCH 4/6] net neighbour: convert to RCU
Hello!
Quoted 1 line
This should not be any more racy than the existing code.
Existing code is not racy.
Critical place is interpretation of refcnt==1. Current code assumes, that when refcnt=1 and entry is in hash table, nobody can take this entry (table is locked). So, it can be unlinked from the table.
See? __neigh_lookup()/__neigh_lookup_errno() _MUST_ return alive and hashed entry. And will stay hashed until the reference is held. Or until neigh entry is forced for destruction by device going down, in this case referring dst entries will die as well.
If dst cache grabs an entry, which is purged from table because for some time it had refcnt==1, you got a valid dst entry referring to dead neighbour entry.
Alexey
Re: [PATCH 4/6] net neighbour: convert to RCU
Quoted 5 lines · On Wed, 30 Aug 2006 03:21:26 +0400 Alexey Kuznetsov <[email protected]>…
Hello!
Quoted 1 line
This should not be any more racy than the existing code.
Existing code is not racy.
Race 1: w/o RCU
Cpu 0: is in neigh_lookup
gets read_lock() finds entry ++refcount to 2 updates it
Cpu 1: is in forced_gc()
waits at write_lock()
releases read_lock() drops ref count to 1.
sees ref count is 1
deletes it
Race 1: w RCU
Cpu 0: is in __neigh_lookup
updates it
Cpu 1: is in forced_gc()
leaves refcount=1
sees ref count is 1
deletes it
Quoted 9 lines
Critical place is interpretation of refcnt==1. Current code assumes, that when refcnt=1 and entry is in hash table, nobody can take this entry (table is locked). So, it can be unlinked from the table.
See? __neigh_lookup()/__neigh_lookup_errno() _MUST_ return alive and hashed entry. And will stay hashed until the reference is held. Or until neigh entry is forced for destruction by device going down, in this case referring dst entries will die as well.
Why must it be hashed, it could always get zapped just after the update.
Quoted 3 lines
If dst cache grabs an entry, which is purged from table because for some time it had refcnt==1, you got a valid dst entry referring to dead neighbour entry.
Hmm.. Since this is a slow path, grabbing the write_lock on the neighbour entry would block the gc from zapping it.
in __neigh_lookup()
neigh_hold();
write_lock(&n->lock);
if (n->dead) {
write_unlock()
neigh_release()
goto rescan;
}
write_unlock()Signature · 1 line
Stephen Hemminger <[email protected]>
Re: [PATCH 4/6] net neighbour: convert to RCU
Hello!
Quoted 4 lines
Race 1: w/o RCU
Cpu 0: is inneigh_lookup
getsread_lock()finds entry
Quoted 8 lines
++refcount to 2 updates it
Cpu 1: is inforced_gc()
waits atwrite_lock()
releasesread_lock()drops ref count to 1.
sees ref count is 1
deletes it
Do you mean it is purged, though it is actually fresh? It is harmless race condition.
Quoted 1 line
Why must it be hashed, it could always get zapped just after the update.
Because otherwise we have to check its validity on level of dst_cache and to rebind. We do not want this. Actually dst->neighbour is supposed to be immutable.
It means that if some valid dst refers to a neighbour, it must remain hashed.
Quoted 1 line
Hmm.. Since this is a slow path,
All the neighbour lookups are in slow path. Actually, lookup of neighbour entry for use in dst cache is the most loaded path, the rest of them are pure maintanance paths.
Yes, this would work. RCU is a little spoiled, but at least global tbl->lock is replaced with per neighbour lock.
Alexey
Re: [PATCH 4/6] net neighbour: convert to RCU
In reply to Stephen HemmingerHello!
Yes, I forgot to say I take back my suggestion about atomic_inc_test_zero(). It would not work.
Seems, it is possible to add some barriers around setting n->dead and testing it in neigh_lookup_rcu(), but it would be scary and ugly. To be honest, I just do not know how to do this. :-)
PATCH 5/6 neighbour: convert lookup to sequence lock
In reply to Stephen HemmingerThe reading of neighbour table entries can be converted from a slow reader/writer lock to a fast lockless sequence number check.
--- include/net/neighbour.h | 2 net/core/neighbour.c | 117 +++++++++++++++++++++++++++++------------------- net/ipv4/arp.c | 101 +++++++++++++++++++++++++---------------- net/ipv6/ndisc.c | 16 +++--- net/ipv6/route.c | 12 ++-- net/sched/sch_teql.c | 11 +++- 6 files changed, 155 insertions(+), 104 deletions(-)
include/net/neighbour.h +1 -1
@@ -100,7 +100,7 @@ struct neighbour__u8 type;__u8 dead;atomic_t probes; -rwlock_t lock; +seqlock_t lock;unsigned char ha[ALIGN (MAX_ADDR_LEN ,sizeof (unsigned long ))];struct hh_cache *hh;atomic_t refcnt;
net/core/neighbour.c +72 -45
310 lines not on this page · raw message
net/ipv4/arp.c +61 -40
161 lines not on this page · raw message
net/ipv6/ndisc.c +8 -8
@@ -1412,15 +1412,15 @@ void ndisc_send_redirect(struct sk_buffreturn ; } -if (dev->addr_len) { -read_lock_bh (&neigh->lock); -if (neigh->nud_state &NUD_VALID ) { +if (dev->addr_len && (neigh->nud_state &NUD_VALID )) { +unsigned seq; +do { + seq =read_seqbegin (&neigh->lock);memcpy (ha_buf, neigh->ha, dev->addr_len); -read_unlock_bh (&neigh->lock); - ha = ha_buf; - len +=ndisc_opt_addr_space (dev); - }else -read_unlock_bh (&neigh->lock); + }while (read_seqretry (&neigh->lock, seq)); + + ha = ha_buf; + len +=ndisc_opt_addr_space (dev); } rd_len = min_t(unsigned int,
net/ipv6/route.c +5 -7
@@ -279,20 +279,19 @@ static void rt6_probe(struct rt6_info *r*/ if (!neigh || (neigh->nud_state &NUD_VALID ))return ; -read_lock_bh (&neigh->lock); +if (!(neigh->nud_state &NUD_VALID ) && -time_after (jiffies, neigh->updated + rt->rt6i_idev->cnf.rtr_probe_interval)) { +time_after (jiffies, + neigh->updated + rt->rt6i_idev->cnf.rtr_probe_interval)) {struct in6_addr mcaddr;struct in6_addr *target; neigh->updated = jiffies; -read_unlock_bh (&neigh->lock); target = (struct in6_addr *)&neigh->primary_key;addrconf_addr_solict_mult (target, &mcaddr);ndisc_send_ns (rt->rt6i_dev,NULL , target, &mcaddr,NULL ); - }else - read_unlock_bh(&neigh->lock); + } }#else static inline void rt6_probe (struct rt6_info *rt)
@@ -323,10 +322,9 @@ static int inline rt6_check_neigh(struct !(rt->rt6i_flags & RTF_GATEWAY)) m =1 ; else if (neigh) { -read_lock_bh (&neigh->lock); +smp_rmb ();if (neigh->nud_state &NUD_VALID ) m =2 ; -read_unlock_bh (&neigh->lock); }return m; }
net/sched/sch_teql.c +8 -3
@@ -248,9 +248,14 @@ __teql_resolve(struct sk_buff *skb, stru }if (neigh_event_send (n, skb_res) ==0 ) {int err; -read_lock (&n->lock); - err = dev->hard_header (skb, dev,ntohs (skb->protocol), n->ha,NULL , skb->len); -read_unlock (&n->lock); +unsigned seq; + +do { + seq =read_seqbegin (&n->lock); + err = dev->hard_header (skb, dev,ntohs (skb->protocol), + n->ha,NULL , skb->len); + }while (read_seqretry (&n->lock, seq)); +if (err <0 ) {neigh_release (n);return -EINVAL ;
--
Stephen Hemminger <[email protected]>
PATCH 6/6 neighbour: convert hard header cache to sequence number
In reply to Stephen HemmingerThe reading of the hard header cache in the output path can be made lockless using seqlock.
--- include/linux/netdevice.h | 3 ++- include/net/neighbour.h | 2 ++ net/core/neighbour.c | 40 +++++++++++++++++++++++++++++++++++----- net/ipv4/ip_output.c | 13 +++---------- net/ipv6/ip6_output.c | 13 +++---------- 5 files changed, 45 insertions(+), 26 deletions(-)
include/linux/netdevice.h +2 -1
@@ -193,7 +193,7 @@ struct hh_cache*/ int hh_len;/* length of header */ int (*hh_output)(struct sk_buff *skb); -rwlock_t hh_lock; +seqlock_t hh_lock;/* cached hardware header; allow for machine alignment needs. */ #define HH_DATA_MOD 16
@@ -217,6 +217,7 @@ struct hh_cache#define LL_RESERVED_SPACE_EXTRA (dev,extra) \ ((((dev)->hard_header_len+extra)&~(HH_DATA_MOD - 1)) + HH_DATA_MOD) +/* These flag bits are private to the generic network queueing * layer, they may not be explicitly referenced by any other * code.
net/core/neighbour.c +35 -5
@@ -591,9 +591,11 @@ void neigh_destroy(struct neighbour *neiwhile ((hh = neigh->hh) !=NULL ) { neigh->hh = hh->hh_next; hh->hh_next =NULL ; -write_lock_bh (&hh->hh_lock); + +write_seqlock_bh (&hh->hh_lock); hh->hh_output = neigh_blackhole; -write_unlock_bh (&hh->hh_lock); +write_sequnlock_bh (&hh->hh_lock); +if (atomic_dec_and_test (&hh->hh_refcnt))kfree (hh); }
@@ -912,9 +914,9 @@ static void neigh_update_hhs(struct neigif (update) {for (hh = neigh->hh; hh; hh = hh->hh_next) { -write_lock_bh (&hh->hh_lock); +write_seqlock_bh (&hh->hh_lock);update (hh, neigh->dev, neigh->ha); -write_unlock_bh (&hh->hh_lock); +write_sequnlock_bh (&hh->hh_lock); } } }
@@ -1105,7 +1107,7 @@ static void neigh_hh_init(struct neighbobreak ;if (!hh && (hh =kzalloc (sizeof (*hh),GFP_ATOMIC )) !=NULL ) { -rwlock_init (&hh->hh_lock); +seqlock_init (&hh->hh_lock); hh->hh_type = protocol;atomic_set (&hh->hh_refcnt,0 ); hh->hh_next =NULL ;
@@ -1128,6 +1130,33 @@ static void neigh_hh_init(struct neighbo } } + +/* +* Add header to skb from hard header cache +* Handle case where cache gets changed. +*/ +int neigh_hh_output (const struct hh_cache *hh,struct sk_buff *skb) +{ +int len, alen; +unsigned seq; +int (*output)(struct sk_buff *); + +for (;;) { + seq =read_seqbegin (&hh->hh_lock); + len = hh->hh_len; + alen =HH_DATA_ALIGN (len); + output = hh->hh_output; +memcpy (skb->data - alen, hh->hh_data, alen); +skb_push (skb, len); + +if (likely (!read_seqretry (&hh->hh_lock, seq))) +return output (skb); + +/* undo and try again */ +__skb_pull (skb, len); + } +} +/* This function can be used in contexts, where only old dev_queue_xmit worked, f.e. if you want to override normal output path (eql, shaper), but resolution is not made yet.
@@ -2767,6 +2796,7 @@ EXPORT_SYMBOL(neigh_delete);EXPORT_SYMBOL (neigh_destroy);EXPORT_SYMBOL (neigh_dump_info);EXPORT_SYMBOL (neigh_event_ns); +EXPORT_SYMBOL (neigh_hh_output);EXPORT_SYMBOL (neigh_ifdown);EXPORT_SYMBOL (neigh_lookup);EXPORT_SYMBOL (neigh_lookup_nodev);
net/ipv4/ip_output.c +3 -10
@@ -182,16 +182,9 @@ static inline int ip_finish_output2(stru skb = skb2; } -if (hh) { -int hh_alen; - -read_lock_bh (&hh->hh_lock); - hh_alen =HH_DATA_ALIGN (hh->hh_len); -memcpy (skb->data - hh_alen, hh->hh_data, hh_alen); -read_unlock_bh (&hh->hh_lock); -skb_push (skb, hh->hh_len); -return hh->hh_output (skb); - }else if (dst->neighbour) +if (hh) +return neigh_hh_output (hh, skb); +else if (dst->neighbour)return dst->neighbour->output (skb);if (net_ratelimit ())
net/ipv6/ip6_output.c +3 -10
@@ -76,16 +76,9 @@ static inline int ip6_output_finish(strustruct dst_entry *dst = skb->dst;struct hh_cache *hh = dst->hh; -if (hh) { -int hh_alen; - -read_lock_bh (&hh->hh_lock); - hh_alen =HH_DATA_ALIGN (hh->hh_len); -memcpy (skb->data - hh_alen, hh->hh_data, hh_alen); -read_unlock_bh (&hh->hh_lock); -skb_push (skb, hh->hh_len); -return hh->hh_output (skb); - }else if (dst->neighbour) +if (hh) +return neigh_hh_output (hh, skb); +else if (dst->neighbour)return dst->neighbour->output (skb);IP6_INC_STATS_BH (IPSTATS_MIB_OUTNOROUTES );
include/net/neighbour.h +2 -0
@@ -193,6 +193,8 @@ extern struct neighbour * neigh_create(s struct net_device *dev);extern void neigh_destroy (struct neighbour *neigh);extern int __neigh_event_send (struct neighbour *neigh,struct sk_buff *skb); +extern int neigh_hh_output (const struct hh_cache *hh,struct sk_buff *skb); +extern int neigh_update (struct neighbour *neigh,const u8 *lladdr,u8 new,u32 flags);extern void neigh_changeaddr (struct neigh_table *tbl,struct net_device *dev);
--
Stephen Hemminger <[email protected]>