Skip to content

Commit

Permalink
net/l2tp: convert tunnel rwlock_t to rcu
Browse files Browse the repository at this point in the history
Previously commit e02d494 ("l2tp: Convert rwlock to RCU") converted
most, but not all, rwlock instances in the l2tp subsystem to RCU.

The remaining rwlock protects the per-tunnel hashlist of sessions which
is used for session lookups in the UDP-encap data path.

Convert the remaining rwlock to rcu to improve performance of UDP-encap
tunnels.

Note that the tunnel and session, which both live on RCU-protected
lists, use slightly different approaches to incrementing their refcounts
in the various getter functions.

The tunnel has to use refcount_inc_not_zero because the tunnel shutdown
process involves dropping the refcount to zero prior to synchronizing
RCU readers (via. kfree_rcu).

By contrast, the session shutdown removes the session from the list(s)
it is on, synchronizes with readers, and then decrements the session
refcount.  Since the getter functions increment the session refcount
with the RCU read lock held we prevent getters seeing a zero session
refcount, and therefore don't need to use refcount_inc_not_zero.

Signed-off-by: Tom Parkin <tparkin@katalix.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Tom Parkin authored and David S. Miller committed Nov 29, 2021
1 parent 275f37e commit 07b8ca3
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 36 deletions.
52 changes: 25 additions & 27 deletions net/l2tp/l2tp_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -250,15 +250,15 @@ struct l2tp_session *l2tp_tunnel_get_session(struct l2tp_tunnel *tunnel,

session_list = l2tp_session_id_hash(tunnel, session_id);

read_lock_bh(&tunnel->hlist_lock);
hlist_for_each_entry(session, session_list, hlist)
rcu_read_lock_bh();
hlist_for_each_entry_rcu(session, session_list, hlist)
if (session->session_id == session_id) {
l2tp_session_inc_refcount(session);
read_unlock_bh(&tunnel->hlist_lock);
rcu_read_unlock_bh();

return session;
}
read_unlock_bh(&tunnel->hlist_lock);
rcu_read_unlock_bh();

return NULL;
}
Expand Down Expand Up @@ -291,18 +291,18 @@ struct l2tp_session *l2tp_session_get_nth(struct l2tp_tunnel *tunnel, int nth)
struct l2tp_session *session;
int count = 0;

