diff options
author | James Chapman <jchapman@katalix.com> | 2024-06-20 14:22:39 +0300 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2024-06-21 13:33:33 +0300 |
commit | 2a3339f6c9636aa39f2493865e4664df1ef2baed (patch) | |
tree | 26a15f56b6328b138e00f322fe479afbaf488354 /net/l2tp | |
parent | aa5e17e1f5ecb68d3f67a069f7345dbf1a8f274f (diff) | |
download | linux-2a3339f6c9636aa39f2493865e4664df1ef2baed.tar.xz |
l2tp: store l2tpv2 sessions in per-net IDR
L2TPv2 sessions are currently kept in a per-tunnel hashlist, keyed by
16-bit session_id. When handling received L2TPv2 packets, we need to
first derive the tunnel using the 16-bit tunnel_id or sock, then
lookup the session in a per-tunnel hlist using the 16-bit session_id.
We want to avoid using sk_user_data in the datapath and double lookups
on every packet. So instead, use a per-net IDR to hold L2TPv2
sessions, keyed by a 32-bit value derived from the 16-bit tunnel_id
and session_id. This will allow the L2TPv2 UDP receive datapath to
lookup a session with a single lookup without deriving the tunnel
first.
L2TPv2 sessions are held in their own IDR to avoid potential
key collisions with L2TPv3 sessions.
Signed-off-by: James Chapman <jchapman@katalix.com>
Reviewed-by: Tom Parkin <tparkin@katalix.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/l2tp')
-rw-r--r-- | net/l2tp/l2tp_core.c | 70 | ||||
-rw-r--r-- | net/l2tp/l2tp_core.h | 1 |
2 files changed, 56 insertions, 15 deletions
diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c index d6bffdb16466..6f30b347fd46 100644 --- a/net/l2tp/l2tp_core.c +++ b/net/l2tp/l2tp_core.c @@ -107,12 +107,18 @@ struct l2tp_net { /* Lock for write access to l2tp_tunnel_idr */ spinlock_t l2tp_tunnel_idr_lock; struct idr l2tp_tunnel_idr; - /* Lock for write access to l2tp_v3_session_idr/htable */ + /* Lock for write access to l2tp_v[23]_session_idr/htable */ spinlock_t l2tp_session_idr_lock; + struct idr l2tp_v2_session_idr; struct idr l2tp_v3_session_idr; struct hlist_head l2tp_v3_session_htable[16]; }; +static inline u32 l2tp_v2_session_key(u16 tunnel_id, u16 session_id) +{ + return ((u32)tunnel_id) << 16 | session_id; +} + static inline unsigned long l2tp_v3_session_hashkey(struct sock *sk, u32 session_id) { return ((unsigned long)sk) + session_id; @@ -292,6 +298,24 @@ struct l2tp_session *l2tp_v3_session_get(const struct net *net, struct sock *sk, } EXPORT_SYMBOL_GPL(l2tp_v3_session_get); +struct l2tp_session *l2tp_v2_session_get(const struct net *net, u16 tunnel_id, u16 session_id) +{ + u32 session_key = l2tp_v2_session_key(tunnel_id, session_id); + const struct l2tp_net *pn = l2tp_pernet(net); + struct l2tp_session *session; + + rcu_read_lock_bh(); + session = idr_find(&pn->l2tp_v2_session_idr, session_key); + if (session && refcount_inc_not_zero(&session->ref_count)) { + rcu_read_unlock_bh(); + return session; + } + rcu_read_unlock_bh(); + + return NULL; +} +EXPORT_SYMBOL_GPL(l2tp_v2_session_get); + struct l2tp_session *l2tp_session_get_nth(struct l2tp_tunnel *tunnel, int nth) { int hash; @@ -477,23 +501,32 @@ int l2tp_session_register(struct l2tp_session *session, err = l2tp_session_collision_add(pn, session, session2); } spin_unlock_bh(&pn->l2tp_session_idr_lock); - if (err == -ENOSPC) - err = -EEXIST; + } else { + session_key = l2tp_v2_session_key(tunnel->tunnel_id, + session->session_id); + spin_lock_bh(&pn->l2tp_session_idr_lock); + err = idr_alloc_u32(&pn->l2tp_v2_session_idr, NULL, + &session_key, session_key, GFP_ATOMIC); + spin_unlock_bh(&pn->l2tp_session_idr_lock); } - if (err) + if (err) { + if (err == -ENOSPC) + err = -EEXIST; goto err_tlock; + } l2tp_tunnel_inc_refcount(tunnel); hlist_add_head_rcu(&session->hlist, head); spin_unlock_bh(&tunnel->hlist_lock); - if (tunnel->version == L2TP_HDR_VER_3) { - spin_lock_bh(&pn->l2tp_session_idr_lock); + spin_lock_bh(&pn->l2tp_session_idr_lock); + if (tunnel->version == L2TP_HDR_VER_3) idr_replace(&pn->l2tp_v3_session_idr, session, session_key); - spin_unlock_bh(&pn->l2tp_session_idr_lock); - } + else + idr_replace(&pn->l2tp_v2_session_idr, session, session_key); + spin_unlock_bh(&pn->l2tp_session_idr_lock); trace_register_session(session); @@ -1321,25 +1354,30 @@ static void l2tp_session_unhash(struct l2tp_session *session) /* Remove the session from core hashes */ if (tunnel) { + struct l2tp_net *pn = l2tp_pernet(tunnel->l2tp_net); + struct l2tp_session *removed = session; + /* Remove from the per-tunnel hash */ 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 IDR: remove from there, too */ + /* Remove from per-net IDR */ + spin_lock_bh(&pn->l2tp_session_idr_lock); if (tunnel->version == L2TP_HDR_VER_3) { - struct l2tp_net *pn = l2tp_pernet(tunnel->l2tp_net); - struct l2tp_session *removed = session; - - spin_lock_bh(&pn->l2tp_session_idr_lock); if (hash_hashed(&session->hlist)) l2tp_session_collision_del(pn, session); else removed = idr_remove(&pn->l2tp_v3_session_idr, session->session_id); - WARN_ON_ONCE(removed && removed != session); - spin_unlock_bh(&pn->l2tp_session_idr_lock); + } else { + u32 session_key = l2tp_v2_session_key(tunnel->tunnel_id, + session->session_id); + removed = idr_remove(&pn->l2tp_v2_session_idr, + session_key); } + WARN_ON_ONCE(removed && removed != session); + spin_unlock_bh(&pn->l2tp_session_idr_lock); synchronize_rcu(); } @@ -1802,6 +1840,7 @@ static __net_init int l2tp_init_net(struct net *net) idr_init(&pn->l2tp_tunnel_idr); spin_lock_init(&pn->l2tp_tunnel_idr_lock); + idr_init(&pn->l2tp_v2_session_idr); idr_init(&pn->l2tp_v3_session_idr); spin_lock_init(&pn->l2tp_session_idr_lock); @@ -1825,6 +1864,7 @@ static __net_exit void l2tp_exit_net(struct net *net) flush_workqueue(l2tp_wq); rcu_barrier(); + idr_destroy(&pn->l2tp_v2_session_idr); idr_destroy(&pn->l2tp_v3_session_idr); idr_destroy(&pn->l2tp_tunnel_idr); } diff --git a/net/l2tp/l2tp_core.h b/net/l2tp/l2tp_core.h index bfccc4ca2644..d80f15f5b9fc 100644 --- a/net/l2tp/l2tp_core.h +++ b/net/l2tp/l2tp_core.h @@ -231,6 +231,7 @@ struct l2tp_session *l2tp_tunnel_get_session(struct l2tp_tunnel *tunnel, u32 session_id); struct l2tp_session *l2tp_v3_session_get(const struct net *net, struct sock *sk, u32 session_id); +struct l2tp_session *l2tp_v2_session_get(const struct net *net, u16 tunnel_id, u16 session_id); struct l2tp_session *l2tp_session_get_nth(struct l2tp_tunnel *tunnel, int nth); struct l2tp_session *l2tp_session_get_by_ifname(const struct net *net, const char *ifname); |