Commit 2a3339f6 authored by James Chapman's avatar James Chapman Committed by David S. Miller
Browse files

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: default avatarJames Chapman <jchapman@katalix.com>
Reviewed-by: default avatarTom Parkin <tparkin@katalix.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent aa5e17e1
Loading
Loading
Loading
Loading
+55 −15
Original line number Diff line number Diff line
@@ -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);
	if (tunnel->version == L2TP_HDR_VER_3)
		idr_replace(&pn->l2tp_v3_session_idr, session, session_key);
	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 */
		if (tunnel->version == L2TP_HDR_VER_3) {
			struct l2tp_net *pn = l2tp_pernet(tunnel->l2tp_net);
			struct l2tp_session *removed = session;

		/* Remove from per-net IDR */
		spin_lock_bh(&pn->l2tp_session_idr_lock);
		if (tunnel->version == L2TP_HDR_VER_3) {
			if (hash_hashed(&session->hlist))
				l2tp_session_collision_del(pn, session);
			else
				removed = idr_remove(&pn->l2tp_v3_session_idr,
						     session->session_id);
		} 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);
}
+1 −0
Original line number Diff line number Diff line
@@ -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);