]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
l2tp: store l2tpv2 sessions in per-net IDR
authorJames Chapman <jchapman@katalix.com>
Thu, 20 Jun 2024 11:22:39 +0000 (12:22 +0100)
committerDavid S. Miller <davem@davemloft.net>
Fri, 21 Jun 2024 10:33:33 +0000 (11:33 +0100)
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>
net/l2tp/l2tp_core.c
net/l2tp/l2tp_core.h

index d6bffdb16466f87f98dfab0adca72dcbc1021cdd..6f30b347fd4643f980dfb2fe8e4af2468e6f64e4 100644 (file)
@@ -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);
 }
index bfccc4ca2644f2023750f38ee7ad864ec80e275a..d80f15f5b9fc7ce6e4bbf502a1cbb1381b43c638 100644 (file)
@@ -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);