mirror of
https://github.com/torvalds/linux.git
synced 2024-12-01 08:31:37 +00:00
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:
parent
8c6245af4f
commit
d18d3f0a24
@ -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;
|
||||||
|
@ -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 */
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user