Commit e0ebcb80 authored by David S. Miller's avatar David S. Miller

Merge branch 'l2tp'

Tom Parkin says:

====================
This l2tp bugfix patchset addresses a number of issues.

The first five patches in the series prevent l2tp sessions pinning an l2tp
tunnel open.  This occurs because the l2tp tunnel is torn down in the tunnel
socket destructor, but each session holds a tunnel socket reference which
prevents tunnels with sessions being deleted.  The solution I've implemented
here involves adding a .destroy hook to udp code, as discussed previously on
netdev[1].

The subsequent seven patches address futher bugs exposed by fixing the problem
above, or exposed through stress testing the implementation above.  Patch 11
(avoid deadlock in l2tp stats update) isn't directly related to tunnel/session
lifetimes, but it does prevent deadlocks on i386 kernels running on 64 bit
hardware.

This patchset has been tested on 32 and 64 bit preempt/non-preempt kernels,
using iproute2, openl2tp, and custom-made stress test code.

[1] http://comments.gmane.org/gmane.linux.network/259169
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents f1e79e20 f6e16b29
......@@ -68,6 +68,7 @@ struct udp_sock {
* For encapsulation sockets.
*/
int (*encap_rcv)(struct sock *sk, struct sk_buff *skb);
void (*encap_destroy)(struct sock *sk);
};
static inline struct udp_sock *udp_sk(const struct sock *sk)
......
......@@ -1762,9 +1762,16 @@ int udp_rcv(struct sk_buff *skb)
void udp_destroy_sock(struct sock *sk)
{
struct udp_sock *up = udp_sk(sk);
bool slow = lock_sock_fast(sk);
udp_flush_pending_frames(sk);
unlock_sock_fast(sk, slow);
if (static_key_false(&udp_encap_needed) && up->encap_type) {
void (*encap_destroy)(struct sock *sk);
encap_destroy = ACCESS_ONCE(up->encap_destroy);
if (encap_destroy)
encap_destroy(sk);
}
}
/*
......
......@@ -1285,10 +1285,18 @@ int udpv6_sendmsg(struct kiocb *iocb, struct sock *sk,
void udpv6_destroy_sock(struct sock *sk)
{
struct udp_sock *up = udp_sk(sk);
lock_sock(sk);
udp_v6_flush_pending_frames(sk);
release_sock(sk);
if (static_key_false(&udpv6_encap_needed) && up->encap_type) {
void (*encap_destroy)(struct sock *sk);
encap_destroy = ACCESS_ONCE(up->encap_destroy);
if (encap_destroy)
encap_destroy(sk);
}
inet6_destroy_sock(sk);
}
......
This diff is collapsed.
......@@ -36,16 +36,15 @@ enum {
struct sk_buff;
struct l2tp_stats {
u64 tx_packets;
u64 tx_bytes;
u64 tx_errors;
u64 rx_packets;
u64 rx_bytes;
u64 rx_seq_discards;
u64 rx_oos_packets;
u64 rx_errors;
u64 rx_cookie_discards;
struct u64_stats_sync syncp;
atomic_long_t tx_packets;
atomic_long_t tx_bytes;
atomic_long_t tx_errors;
atomic_long_t rx_packets;
atomic_long_t rx_bytes;
atomic_long_t rx_seq_discards;
atomic_long_t rx_oos_packets;
atomic_long_t rx_errors;
atomic_long_t rx_cookie_discards;
};
struct l2tp_tunnel;
......@@ -240,11 +239,14 @@ extern struct l2tp_tunnel *l2tp_tunnel_find(struct net *net, u32 tunnel_id);
extern struct l2tp_tunnel *l2tp_tunnel_find_nth(struct net *net, int nth);
extern int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_id, u32 peer_tunnel_id, struct l2tp_tunnel_cfg *cfg, struct l2tp_tunnel **tunnelp);
extern void l2tp_tunnel_closeall(struct l2tp_tunnel *tunnel);
extern int l2tp_tunnel_delete(struct l2tp_tunnel *tunnel);
extern struct l2tp_session *l2tp_session_create(int priv_size, struct l2tp_tunnel *tunnel, u32 session_id, u32 peer_session_id, struct l2tp_session_cfg *cfg);
extern void __l2tp_session_unhash(struct l2tp_session *session);
extern int l2tp_session_delete(struct l2tp_session *session);
extern void l2tp_session_free(struct l2tp_session *session);
extern void l2tp_recv_common(struct l2tp_session *session, struct sk_buff *skb, unsigned char *ptr, unsigned char *optr, u16 hdrflags, int length, int (*payload_hook)(struct sk_buff *skb));
extern int l2tp_session_queue_purge(struct l2tp_session *session);
extern int l2tp_udp_encap_recv(struct sock *sk, struct sk_buff *skb);
extern int l2tp_xmit_skb(struct l2tp_session *session, struct sk_buff *skb, int hdr_len);
......
......@@ -146,14 +146,14 @@ static void l2tp_dfs_seq_tunnel_show(struct seq_file *m, void *v)
tunnel->sock ? atomic_read(&tunnel->sock->sk_refcnt) : 0,
atomic_read(&tunnel->ref_count));
seq_printf(m, " %08x rx %llu/%llu/%llu rx %llu/%llu/%llu\n",
seq_printf(m, " %08x rx %ld/%ld/%ld rx %ld/%ld/%ld\n",
tunnel->debug,
(unsigned long long)tunnel->stats.tx_packets,
(unsigned long long)tunnel->stats.tx_bytes,
(unsigned long long)tunnel->stats.tx_errors,
(unsigned long long)tunnel->stats.rx_packets,
(unsigned long long)tunnel->stats.rx_bytes,
(unsigned long long)tunnel->stats.rx_errors);
atomic_long_read(&tunnel->stats.tx_packets),
atomic_long_read(&tunnel->stats.tx_bytes),
atomic_long_read(&tunnel->stats.tx_errors),
atomic_long_read(&tunnel->stats.rx_packets),
atomic_long_read(&tunnel->stats.rx_bytes),
atomic_long_read(&tunnel->stats.rx_errors));
if (tunnel->show != NULL)
tunnel->show(m, tunnel);
......@@ -203,14 +203,14 @@ static void l2tp_dfs_seq_session_show(struct seq_file *m, void *v)
seq_printf(m, "\n");
}
seq_printf(m, " %hu/%hu tx %llu/%llu/%llu rx %llu/%llu/%llu\n",
seq_printf(m, " %hu/%hu tx %ld/%ld/%ld rx %ld/%ld/%ld\n",
session->nr, session->ns,
(unsigned long long)session->stats.tx_packets,
(unsigned long long)session->stats.tx_bytes,
(unsigned long long)session->stats.tx_errors,
(unsigned long long)session->stats.rx_packets,
(unsigned long long)session->stats.rx_bytes,
(unsigned long long)session->stats.rx_errors);
atomic_long_read(&session->stats.tx_packets),
atomic_long_read(&session->stats.tx_bytes),
atomic_long_read(&session->stats.tx_errors),
atomic_long_read(&session->stats.rx_packets),
atomic_long_read(&session->stats.rx_bytes),
atomic_long_read(&session->stats.rx_errors));
if (session->show != NULL)
session->show(m, session);
......
......@@ -228,10 +228,16 @@ static void l2tp_ip_close(struct sock *sk, long timeout)
static void l2tp_ip_destroy_sock(struct sock *sk)
{
struct sk_buff *skb;
struct l2tp_tunnel *tunnel = l2tp_sock_to_tunnel(sk);
while ((skb = __skb_dequeue_tail(&sk->sk_write_queue)) != NULL)
kfree_skb(skb);
if (tunnel) {
l2tp_tunnel_closeall(tunnel);
sock_put(sk);
}
sk_refcnt_debug_dec(sk);
}
......
......@@ -241,10 +241,17 @@ static void l2tp_ip6_close(struct sock *sk, long timeout)
static void l2tp_ip6_destroy_sock(struct sock *sk)
{
struct l2tp_tunnel *tunnel = l2tp_sock_to_tunnel(sk);
lock_sock(sk);
ip6_flush_pending_frames(sk);
release_sock(sk);
if (tunnel) {
l2tp_tunnel_closeall(tunnel);
sock_put(sk);
}
inet6_destroy_sock(sk);
}
......
......@@ -246,8 +246,6 @@ static int l2tp_nl_tunnel_send(struct sk_buff *skb, u32 portid, u32 seq, int fla
#if IS_ENABLED(CONFIG_IPV6)
struct ipv6_pinfo *np = NULL;
#endif
struct l2tp_stats stats;
unsigned int start;
hdr = genlmsg_put(skb, portid, seq, &l2tp_nl_family, flags,
L2TP_CMD_TUNNEL_GET);
......@@ -265,28 +263,22 @@ static int l2tp_nl_tunnel_send(struct sk_buff *skb, u32 portid, u32 seq, int fla
if (nest == NULL)
goto nla_put_failure;
do {
start = u64_stats_fetch_begin(&tunnel->stats.syncp);
stats.tx_packets = tunnel->stats.tx_packets;
stats.tx_bytes = tunnel->stats.tx_bytes;
stats.tx_errors = tunnel->stats.tx_errors;
stats.rx_packets = tunnel->stats.rx_packets;
stats.rx_bytes = tunnel->stats.rx_bytes;
stats.rx_errors = tunnel->stats.rx_errors;
stats.rx_seq_discards = tunnel->stats.rx_seq_discards;
stats.rx_oos_packets = tunnel->stats.rx_oos_packets;
} while (u64_stats_fetch_retry(&tunnel->stats.syncp, start));
if (nla_put_u64(skb, L2TP_ATTR_TX_PACKETS, stats.tx_packets) ||
nla_put_u64(skb, L2TP_ATTR_TX_BYTES, stats.tx_bytes) ||
nla_put_u64(skb, L2TP_ATTR_TX_ERRORS, stats.tx_errors) ||
nla_put_u64(skb, L2TP_ATTR_RX_PACKETS, stats.rx_packets) ||
nla_put_u64(skb, L2TP_ATTR_RX_BYTES, stats.rx_bytes) ||
if (nla_put_u64(skb, L2TP_ATTR_TX_PACKETS,
atomic_long_read(&tunnel->stats.tx_packets)) ||
nla_put_u64(skb, L2TP_ATTR_TX_BYTES,
atomic_long_read(&tunnel->stats.tx_bytes)) ||
nla_put_u64(skb, L2TP_ATTR_TX_ERRORS,
atomic_long_read(&tunnel->stats.tx_errors)) ||
nla_put_u64(skb, L2TP_ATTR_RX_PACKETS,
atomic_long_read(&tunnel->stats.rx_packets)) ||
nla_put_u64(skb, L2TP_ATTR_RX_BYTES,
atomic_long_read(&tunnel->stats.rx_bytes)) ||
nla_put_u64(skb, L2TP_ATTR_RX_SEQ_DISCARDS,
stats.rx_seq_discards) ||
atomic_long_read(&tunnel->stats.rx_seq_discards)) ||
nla_put_u64(skb, L2TP_ATTR_RX_OOS_PACKETS,
stats.rx_oos_packets) ||
nla_put_u64(skb, L2TP_ATTR_RX_ERRORS, stats.rx_errors))
atomic_long_read(&tunnel->stats.rx_oos_packets)) ||
nla_put_u64(skb, L2TP_ATTR_RX_ERRORS,
atomic_long_read(&tunnel->stats.rx_errors)))
goto nla_put_failure;
nla_nest_end(skb, nest);
......@@ -612,8 +604,6 @@ static int l2tp_nl_session_send(struct sk_buff *skb, u32 portid, u32 seq, int fl
struct nlattr *nest;
struct l2tp_tunnel *tunnel = session->tunnel;
struct sock *sk = NULL;
struct l2tp_stats stats;
unsigned int start;
sk = tunnel->sock;
......@@ -656,28 +646,22 @@ static int l2tp_nl_session_send(struct sk_buff *skb, u32 portid, u32 seq, int fl
if (nest == NULL)
goto nla_put_failure;
do {
start = u64_stats_fetch_begin(&session->stats.syncp);
stats.tx_packets = session->stats.tx_packets;
stats.tx_bytes = session->stats.tx_bytes;
stats.tx_errors = session->stats.tx_errors;
stats.rx_packets = session->stats.rx_packets;
stats.rx_bytes = session->stats.rx_bytes;
stats.rx_errors = session->stats.rx_errors;
stats.rx_seq_discards = session->stats.rx_seq_discards;
stats.rx_oos_packets = session->stats.rx_oos_packets;
} while (u64_stats_fetch_retry(&session->stats.syncp, start));
if (nla_put_u64(skb, L2TP_ATTR_TX_PACKETS, stats.tx_packets) ||
nla_put_u64(skb, L2TP_ATTR_TX_BYTES, stats.tx_bytes) ||
nla_put_u64(skb, L2TP_ATTR_TX_ERRORS, stats.tx_errors) ||
nla_put_u64(skb, L2TP_ATTR_RX_PACKETS, stats.rx_packets) ||
nla_put_u64(skb, L2TP_ATTR_RX_BYTES, stats.rx_bytes) ||
if (nla_put_u64(skb, L2TP_ATTR_TX_PACKETS,
atomic_long_read(&session->stats.tx_packets)) ||
nla_put_u64(skb, L2TP_ATTR_TX_BYTES,
atomic_long_read(&session->stats.tx_bytes)) ||
nla_put_u64(skb, L2TP_ATTR_TX_ERRORS,
atomic_long_read(&session->stats.tx_errors)) ||
nla_put_u64(skb, L2TP_ATTR_RX_PACKETS,
atomic_long_read(&session->stats.rx_packets)) ||
nla_put_u64(skb, L2TP_ATTR_RX_BYTES,
atomic_long_read(&session->stats.rx_bytes)) ||
nla_put_u64(skb, L2TP_ATTR_RX_SEQ_DISCARDS,
stats.rx_seq_discards) ||
atomic_long_read(&session->stats.rx_seq_discards)) ||
nla_put_u64(skb, L2TP_ATTR_RX_OOS_PACKETS,
stats.rx_oos_packets) ||
nla_put_u64(skb, L2TP_ATTR_RX_ERRORS, stats.rx_errors))
atomic_long_read(&session->stats.rx_oos_packets)) ||
nla_put_u64(skb, L2TP_ATTR_RX_ERRORS,
atomic_long_read(&session->stats.rx_errors)))
goto nla_put_failure;
nla_nest_end(skb, nest);
......
......@@ -97,6 +97,7 @@
#include <net/ip.h>
#include <net/udp.h>
#include <net/xfrm.h>
#include <net/inet_common.h>
#include <asm/byteorder.h>
#include <linux/atomic.h>
......@@ -259,7 +260,7 @@ static void pppol2tp_recv(struct l2tp_session *session, struct sk_buff *skb, int
session->name);
/* Not bound. Nothing we can do, so discard. */
session->stats.rx_errors++;
atomic_long_inc(&session->stats.rx_errors);
kfree_skb(skb);
}
......@@ -447,34 +448,16 @@ static void pppol2tp_session_close(struct l2tp_session *session)
{
struct pppol2tp_session *ps = l2tp_session_priv(session);
struct sock *sk = ps->sock;
struct sk_buff *skb;
struct socket *sock = sk->sk_socket;
BUG_ON(session->magic != L2TP_SESSION_MAGIC);
if (session->session_id == 0)
goto out;
if (sk != NULL) {
lock_sock(sk);
if (sk->sk_state & (PPPOX_CONNECTED | PPPOX_BOUND)) {
pppox_unbind_sock(sk);
sk->sk_state = PPPOX_DEAD;
sk->sk_state_change(sk);
if (sock) {
inet_shutdown(sock, 2);
/* Don't let the session go away before our socket does */
l2tp_session_inc_refcount(session);
}
/* Purge any queued data */
skb_queue_purge(&sk->sk_receive_queue);
skb_queue_purge(&sk->sk_write_queue);
while ((skb = skb_dequeue(&session->reorder_q))) {
kfree_skb(skb);
sock_put(sk);
}
release_sock(sk);
}
out:
return;
}
......@@ -483,19 +466,12 @@ static void pppol2tp_session_close(struct l2tp_session *session)
*/
static void pppol2tp_session_destruct(struct sock *sk)
{
struct l2tp_session *session;
if (sk->sk_user_data != NULL) {
session = sk->sk_user_data;
if (session == NULL)
goto out;
struct l2tp_session *session = sk->sk_user_data;
if (session) {
sk->sk_user_data = NULL;
BUG_ON(session->magic != L2TP_SESSION_MAGIC);
l2tp_session_dec_refcount(session);
}
out:
return;
}
......@@ -525,16 +501,13 @@ static int pppol2tp_release(struct socket *sock)
session = pppol2tp_sock_to_session(sk);
/* Purge any queued data */
skb_queue_purge(&sk->sk_receive_queue);
skb_queue_purge(&sk->sk_write_queue);
if (session != NULL) {
struct sk_buff *skb;
while ((skb = skb_dequeue(&session->reorder_q))) {
kfree_skb(skb);
sock_put(sk);
}
__l2tp_session_unhash(session);
l2tp_session_queue_purge(session);
sock_put(sk);
}
skb_queue_purge(&sk->sk_receive_queue);
skb_queue_purge(&sk->sk_write_queue);
release_sock(sk);
......@@ -880,18 +853,6 @@ static int pppol2tp_session_create(struct net *net, u32 tunnel_id, u32 session_i
return error;
}
/* Called when deleting sessions via the netlink interface.
*/
static int pppol2tp_session_delete(struct l2tp_session *session)
{
struct pppol2tp_session *ps = l2tp_session_priv(session);
if (ps->sock == NULL)
l2tp_session_dec_refcount(session);
return 0;
}
#endif /* CONFIG_L2TP_V3 */
/* getname() support.
......@@ -1025,14 +986,14 @@ static int pppol2tp_getname(struct socket *sock, struct sockaddr *uaddr,
static void pppol2tp_copy_stats(struct pppol2tp_ioc_stats *dest,
struct l2tp_stats *stats)
{
dest->tx_packets = stats->tx_packets;
dest->tx_bytes = stats->tx_bytes;
dest->tx_errors = stats->tx_errors;
dest->rx_packets = stats->rx_packets;
dest->rx_bytes = stats->rx_bytes;
dest->rx_seq_discards = stats->rx_seq_discards;
dest->rx_oos_packets = stats->rx_oos_packets;
dest->rx_errors = stats->rx_errors;
dest->tx_packets = atomic_long_read(&stats->tx_packets);
dest->tx_bytes = atomic_long_read(&stats->tx_bytes);
dest->tx_errors = atomic_long_read(&stats->tx_errors);
dest->rx_packets = atomic_long_read(&stats->rx_packets);
dest->rx_bytes = atomic_long_read(&stats->rx_bytes);
dest->rx_seq_discards = atomic_long_read(&stats->rx_seq_discards);
dest->rx_oos_packets = atomic_long_read(&stats->rx_oos_packets);
dest->rx_errors = atomic_long_read(&stats->rx_errors);
}
/* Session ioctl helper.
......@@ -1666,14 +1627,14 @@ static void pppol2tp_seq_tunnel_show(struct seq_file *m, void *v)
tunnel->name,
(tunnel == tunnel->sock->sk_user_data) ? 'Y' : 'N',
atomic_read(&tunnel->ref_count) - 1);
seq_printf(m, " %08x %llu/%llu/%llu %llu/%llu/%llu\n",
seq_printf(m, " %08x %ld/%ld/%ld %ld/%ld/%ld\n",
tunnel->debug,
(unsigned long long)tunnel->stats.tx_packets,
(unsigned long long)tunnel->stats.tx_bytes,
(unsigned long long)tunnel->stats.tx_errors,
(unsigned long long)tunnel->stats.rx_packets,
(unsigned long long)tunnel->stats.rx_bytes,
(unsigned long long)tunnel->stats.rx_errors);
atomic_long_read(&tunnel->stats.tx_packets),
atomic_long_read(&tunnel->stats.tx_bytes),
atomic_long_read(&tunnel->stats.tx_errors),
atomic_long_read(&tunnel->stats.rx_packets),
atomic_long_read(&tunnel->stats.rx_bytes),
atomic_long_read(&tunnel->stats.rx_errors));
}
static void pppol2tp_seq_session_show(struct seq_file *m, void *v)
......@@ -1708,14 +1669,14 @@ static void pppol2tp_seq_session_show(struct seq_file *m, void *v)
session->lns_mode ? "LNS" : "LAC",
session->debug,
jiffies_to_msecs(session->reorder_timeout));
seq_printf(m, " %hu/%hu %llu/%llu/%llu %llu/%llu/%llu\n",
seq_printf(m, " %hu/%hu %ld/%ld/%ld %ld/%ld/%ld\n",
session->nr, session->ns,
(unsigned long long)session->stats.tx_packets,
(unsigned long long)session->stats.tx_bytes,
(unsigned long long)session->stats.tx_errors,
(unsigned long long)session->stats.rx_packets,
(unsigned long long)session->stats.rx_bytes,
(unsigned long long)session->stats.rx_errors);
atomic_long_read(&session->stats.tx_packets),
atomic_long_read(&session->stats.tx_bytes),
atomic_long_read(&session->stats.tx_errors),
atomic_long_read(&session->stats.rx_packets),
atomic_long_read(&session->stats.rx_bytes),
atomic_long_read(&session->stats.rx_errors));
if (po)
seq_printf(m, " interface %s\n", ppp_dev_name(&po->chan));
......@@ -1839,7 +1800,7 @@ static const struct pppox_proto pppol2tp_proto = {
static const struct l2tp_nl_cmd_ops pppol2tp_nl_cmd_ops = {
.session_create = pppol2tp_session_create,
.session_delete = pppol2tp_session_delete,
.session_delete = l2tp_session_delete,
};
#endif /* CONFIG_L2TP_V3 */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment