diff mbox series

[net-next,3/8] l2tp: store l2tpv2 sessions in per-net IDR

Message ID efd2d6f1479ae42ee867fae3119af1bc80ea23ed.1718877398.git.jchapman@katalix.com (mailing list archive)
State Accepted
Commit 2a3339f6c9636aa39f2493865e4664df1ef2baed
Delegated to: Netdev Maintainers
Headers show
Series l2tp: don't use the tunnel socket's sk_user_data in datapath | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for net-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 842 this patch: 842
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers warning 4 maintainers not CCed: pabeni@redhat.com tparkin@katalix.com kuba@kernel.org edumazet@google.com
netdev/build_clang success Errors and warnings before: 852 this patch: 849
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 849 this patch: 849
netdev/checkpatch warning WARNING: line length of 83 exceeds 80 columns WARNING: line length of 94 exceeds 80 columns WARNING: line length of 95 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline fail Was 0 now: 1

Commit Message

James Chapman June 20, 2024, 11:22 a.m. UTC
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>
---
 net/l2tp/l2tp_core.c | 70 ++++++++++++++++++++++++++++++++++----------
 net/l2tp/l2tp_core.h |  1 +
 2 files changed, 56 insertions(+), 15 deletions(-)
diff mbox series

Patch

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);