l2tp: replace hlist with simple list for per-tunnel session list

The per-tunnel session list is no longer used by the
datapath. However, we still need a list of sessions in the tunnel for
l2tp_session_get_nth, which is used by management code. (An
alternative might be to walk each session IDR list, matching only
sessions of a given tunnel.)

Replace the per-tunnel hlist with a per-tunnel list. In functions
which walk a list of sessions of a tunnel, walk this list instead.

Signed-off-by: James Chapman <jchapman@katalix.com>
Reviewed-by: Tom Parkin <tparkin@katalix.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
James Chapman 2024-06-20 12:22:44 +01:00 committed by David S. Miller
parent 8c6245af4f
commit d18d3f0a24
3 changed files with 49 additions and 90 deletions

View File

@ -39,7 +39,6 @@
#include <linux/ip.h> #include <linux/ip.h>
#include <linux/udp.h> #include <linux/udp.h>
#include <linux/l2tp.h> #include <linux/l2tp.h>
#include <linux/hash.h>
#include <linux/sort.h> #include <linux/sort.h>
#include <linux/file.h> #include <linux/file.h>
#include <linux/nsproxy.h> #include <linux/nsproxy.h>
@ -137,18 +136,6 @@ static inline struct l2tp_net *l2tp_pernet(const struct net *net)
return net_generic(net, l2tp_net_id); return net_generic(net, l2tp_net_id);
} }
/* Session hash list.
* The session_id SHOULD be random according to RFC2661, but several
* L2TP implementations (Cisco and Microsoft) use incrementing
* session_ids. So we do a real hash on the session_id, rather than a
* simple bitmask.
*/
static inline struct hlist_head *
l2tp_session_id_hash(struct l2tp_tunnel *tunnel, u32 session_id)
{
return &tunnel->session_hlist[hash_32(session_id, L2TP_HASH_BITS)];
}
static void l2tp_tunnel_free(struct l2tp_tunnel *tunnel) static void l2tp_tunnel_free(struct l2tp_tunnel *tunnel)
{ {
trace_free_tunnel(tunnel); trace_free_tunnel(tunnel);
@ -306,21 +293,17 @@ EXPORT_SYMBOL_GPL(l2tp_session_get);
struct l2tp_session *l2tp_session_get_nth(struct l2tp_tunnel *tunnel, int nth) struct l2tp_session *l2tp_session_get_nth(struct l2tp_tunnel *tunnel, int nth)
{ {
int hash;
struct l2tp_session *session; struct l2tp_session *session;
int count = 0; int count = 0;
rcu_read_lock_bh(); rcu_read_lock_bh();
for (hash = 0; hash < L2TP_HASH_SIZE; hash++) { list_for_each_entry_rcu(session, &tunnel->session_list, list) {
hlist_for_each_entry_rcu(session, &tunnel->session_hlist[hash], hlist) { if (++count > nth) {
if (++count > nth) { l2tp_session_inc_refcount(session);
l2tp_session_inc_refcount(session); rcu_read_unlock_bh();
rcu_read_unlock_bh(); return session;
return session;
}
} }
} }
rcu_read_unlock_bh(); rcu_read_unlock_bh();
return NULL; return NULL;
@ -334,21 +317,23 @@ struct l2tp_session *l2tp_session_get_by_ifname(const struct net *net,
const char *ifname) const char *ifname)
{ {
struct l2tp_net *pn = l2tp_pernet(net); struct l2tp_net *pn = l2tp_pernet(net);
unsigned long session_id, tmp; unsigned long tunnel_id, tmp;
struct l2tp_session *session; struct l2tp_session *session;
struct l2tp_tunnel *tunnel;
rcu_read_lock_bh(); rcu_read_lock_bh();
idr_for_each_entry_ul(&pn->l2tp_v3_session_idr, session, tmp, session_id) { idr_for_each_entry_ul(&pn->l2tp_tunnel_idr, tunnel, tmp, tunnel_id) {
if (session) { if (tunnel) {
if (!strcmp(session->ifname, ifname)) { list_for_each_entry_rcu(session, &tunnel->session_list, list) {
l2tp_session_inc_refcount(session); if (!strcmp(session->ifname, ifname)) {
rcu_read_unlock_bh(); l2tp_session_inc_refcount(session);
rcu_read_unlock_bh();
return session; return session;
}
} }
} }
} }
rcu_read_unlock_bh(); rcu_read_unlock_bh();
return NULL; return NULL;
@ -452,25 +437,15 @@ int l2tp_session_register(struct l2tp_session *session,
struct l2tp_tunnel *tunnel) struct l2tp_tunnel *tunnel)
{ {
struct l2tp_net *pn = l2tp_pernet(tunnel->l2tp_net); struct l2tp_net *pn = l2tp_pernet(tunnel->l2tp_net);
struct l2tp_session *session_walk;
struct hlist_head *head;
u32 session_key; u32 session_key;
int err; int err;
head = l2tp_session_id_hash(tunnel, session->session_id); spin_lock_bh(&tunnel->list_lock);
spin_lock_bh(&tunnel->hlist_lock);
if (!tunnel->acpt_newsess) { if (!tunnel->acpt_newsess) {
err = -ENODEV; err = -ENODEV;
goto err_tlock; goto err_tlock;
} }
hlist_for_each_entry(session_walk, head, hlist)
if (session_walk->session_id == session->session_id) {
err = -EEXIST;
goto err_tlock;
}
if (tunnel->version == L2TP_HDR_VER_3) { if (tunnel->version == L2TP_HDR_VER_3) {
session_key = session->session_id; session_key = session->session_id;
spin_lock_bh(&pn->l2tp_session_idr_lock); spin_lock_bh(&pn->l2tp_session_idr_lock);
@ -506,8 +481,8 @@ int l2tp_session_register(struct l2tp_session *session,
l2tp_tunnel_inc_refcount(tunnel); l2tp_tunnel_inc_refcount(tunnel);
hlist_add_head_rcu(&session->hlist, head); list_add(&session->list, &tunnel->session_list);
spin_unlock_bh(&tunnel->hlist_lock); spin_unlock_bh(&tunnel->list_lock);
spin_lock_bh(&pn->l2tp_session_idr_lock); spin_lock_bh(&pn->l2tp_session_idr_lock);
if (tunnel->version == L2TP_HDR_VER_3) if (tunnel->version == L2TP_HDR_VER_3)
@ -521,7 +496,7 @@ int l2tp_session_register(struct l2tp_session *session,
return 0; return 0;
err_tlock: err_tlock:
spin_unlock_bh(&tunnel->hlist_lock); spin_unlock_bh(&tunnel->list_lock);
return err; return err;
} }
@ -1275,20 +1250,19 @@ end:
return; return;
} }
/* Remove an l2tp session from l2tp_core's hash lists. */ /* Remove an l2tp session from l2tp_core's lists. */
static void l2tp_session_unhash(struct l2tp_session *session) static void l2tp_session_unhash(struct l2tp_session *session)
{ {
struct l2tp_tunnel *tunnel = session->tunnel; struct l2tp_tunnel *tunnel = session->tunnel;
/* Remove the session from core hashes */
if (tunnel) { if (tunnel) {
struct l2tp_net *pn = l2tp_pernet(tunnel->l2tp_net); struct l2tp_net *pn = l2tp_pernet(tunnel->l2tp_net);
struct l2tp_session *removed = session; struct l2tp_session *removed = session;
/* Remove from the per-tunnel hash */ /* Remove from the per-tunnel list */
spin_lock_bh(&tunnel->hlist_lock); spin_lock_bh(&tunnel->list_lock);
hlist_del_init_rcu(&session->hlist); list_del_init(&session->list);
spin_unlock_bh(&tunnel->hlist_lock); spin_unlock_bh(&tunnel->list_lock);
/* Remove from per-net IDR */ /* Remove from per-net IDR */
spin_lock_bh(&pn->l2tp_session_idr_lock); spin_lock_bh(&pn->l2tp_session_idr_lock);
@ -1316,28 +1290,19 @@ static void l2tp_session_unhash(struct l2tp_session *session)
static void l2tp_tunnel_closeall(struct l2tp_tunnel *tunnel) static void l2tp_tunnel_closeall(struct l2tp_tunnel *tunnel)
{ {
struct l2tp_session *session; struct l2tp_session *session;
int hash; struct list_head __rcu *pos;
struct list_head *tmp;
spin_lock_bh(&tunnel->hlist_lock); spin_lock_bh(&tunnel->list_lock);
tunnel->acpt_newsess = false; tunnel->acpt_newsess = false;
for (hash = 0; hash < L2TP_HASH_SIZE; hash++) { list_for_each_safe(pos, tmp, &tunnel->session_list) {
again: session = list_entry(pos, struct l2tp_session, list);
hlist_for_each_entry_rcu(session, &tunnel->session_hlist[hash], hlist) { list_del_init(&session->list);
hlist_del_init_rcu(&session->hlist); spin_unlock_bh(&tunnel->list_lock);
l2tp_session_delete(session);
spin_unlock_bh(&tunnel->hlist_lock); spin_lock_bh(&tunnel->list_lock);
l2tp_session_delete(session);
spin_lock_bh(&tunnel->hlist_lock);
/* Now restart from the beginning of this hash
* chain. We always remove a session from the
* list so we are guaranteed to make forward
* progress.
*/
goto again;
}
} }
spin_unlock_bh(&tunnel->hlist_lock); spin_unlock_bh(&tunnel->list_lock);
} }
/* Tunnel socket destroy hook for UDP encapsulation */ /* Tunnel socket destroy hook for UDP encapsulation */
@ -1531,8 +1496,9 @@ int l2tp_tunnel_create(int fd, int version, u32 tunnel_id, u32 peer_tunnel_id,
tunnel->magic = L2TP_TUNNEL_MAGIC; tunnel->magic = L2TP_TUNNEL_MAGIC;
sprintf(&tunnel->name[0], "tunl %u", tunnel_id); sprintf(&tunnel->name[0], "tunl %u", tunnel_id);
spin_lock_init(&tunnel->hlist_lock); spin_lock_init(&tunnel->list_lock);
tunnel->acpt_newsess = true; tunnel->acpt_newsess = true;
INIT_LIST_HEAD(&tunnel->session_list);
tunnel->encap = encap; tunnel->encap = encap;
@ -1732,6 +1698,7 @@ struct l2tp_session *l2tp_session_create(int priv_size, struct l2tp_tunnel *tunn
session->hlist_key = l2tp_v3_session_hashkey(tunnel->sock, session->session_id); session->hlist_key = l2tp_v3_session_hashkey(tunnel->sock, session->session_id);
INIT_HLIST_NODE(&session->hlist); INIT_HLIST_NODE(&session->hlist);
INIT_LIST_HEAD(&session->clist); INIT_LIST_HEAD(&session->clist);
INIT_LIST_HEAD(&session->list);
if (cfg) { if (cfg) {
session->pwtype = cfg->pw_type; session->pwtype = cfg->pw_type;

View File

@ -19,10 +19,6 @@
#define L2TP_TUNNEL_MAGIC 0x42114DDA #define L2TP_TUNNEL_MAGIC 0x42114DDA
#define L2TP_SESSION_MAGIC 0x0C04EB7D #define L2TP_SESSION_MAGIC 0x0C04EB7D
/* Per tunnel session hash table size */
#define L2TP_HASH_BITS 4
#define L2TP_HASH_SIZE BIT(L2TP_HASH_BITS)
struct sk_buff; struct sk_buff;
struct l2tp_stats { struct l2tp_stats {
@ -65,8 +61,7 @@ struct l2tp_session_coll_list {
/* Represents a session (pseudowire) instance. /* Represents a session (pseudowire) instance.
* Tracks runtime state including cookies, dataplane packet sequencing, and IO statistics. * Tracks runtime state including cookies, dataplane packet sequencing, and IO statistics.
* Is linked into a per-tunnel session hashlist; and in the case of an L2TPv3 session into * Is linked into a per-tunnel session list and a per-net ("global") IDR tree.
* an additional per-net ("global") hashlist.
*/ */
#define L2TP_SESSION_NAME_MAX 32 #define L2TP_SESSION_NAME_MAX 32
struct l2tp_session { struct l2tp_session {
@ -90,6 +85,7 @@ struct l2tp_session {
u32 nr_oos; /* NR of last OOS packet */ u32 nr_oos; /* NR of last OOS packet */
int nr_oos_count; /* for OOS recovery */ int nr_oos_count; /* for OOS recovery */
int nr_oos_count_max; int nr_oos_count_max;
struct list_head list; /* per-tunnel list node */
refcount_t ref_count; refcount_t ref_count;
struct hlist_node hlist; /* per-net session hlist */ struct hlist_node hlist; /* per-net session hlist */
unsigned long hlist_key; /* key for session hlist */ unsigned long hlist_key; /* key for session hlist */
@ -118,7 +114,7 @@ struct l2tp_session {
/* Session close handler. /* Session close handler.
* Each pseudowire implementation may implement this callback in order to carry * Each pseudowire implementation may implement this callback in order to carry
* out pseudowire-specific shutdown actions. * out pseudowire-specific shutdown actions.
* The callback is called by core after unhashing the session and purging its * The callback is called by core after unlisting the session and purging its
* reorder queue. * reorder queue.
*/ */
void (*session_close)(struct l2tp_session *session); void (*session_close)(struct l2tp_session *session);
@ -154,7 +150,7 @@ struct l2tp_tunnel_cfg {
/* Represents a tunnel instance. /* Represents a tunnel instance.
* Tracks runtime state including IO statistics. * Tracks runtime state including IO statistics.
* Holds the tunnel socket (either passed from userspace or directly created by the kernel). * Holds the tunnel socket (either passed from userspace or directly created by the kernel).
* Maintains a hashlist of sessions belonging to the tunnel instance. * Maintains a list of sessions belonging to the tunnel instance.
* Is linked into a per-net list of tunnels. * Is linked into a per-net list of tunnels.
*/ */
#define L2TP_TUNNEL_NAME_MAX 20 #define L2TP_TUNNEL_NAME_MAX 20
@ -164,12 +160,11 @@ struct l2tp_tunnel {
unsigned long dead; unsigned long dead;
struct rcu_head rcu; struct rcu_head rcu;
spinlock_t hlist_lock; /* write-protection for session_hlist */ spinlock_t list_lock; /* write-protection for session_list */
bool acpt_newsess; /* indicates whether this tunnel accepts bool acpt_newsess; /* indicates whether this tunnel accepts
* new sessions. Protected by hlist_lock. * new sessions. Protected by list_lock.
*/ */
struct hlist_head session_hlist[L2TP_HASH_SIZE]; struct list_head session_list; /* list of sessions */
/* hashed list of sessions, hashed by id */
u32 tunnel_id; u32 tunnel_id;
u32 peer_tunnel_id; u32 peer_tunnel_id;
int version; /* 2=>L2TPv2, 3=>L2TPv3 */ int version; /* 2=>L2TPv2, 3=>L2TPv3 */

View File

@ -123,17 +123,14 @@ static void l2tp_dfs_seq_tunnel_show(struct seq_file *m, void *v)
struct l2tp_tunnel *tunnel = v; struct l2tp_tunnel *tunnel = v;
struct l2tp_session *session; struct l2tp_session *session;
int session_count = 0; int session_count = 0;
int hash;
rcu_read_lock_bh(); rcu_read_lock_bh();
for (hash = 0; hash < L2TP_HASH_SIZE; hash++) { list_for_each_entry_rcu(session, &tunnel->session_list, list) {
hlist_for_each_entry_rcu(session, &tunnel->session_hlist[hash], hlist) { /* Session ID of zero is a dummy/reserved value used by pppol2tp */
/* Session ID of zero is a dummy/reserved value used by pppol2tp */ if (session->session_id == 0)
if (session->session_id == 0) continue;
continue;
session_count++; session_count++;
}
} }
rcu_read_unlock_bh(); rcu_read_unlock_bh();