diff options
author | Eric Dumazet <edumazet@google.com> | 2012-06-04 23:00:18 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2012-06-06 13:45:15 -0400 |
commit | 55432d2b543a4b6dfae54f5c432a566877a85d90 (patch) | |
tree | f830eb5150e38be149fcfce74f68716f5bf2641a | |
parent | dd03cff23d694cfb0fdae80cb618e7ced05ea696 (diff) |
inetpeer: fix a race in inetpeer_gc_worker()
commit 5faa5df1fa2024 (inetpeer: Invalidate the inetpeer tree along with
the routing cache) added a race :
Before freeing an inetpeer, we must respect a RCU grace period, and make
sure no user will attempt to increase refcnt.
inetpeer_invalidate_tree() waits for a RCU grace period before inserting
inetpeer tree into gc_list and waking the worker. At that time, no
concurrent lookup can find a inetpeer in this tree.
Signed-off-by: Eric Dumazet <edumazet@google.com>
Cc: Steffen Klassert <steffen.klassert@secunet.com>
Acked-by: Steffen Klassert <steffen.klassert@secunet.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | include/net/inetpeer.h | 5 | ||||
-rw-r--r-- | net/ipv4/inetpeer.c | 16 |
2 files changed, 16 insertions, 5 deletions
diff --git a/include/net/inetpeer.h b/include/net/inetpeer.h index b94765e38e80..2040bff945d4 100644 --- a/include/net/inetpeer.h +++ b/include/net/inetpeer.h | |||
@@ -40,7 +40,10 @@ struct inet_peer { | |||
40 | u32 pmtu_orig; | 40 | u32 pmtu_orig; |
41 | u32 pmtu_learned; | 41 | u32 pmtu_learned; |
42 | struct inetpeer_addr_base redirect_learned; | 42 | struct inetpeer_addr_base redirect_learned; |
43 | struct list_head gc_list; | 43 | union { |
44 | struct list_head gc_list; | ||
45 | struct rcu_head gc_rcu; | ||
46 | }; | ||
44 | /* | 47 | /* |
45 | * Once inet_peer is queued for deletion (refcnt == -1), following fields | 48 | * Once inet_peer is queued for deletion (refcnt == -1), following fields |
46 | * are not available: rid, ip_id_count, tcp_ts, tcp_ts_stamp | 49 | * are not available: rid, ip_id_count, tcp_ts, tcp_ts_stamp |
diff --git a/net/ipv4/inetpeer.c b/net/ipv4/inetpeer.c index d4d61b694fab..dfba343b2509 100644 --- a/net/ipv4/inetpeer.c +++ b/net/ipv4/inetpeer.c | |||
@@ -560,6 +560,17 @@ bool inet_peer_xrlim_allow(struct inet_peer *peer, int timeout) | |||
560 | } | 560 | } |
561 | EXPORT_SYMBOL(inet_peer_xrlim_allow); | 561 | EXPORT_SYMBOL(inet_peer_xrlim_allow); |
562 | 562 | ||
563 | static void inetpeer_inval_rcu(struct rcu_head *head) | ||
564 | { | ||
565 | struct inet_peer *p = container_of(head, struct inet_peer, gc_rcu); | ||
566 | |||
567 | spin_lock_bh(&gc_lock); | ||
568 | list_add_tail(&p->gc_list, &gc_list); | ||
569 | spin_unlock_bh(&gc_lock); | ||
570 | |||
571 | schedule_delayed_work(&gc_work, gc_delay); | ||
572 | } | ||
573 | |||
563 | void inetpeer_invalidate_tree(int family) | 574 | void inetpeer_invalidate_tree(int family) |
564 | { | 575 | { |
565 | struct inet_peer *old, *new, *prev; | 576 | struct inet_peer *old, *new, *prev; |
@@ -576,10 +587,7 @@ void inetpeer_invalidate_tree(int family) | |||
576 | prev = cmpxchg(&base->root, old, new); | 587 | prev = cmpxchg(&base->root, old, new); |
577 | if (prev == old) { | 588 | if (prev == old) { |
578 | base->total = 0; | 589 | base->total = 0; |
579 | spin_lock(&gc_lock); | 590 | call_rcu(&prev->gc_rcu, inetpeer_inval_rcu); |
580 | list_add_tail(&prev->gc_list, &gc_list); | ||
581 | spin_unlock(&gc_lock); | ||
582 | schedule_delayed_work(&gc_work, gc_delay); | ||
583 | } | 591 | } |
584 | 592 | ||
585 | out: | 593 | out: |