read_lock_bh(&tunnel->hlist_lock);
rcu_read_lock_bh();
for (hash = 0; hash < L2TP_HASH_SIZE; hash++) {
hlist_for_each_entry(session, &tunnel->session_hlist[hash], hlist) {
hlist_for_each_entry_rcu(session, &tunnel->session_hlist[hash], hlist) {
if (++count > nth) {
l2tp_session_inc_refcount(session);
read_unlock_bh(&tunnel->hlist_lock);
rcu_read_unlock_bh();
return session;
}
}
}

read_unlock_bh(&tunnel->hlist_lock);
rcu_read_unlock_bh();

return NULL;
}
Expand Down Expand Up @@ -347,7 +347,7 @@ int l2tp_session_register(struct l2tp_session *session,

head = l2tp_session_id_hash(tunnel, session->session_id);

write_lock_bh(&tunnel->hlist_lock);
spin_lock_bh(&tunnel->hlist_lock);
if (!tunnel->acpt_newsess) {
err = -ENODEV;
goto err_tlock;
Expand Down Expand Up @@ -384,8 +384,8 @@ int l2tp_session_register(struct l2tp_session *session,
l2tp_tunnel_inc_refcount(tunnel);
}

hlist_add_head(&session->hlist, head);
write_unlock_bh(&tunnel->hlist_lock);
hlist_add_head_rcu(&session->hlist, head);
spin_unlock_bh(&tunnel->hlist_lock);

trace_register_session(session);

Expand All @@ -394,7 +394,7 @@ int l2tp_session_register(struct l2tp_session *session,
err_tlock_pnlock:
spin_unlock_bh(&pn->l2tp_session_hlist_lock);
err_tlock:
write_unlock_bh(&tunnel->hlist_lock);
spin_unlock_bh(&tunnel->hlist_lock);

return err;
}
Expand Down Expand Up @@ -1170,9 +1170,9 @@ static void l2tp_session_unhash(struct l2tp_session *session)
/* Remove the session from core hashes */
if (tunnel) {
/* Remove from the per-tunnel hash */
write_lock_bh(&tunnel->hlist_lock);
hlist_del_init(&session->hlist);
write_unlock_bh(&tunnel->hlist_lock);
spin_lock_bh(&tunnel->hlist_lock);
hlist_del_init_rcu(&session->hlist);
spin_unlock_bh(&tunnel->hlist_lock);

/* For L2TPv3 we have a per-net hash: remove from there, too */
if (tunnel->version != L2TP_HDR_VER_2) {
Expand All @@ -1181,31 +1181,29 @@ static void l2tp_session_unhash(struct l2tp_session *session)
spin_lock_bh(&pn->l2tp_session_hlist_lock);
hlist_del_init_rcu(&session->global_hlist);
spin_unlock_bh(&pn->l2tp_session_hlist_lock);
synchronize_rcu();
}

synchronize_rcu();
}
}

/* When the tunnel is closed, all the attached sessions need to go too.
*/
static void l2tp_tunnel_closeall(struct l2tp_tunnel *tunnel)
{
int hash;
struct hlist_node *walk;
struct hlist_node *tmp;
struct l2tp_session *session;
int hash;

write_lock_bh(&tunnel->hlist_lock);
spin_lock_bh(&tunnel->hlist_lock);
tunnel->acpt_newsess = false;
for (hash = 0; hash < L2TP_HASH_SIZE; hash++) {
again:
hlist_for_each_safe(walk, tmp, &tunnel->session_hlist[hash]) {
session = hlist_entry(walk, struct l2tp_session, hlist);
hlist_del_init(&session->hlist);
hlist_for_each_entry_rcu(session, &tunnel->session_hlist[hash], hlist) {
hlist_del_init_rcu(&session->hlist);

write_unlock_bh(&tunnel->hlist_lock);
spin_unlock_bh(&tunnel->hlist_lock);
l2tp_session_delete(session);
write_lock_bh(&tunnel->hlist_lock);
spin_lock_bh(&tunnel->hlist_lock);

/* Now restart from the beginning of this hash
* chain. We always remove a session from the
Expand All @@ -1215,7 +1213,7 @@ static void l2tp_tunnel_closeall(struct l2tp_tunnel *tunnel)
goto again;
}
}
write_unlock_bh(&tunnel->hlist_lock);
spin_unlock_bh(&tunnel->hlist_lock);
}

/* Tunnel socket destroy hook for UDP encapsulation */
Expand Down Expand Up @@ -1408,7 +1406,7 @@ int l2tp_tunnel_create(int fd, int version, u32 tunnel_id, u32 peer_tunnel_id,

tunnel->magic = L2TP_TUNNEL_MAGIC;
sprintf(&tunnel->name[0], "tunl %u", tunnel_id);
rwlock_init(&tunnel->hlist_lock);
spin_lock_init(&tunnel->hlist_lock);
tunnel->acpt_newsess = true;

tunnel->encap = encap;
Expand Down
2 changes: 1 addition & 1 deletion net/l2tp/l2tp_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ struct l2tp_tunnel {
unsigned long dead;

struct rcu_head rcu;
rwlock_t hlist_lock; /* protect session_hlist */
spinlock_t hlist_lock; /* write-protection for session_hlist */
bool acpt_newsess; /* indicates whether this tunnel accepts
* new sessions. Protected by hlist_lock.
*/
Expand Down
13 changes: 5 additions & 8 deletions net/l2tp/l2tp_debugfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -120,24 +120,21 @@ static void l2tp_dfs_seq_stop(struct seq_file *p, void *v)
static void l2tp_dfs_seq_tunnel_show(struct seq_file *m, void *v)
{
struct l2tp_tunnel *tunnel = v;
struct l2tp_session *session;
int session_count = 0;
int hash;
struct hlist_node *walk;
struct hlist_node *tmp;

read_lock_bh(&tunnel->hlist_lock);
rcu_read_lock_bh();
for (hash = 0; hash < L2TP_HASH_SIZE; hash++) {
hlist_for_each_safe(walk, tmp, &tunnel->session_hlist[hash]) {
struct l2tp_session *session;

session = hlist_entry(walk, struct l2tp_session, hlist);
hlist_for_each_entry_rcu(session, &tunnel->session_hlist[hash], hlist) {
/* Session ID of zero is a dummy/reserved value used by pppol2tp */
if (session->session_id == 0)
continue;

session_count++;
}
}
read_unlock_bh(&tunnel->hlist_lock);
rcu_read_unlock_bh();

seq_printf(m, "\nTUNNEL %u peer %u", tunnel->tunnel_id, tunnel->peer_tunnel_id);
if (tunnel->sock) {
Expand Down

0 comments on commit 07b8ca3

Please sign in to comment.