lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <1340798457-28270-1-git-send-email-tparkin@katalix.com>
Date:	Wed, 27 Jun 2012 13:00:57 +0100
From:	Tom Parkin <tparkin@...alix.com>
To:	netdev@...r.kernel.org
Cc:	David.Laight@...LAB.COM, Tom Parkin <tparkin@...alix.com>,
	James Chapman <jchapman@...alix.com>
Subject: [PATCH v2] l2tp: use per-cpu variables for u64_stats updates

This patch fixes a race condition in l2tp when updating tunnel and
session statistics.  Previously it was possible for multiple threads
to concurrently call u64_stats_update*(), which lead to statistics
readers blocking forever.

This race was discovered on an AMD64 SMP machine running a 32bit
kernel.  Running "ip l2tp" while sending data over an Ethernet
pseudowire resulted in an occasional soft lockup in
u64_stats_fetch_begin() called from l2tp_nl_session_send().

For safe lockless update of l2tp stats, data is now stored in per-cpu
variables.  These per-cpu datasets are then summed at read time via.
an extra helper function l2tp_stats_copy() which has been added to
l2tp_core.c.

Signed-off-by: Tom Parkin <tparkin@...alix.com>
Signed-off-by: James Chapman <jchapman@...alix.com>
---
 net/l2tp/l2tp_core.c    |  286 ++++++++++++++++++++++++++++-------------------
 net/l2tp/l2tp_core.h    |   44 ++++++--
 net/l2tp/l2tp_debugfs.c |   42 ++++---
 net/l2tp/l2tp_netlink.c |   64 ++++-------
 net/l2tp/l2tp_ppp.c     |   75 ++++++++-----
 5 files changed, 301 insertions(+), 210 deletions(-)

diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c
index 32b2155..ab2ffc0 100644
--- a/net/l2tp/l2tp_core.c
+++ b/net/l2tp/l2tp_core.c
@@ -320,6 +320,43 @@ struct l2tp_tunnel *l2tp_tunnel_find_nth(struct net *net, int nth)
 }
 EXPORT_SYMBOL_GPL(l2tp_tunnel_find_nth);
 
+/*
+ * Sum tunnel/session statistics across all CPUs
+ */
+int l2tp_stats_copy(struct l2tp_stats *cpustats, struct l2tp_stats *dest)
+{
+	int i;
+	unsigned int start;
+
+	if (!cpustats || !dest)
+		return 1;
+
+	memset(dest, 0, sizeof(struct l2tp_stats));
+
+	for_each_possible_cpu(i) {
+		struct l2tp_stats *stats = per_cpu_ptr(cpustats, i);
+
+		do {
+			start = u64_stats_fetch_begin(&stats->tx.syncp);
+			dest->tx.packets += stats->tx.packets;
+			dest->tx.bytes += stats->tx.bytes;
+			dest->tx.errors += stats->tx.errors;
+		} while (u64_stats_fetch_retry(&stats->tx.syncp, start));
+
+		do {
+			start = u64_stats_fetch_begin(&stats->rx.syncp);
+			dest->rx.packets += stats->rx.packets;
+			dest->rx.bytes += stats->rx.bytes;
+			dest->rx.errors += stats->rx.errors;
+			dest->rx.seq_discards += stats->rx.seq_discards;
+			dest->rx.oos_packets += stats->rx.oos_packets;
+		} while (u64_stats_fetch_retry(&stats->rx.syncp, start));
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(l2tp_stats_copy);
+
 /*****************************************************************************
  * Receive data handling
  *****************************************************************************/
@@ -335,7 +372,6 @@ static void l2tp_recv_queue_skb(struct l2tp_session *session, struct sk_buff *sk
 	struct l2tp_stats *sstats;
 
 	spin_lock_bh(&session->reorder_q.lock);
-	sstats = &session->stats;
 	skb_queue_walk_safe(&session->reorder_q, skbp, tmp) {
 		if (L2TP_SKB_CB(skbp)->ns > ns) {
 			__skb_queue_before(&session->reorder_q, skbp, skb);
@@ -343,9 +379,10 @@ static void l2tp_recv_queue_skb(struct l2tp_session *session, struct sk_buff *sk
 				 "%s: pkt %hu, inserted before %hu, reorder_q len=%d\n",
 				 session->name, ns, L2TP_SKB_CB(skbp)->ns,
 				 skb_queue_len(&session->reorder_q));
-			u64_stats_update_begin(&sstats->syncp);
-			sstats->rx_oos_packets++;
-			u64_stats_update_end(&sstats->syncp);
+			sstats = this_cpu_ptr(session->cpustats);
+			u64_stats_update_begin(&sstats->rx.syncp);
+			sstats->rx.oos_packets++;
+			u64_stats_update_end(&sstats->rx.syncp);
 			goto out;
 		}
 	}
@@ -369,16 +406,17 @@ static void l2tp_recv_dequeue_skb(struct l2tp_session *session, struct sk_buff *
 	 */
 	skb_orphan(skb);
 
-	tstats = &tunnel->stats;
-	u64_stats_update_begin(&tstats->syncp);
-	sstats = &session->stats;
-	u64_stats_update_begin(&sstats->syncp);
-	tstats->rx_packets++;
-	tstats->rx_bytes += length;
-	sstats->rx_packets++;
-	sstats->rx_bytes += length;
-	u64_stats_update_end(&tstats->syncp);
-	u64_stats_update_end(&sstats->syncp);
+	tstats = this_cpu_ptr(tunnel->cpustats);
+	u64_stats_update_begin(&tstats->rx.syncp);
+	tstats->rx.packets++;
+	tstats->rx.bytes += length;
+	u64_stats_update_end(&tstats->rx.syncp);
+
+	sstats = this_cpu_ptr(session->cpustats);
+	u64_stats_update_begin(&sstats->rx.syncp);
+	sstats->rx.packets++;
+	sstats->rx.bytes += length;
+	u64_stats_update_end(&sstats->rx.syncp);
 
 	if (L2TP_SKB_CB(skb)->has_seq) {
 		/* Bump our Nr */
@@ -417,13 +455,13 @@ static void l2tp_recv_dequeue(struct l2tp_session *session)
 	 */
 start:
 	spin_lock_bh(&session->reorder_q.lock);
-	sstats = &session->stats;
 	skb_queue_walk_safe(&session->reorder_q, skb, tmp) {
 		if (time_after(jiffies, L2TP_SKB_CB(skb)->expires)) {
-			u64_stats_update_begin(&sstats->syncp);
-			sstats->rx_seq_discards++;
-			sstats->rx_errors++;
-			u64_stats_update_end(&sstats->syncp);
+			sstats = this_cpu_ptr(session->cpustats);
+			u64_stats_update_begin(&sstats->rx.syncp);
+			sstats->rx.seq_discards++;
+			sstats->rx.errors++;
+			u64_stats_update_end(&sstats->rx.syncp);
 			l2tp_dbg(session, L2TP_MSG_SEQ,
 				 "%s: oos pkt %u len %d discarded (too old), waiting for %u, reorder_q_len=%d\n",
 				 session->name, L2TP_SKB_CB(skb)->ns,
@@ -582,7 +620,7 @@ void l2tp_recv_common(struct l2tp_session *session, struct sk_buff *skb,
 	struct l2tp_tunnel *tunnel = session->tunnel;
 	int offset;
 	u32 ns, nr;
-	struct l2tp_stats *sstats = &session->stats;
+	struct l2tp_stats *sstats;
 
 	/* The ref count is increased since we now hold a pointer to
 	 * the session. Take care to decrement the refcnt when exiting
@@ -599,9 +637,10 @@ void l2tp_recv_common(struct l2tp_session *session, struct sk_buff *skb,
 				  "%s: cookie mismatch (%u/%u). Discarding.\n",
 				  tunnel->name, tunnel->tunnel_id,
 				  session->session_id);
-			u64_stats_update_begin(&sstats->syncp);
-			sstats->rx_cookie_discards++;
-			u64_stats_update_end(&sstats->syncp);
+			sstats = this_cpu_ptr(session->cpustats);
+			u64_stats_update_begin(&sstats->rx.syncp);
+			sstats->rx.cookie_discards++;
+			u64_stats_update_end(&sstats->rx.syncp);
 			goto discard;
 		}
 		ptr += session->peer_cookie_len;
@@ -670,9 +709,10 @@ void l2tp_recv_common(struct l2tp_session *session, struct sk_buff *skb,
 			l2tp_warn(session, L2TP_MSG_SEQ,
 				  "%s: recv data has no seq numbers when required. Discarding.\n",
 				  session->name);
-			u64_stats_update_begin(&sstats->syncp);
-			sstats->rx_seq_discards++;
-			u64_stats_update_end(&sstats->syncp);
+			sstats = this_cpu_ptr(session->cpustats);
+			u64_stats_update_begin(&sstats->rx.syncp);
+			sstats->rx.seq_discards++;
+			u64_stats_update_end(&sstats->rx.syncp);
 			goto discard;
 		}
 
@@ -691,9 +731,10 @@ void l2tp_recv_common(struct l2tp_session *session, struct sk_buff *skb,
 			l2tp_warn(session, L2TP_MSG_SEQ,
 				  "%s: recv data has no seq numbers when required. Discarding.\n",
 				  session->name);
-			u64_stats_update_begin(&sstats->syncp);
-			sstats->rx_seq_discards++;
-			u64_stats_update_end(&sstats->syncp);
+			sstats = this_cpu_ptr(session->cpustats);
+			u64_stats_update_begin(&sstats->rx.syncp);
+			sstats->rx.seq_discards++;
+			u64_stats_update_end(&sstats->rx.syncp);
 			goto discard;
 		}
 	}
@@ -747,9 +788,10 @@ void l2tp_recv_common(struct l2tp_session *session, struct sk_buff *skb,
 			 * packets
 			 */
 			if (L2TP_SKB_CB(skb)->ns != session->nr) {
-				u64_stats_update_begin(&sstats->syncp);
-				sstats->rx_seq_discards++;
-				u64_stats_update_end(&sstats->syncp);
+				sstats = this_cpu_ptr(session->cpustats);
+				u64_stats_update_begin(&sstats->rx.syncp);
+				sstats->rx.seq_discards++;
+				u64_stats_update_end(&sstats->rx.syncp);
 				l2tp_dbg(session, L2TP_MSG_SEQ,
 					 "%s: oos pkt %u len %d discarded, waiting for %u, reorder_q_len=%d\n",
 					 session->name, L2TP_SKB_CB(skb)->ns,
@@ -775,9 +817,10 @@ void l2tp_recv_common(struct l2tp_session *session, struct sk_buff *skb,
 	return;
 
 discard:
-	u64_stats_update_begin(&sstats->syncp);
-	sstats->rx_errors++;
-	u64_stats_update_end(&sstats->syncp);
+	sstats = this_cpu_ptr(session->cpustats);
+	u64_stats_update_begin(&sstats->rx.syncp);
+	sstats->rx.errors++;
+	u64_stats_update_end(&sstats->rx.syncp);
 	kfree_skb(skb);
 
 	if (session->deref)
@@ -891,10 +934,10 @@ static int l2tp_udp_recv_core(struct l2tp_tunnel *tunnel, struct sk_buff *skb,
 discard_bad_csum:
 	LIMIT_NETDEBUG("%s: UDP: bad checksum\n", tunnel->name);
 	UDP_INC_STATS_USER(tunnel->l2tp_net, UDP_MIB_INERRORS, 0);
-	tstats = &tunnel->stats;
-	u64_stats_update_begin(&tstats->syncp);
-	tstats->rx_errors++;
-	u64_stats_update_end(&tstats->syncp);
+	tstats = this_cpu_ptr(tunnel->cpustats);
+	u64_stats_update_begin(&tstats->rx.syncp);
+	tstats->rx.errors++;
+	u64_stats_update_end(&tstats->rx.syncp);
 	kfree_skb(skb);
 
 	return 0;
@@ -1050,21 +1093,21 @@ static int l2tp_xmit_core(struct l2tp_session *session, struct sk_buff *skb,
 		error = ip_queue_xmit(skb, fl);
 
 	/* Update stats */
-	tstats = &tunnel->stats;
-	u64_stats_update_begin(&tstats->syncp);
-	sstats = &session->stats;
-	u64_stats_update_begin(&sstats->syncp);
+	tstats = this_cpu_ptr(tunnel->cpustats);
+	sstats = this_cpu_ptr(session->cpustats);
+	u64_stats_update_begin(&tstats->tx.syncp);
+	u64_stats_update_begin(&sstats->tx.syncp);
 	if (error >= 0) {
-		tstats->tx_packets++;
-		tstats->tx_bytes += len;
-		sstats->tx_packets++;
-		sstats->tx_bytes += len;
+		tstats->tx.packets++;
+		tstats->tx.bytes += len;
+		sstats->tx.packets++;
+		sstats->tx.bytes += len;
 	} else {
-		tstats->tx_errors++;
-		sstats->tx_errors++;
+		tstats->tx.errors++;
+		sstats->tx.errors++;
 	}
-	u64_stats_update_end(&tstats->syncp);
-	u64_stats_update_end(&sstats->syncp);
+	u64_stats_update_end(&tstats->tx.syncp);
+	u64_stats_update_end(&sstats->tx.syncp);
 
 	return 0;
 }
@@ -1256,6 +1299,8 @@ static void l2tp_tunnel_destruct(struct sock *sk)
 	sk->sk_destruct = tunnel->old_sk_destruct;
 	sk->sk_user_data = NULL;
 
+	free_percpu(tunnel->cpustats);
+
 	/* Call the original destructor */
 	if (sk->sk_destruct)
 		(*sk->sk_destruct)(sk);
@@ -1567,6 +1612,13 @@ int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_id, u32
 		goto err;
 	}
 
+	tunnel->cpustats = alloc_percpu(struct l2tp_stats);
+	if (tunnel->cpustats == NULL) {
+		kfree(tunnel);
+		err = -ENOMEM;
+		goto err;
+	}
+
 	tunnel->version = version;
 	tunnel->tunnel_id = tunnel_id;
 	tunnel->peer_tunnel_id = peer_tunnel_id;
@@ -1692,6 +1744,8 @@ void l2tp_session_free(struct l2tp_session *session)
 
 		sock_put(tunnel->sock);
 
+		free_percpu(session->cpustats);
+
 		/* This will delete the tunnel context if this
 		 * is the last session on the tunnel.
 		 */
@@ -1742,80 +1796,88 @@ struct l2tp_session *l2tp_session_create(int priv_size, struct l2tp_tunnel *tunn
 	struct l2tp_session *session;
 
 	session = kzalloc(sizeof(struct l2tp_session) + priv_size, GFP_KERNEL);
-	if (session != NULL) {
-		session->magic = L2TP_SESSION_MAGIC;
-		session->tunnel = tunnel;
+	if (session == NULL)
+		goto err;
 
-		session->session_id = session_id;
-		session->peer_session_id = peer_session_id;
-		session->nr = 0;
+	session->cpustats = alloc_percpu(struct l2tp_stats);
+	if (session->cpustats == NULL) {
+		kfree(session);
+		goto err;
+	}
 
-		sprintf(&session->name[0], "sess %u/%u",
-			tunnel->tunnel_id, session->session_id);
+	session->magic = L2TP_SESSION_MAGIC;
+	session->tunnel = tunnel;
 
-		skb_queue_head_init(&session->reorder_q);
-
-		INIT_HLIST_NODE(&session->hlist);
-		INIT_HLIST_NODE(&session->global_hlist);
-
-		/* Inherit debug options from tunnel */
-		session->debug = tunnel->debug;
-
-		if (cfg) {
-			session->pwtype = cfg->pw_type;
-			session->debug = cfg->debug;
-			session->mtu = cfg->mtu;
-			session->mru = cfg->mru;
-			session->send_seq = cfg->send_seq;
-			session->recv_seq = cfg->recv_seq;
-			session->lns_mode = cfg->lns_mode;
-			session->reorder_timeout = cfg->reorder_timeout;
-			session->offset = cfg->offset;
-			session->l2specific_type = cfg->l2specific_type;
-			session->l2specific_len = cfg->l2specific_len;
-			session->cookie_len = cfg->cookie_len;
-			memcpy(&session->cookie[0], &cfg->cookie[0], cfg->cookie_len);
-			session->peer_cookie_len = cfg->peer_cookie_len;
-			memcpy(&session->peer_cookie[0], &cfg->peer_cookie[0], cfg->peer_cookie_len);
-		}
+	session->session_id = session_id;
+	session->peer_session_id = peer_session_id;
+	session->nr = 0;
 
-		if (tunnel->version == L2TP_HDR_VER_2)
-			session->build_header = l2tp_build_l2tpv2_header;
-		else
-			session->build_header = l2tp_build_l2tpv3_header;
+	sprintf(&session->name[0], "sess %u/%u",
+			tunnel->tunnel_id, session->session_id);
 
-		l2tp_session_set_header_len(session, tunnel->version);
+	skb_queue_head_init(&session->reorder_q);
+
+	INIT_HLIST_NODE(&session->hlist);
+	INIT_HLIST_NODE(&session->global_hlist);
+
+	/* Inherit debug options from tunnel */
+	session->debug = tunnel->debug;
+
+	if (cfg) {
+		session->pwtype = cfg->pw_type;
+		session->debug = cfg->debug;
+		session->mtu = cfg->mtu;
+		session->mru = cfg->mru;
+		session->send_seq = cfg->send_seq;
+		session->recv_seq = cfg->recv_seq;
+		session->lns_mode = cfg->lns_mode;
+		session->reorder_timeout = cfg->reorder_timeout;
+		session->offset = cfg->offset;
+		session->l2specific_type = cfg->l2specific_type;
+		session->l2specific_len = cfg->l2specific_len;
+		session->cookie_len = cfg->cookie_len;
+		memcpy(&session->cookie[0], &cfg->cookie[0], cfg->cookie_len);
+		session->peer_cookie_len = cfg->peer_cookie_len;
+		memcpy(&session->peer_cookie[0], &cfg->peer_cookie[0], cfg->peer_cookie_len);
+	}
 
-		/* Bump the reference count. The session context is deleted
-		 * only when this drops to zero.
-		 */
-		l2tp_session_inc_refcount(session);
-		l2tp_tunnel_inc_refcount(tunnel);
+	if (tunnel->version == L2TP_HDR_VER_2)
+		session->build_header = l2tp_build_l2tpv2_header;
+	else
+		session->build_header = l2tp_build_l2tpv3_header;
 
-		/* Ensure tunnel socket isn't deleted */
-		sock_hold(tunnel->sock);
+	l2tp_session_set_header_len(session, tunnel->version);
 
-		/* Add session to the tunnel's hash list */
-		write_lock_bh(&tunnel->hlist_lock);
-		hlist_add_head(&session->hlist,
-			       l2tp_session_id_hash(tunnel, session_id));
-		write_unlock_bh(&tunnel->hlist_lock);
+	/* Bump the reference count. The session context is deleted
+	 * only when this drops to zero.
+	 */
+	l2tp_session_inc_refcount(session);
+	l2tp_tunnel_inc_refcount(tunnel);
 
-		/* And to the global session list if L2TPv3 */
-		if (tunnel->version != L2TP_HDR_VER_2) {
-			struct l2tp_net *pn = l2tp_pernet(tunnel->l2tp_net);
+	/* Ensure tunnel socket isn't deleted */
+	sock_hold(tunnel->sock);
 
-			spin_lock_bh(&pn->l2tp_session_hlist_lock);
-			hlist_add_head_rcu(&session->global_hlist,
-					   l2tp_session_id_hash_2(pn, session_id));
-			spin_unlock_bh(&pn->l2tp_session_hlist_lock);
-		}
+	/* Add session to the tunnel's hash list */
+	write_lock_bh(&tunnel->hlist_lock);
+	hlist_add_head(&session->hlist,
+			l2tp_session_id_hash(tunnel, session_id));
+	write_unlock_bh(&tunnel->hlist_lock);
 
-		/* Ignore management session in session count value */
-		if (session->session_id != 0)
-			atomic_inc(&l2tp_session_count);
+	/* And to the global session list if L2TPv3 */
+	if (tunnel->version != L2TP_HDR_VER_2) {
+		struct l2tp_net *pn = l2tp_pernet(tunnel->l2tp_net);
+
+		spin_lock_bh(&pn->l2tp_session_hlist_lock);
+		hlist_add_head_rcu(&session->global_hlist,
+				l2tp_session_id_hash_2(pn, session_id));
+		spin_unlock_bh(&pn->l2tp_session_hlist_lock);
 	}
 
+	/* Ignore management session in session count value */
+	if (session->session_id != 0)
+		atomic_inc(&l2tp_session_count);
+
+err:
 	return session;
 }
 EXPORT_SYMBOL_GPL(l2tp_session_create);
diff --git a/net/l2tp/l2tp_core.h b/net/l2tp/l2tp_core.h
index a38ec6c..29bb34c 100644
--- a/net/l2tp/l2tp_core.h
+++ b/net/l2tp/l2tp_core.h
@@ -35,19 +35,34 @@ 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;
+/* Stats are synchronised via a synchronisation point for safe
+ * reader/writer access on 64 and 32 bit kernels.  Stats are
+ * stored on a per-cpu basis for safe lockless updating, and
+ * summed at read time.  This allows the statistics update in
+ * the data path to be fast at some small cost when reading.
+ */
+struct l2tp_rx_stats {
+	u64			packets;
+	u64			bytes;
+	u64			errors;
+	u64			seq_discards;
+	u64			cookie_discards;
+	u64			oos_packets;
+	struct u64_stats_sync	syncp;
+};
+
+struct l2tp_tx_stats {
+	u64			packets;
+	u64			bytes;
+	u64			errors;
 	struct u64_stats_sync	syncp;
 };
 
+struct l2tp_stats {
+	struct l2tp_rx_stats	rx;
+	struct l2tp_tx_stats	tx;
+};
+
 struct l2tp_tunnel;
 
 /* Describes a session. Contains information to determine incoming
@@ -127,7 +142,9 @@ struct l2tp_session {
 	int			mtu;
 	int			mru;
 	enum l2tp_pwtype	pwtype;
-	struct l2tp_stats	stats;
+
+	struct l2tp_stats	*cpustats;	/* per-cpu counters */
+
 	struct hlist_node	global_hlist;	/* Global hash list node */
 
 	int (*build_header)(struct l2tp_session *session, void *buf);
@@ -175,7 +192,8 @@ struct l2tp_tunnel {
 	int			debug;		/* bitmask of debug message
 						 * categories */
 	enum l2tp_encap_type	encap;
-	struct l2tp_stats	stats;
+
+	struct l2tp_stats	*cpustats;	/* per-cpu counters */
 
 	struct list_head	list;		/* Keep a list of all tunnels */
 	struct net		*l2tp_net;	/* the net we belong to */
@@ -246,6 +264,8 @@ extern int l2tp_xmit_skb(struct l2tp_session *session, struct sk_buff *skb, int
 extern int l2tp_nl_register_ops(enum l2tp_pwtype pw_type, const struct l2tp_nl_cmd_ops *ops);
 extern void l2tp_nl_unregister_ops(enum l2tp_pwtype pw_type);
 
+extern int l2tp_stats_copy(struct l2tp_stats *cpustats, struct l2tp_stats *dest);
+
 /* Session reference counts. Incremented when code obtains a reference
  * to a session.
  */
diff --git a/net/l2tp/l2tp_debugfs.c b/net/l2tp/l2tp_debugfs.c
index c3813bc..5ec4732 100644
--- a/net/l2tp/l2tp_debugfs.c
+++ b/net/l2tp/l2tp_debugfs.c
@@ -106,6 +106,7 @@ static void l2tp_dfs_seq_tunnel_show(struct seq_file *m, void *v)
 	int hash;
 	struct hlist_node *walk;
 	struct hlist_node *tmp;
+	struct l2tp_stats stats;
 
 	read_lock_bh(&tunnel->hlist_lock);
 	for (hash = 0; hash < L2TP_HASH_SIZE; hash++) {
@@ -146,14 +147,18 @@ 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",
-		   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);
+	if (!l2tp_stats_copy(tunnel->cpustats, &stats)) {
+		seq_printf(m, " %08x tx %llu/%llu/%llu rx %llu/%llu/%llu\n",
+				tunnel->debug,
+				(unsigned long long)stats.tx.packets,
+				(unsigned long long)stats.tx.bytes,
+				(unsigned long long)stats.tx.errors,
+				(unsigned long long)stats.rx.packets,
+				(unsigned long long)stats.rx.bytes,
+				(unsigned long long)stats.rx.errors);
+	} else {
+		seq_printf(m, " stats lookup failure\n");
+	}
 
 	if (tunnel->show != NULL)
 		tunnel->show(m, tunnel);
@@ -162,6 +167,7 @@ static void l2tp_dfs_seq_tunnel_show(struct seq_file *m, void *v)
 static void l2tp_dfs_seq_session_show(struct seq_file *m, void *v)
 {
 	struct l2tp_session *session = v;
+	struct l2tp_stats stats;
 
 	seq_printf(m, "  SESSION %u, peer %u, %s\n", session->session_id,
 		   session->peer_session_id,
@@ -203,14 +209,18 @@ 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",
-		   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);
+	if (!l2tp_stats_copy(session->cpustats, &stats)) {
+		seq_printf(m, "   %hu/%hu tx %llu/%llu/%llu rx %llu/%llu/%llu\n",
+				session->nr, session->ns,
+				(unsigned long long)stats.tx.packets,
+				(unsigned long long)stats.tx.bytes,
+				(unsigned long long)stats.tx.errors,
+				(unsigned long long)stats.rx.packets,
+				(unsigned long long)stats.rx.bytes,
+				(unsigned long long)stats.rx.errors);
+	} else {
+		seq_printf(m, " stats lookup failure\n");
+	}
 
 	if (session->show != NULL)
 		session->show(m, session);
diff --git a/net/l2tp/l2tp_netlink.c b/net/l2tp/l2tp_netlink.c
index ddc553e..09fd33d 100644
--- a/net/l2tp/l2tp_netlink.c
+++ b/net/l2tp/l2tp_netlink.c
@@ -246,7 +246,6 @@ static int l2tp_nl_tunnel_send(struct sk_buff *skb, u32 pid, u32 seq, int flags,
 	struct ipv6_pinfo *np = NULL;
 #endif
 	struct l2tp_stats stats;
-	unsigned int start;
 
 	hdr = genlmsg_put(skb, pid, seq, &l2tp_nl_family, flags,
 			  L2TP_CMD_TUNNEL_GET);
@@ -264,28 +263,19 @@ static int l2tp_nl_tunnel_send(struct sk_buff *skb, u32 pid, u32 seq, int flags,
 	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 (0 != l2tp_stats_copy(tunnel->cpustats, &stats))
+		goto nla_put_failure;
+
+	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) ||
 	    nla_put_u64(skb, L2TP_ATTR_RX_SEQ_DISCARDS,
-			stats.rx_seq_discards) ||
+			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))
+			stats.rx.oos_packets) ||
+	    nla_put_u64(skb, L2TP_ATTR_RX_ERRORS, stats.rx.errors))
 		goto nla_put_failure;
 	nla_nest_end(skb, nest);
 
@@ -612,7 +602,6 @@ static int l2tp_nl_session_send(struct sk_buff *skb, u32 pid, u32 seq, int flags
 	struct l2tp_tunnel *tunnel = session->tunnel;
 	struct sock *sk = NULL;
 	struct l2tp_stats stats;
-	unsigned int start;
 
 	sk = tunnel->sock;
 
@@ -655,28 +644,19 @@ static int l2tp_nl_session_send(struct sk_buff *skb, u32 pid, u32 seq, int flags
 	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 (0 != l2tp_stats_copy(session->cpustats, &stats))
+		goto nla_put_failure;
+
+	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) ||
 	    nla_put_u64(skb, L2TP_ATTR_RX_SEQ_DISCARDS,
-			stats.rx_seq_discards) ||
+			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))
+			stats.rx.oos_packets) ||
+	    nla_put_u64(skb, L2TP_ATTR_RX_ERRORS, stats.rx.errors))
 		goto nla_put_failure;
 	nla_nest_end(skb, nest);
 
diff --git a/net/l2tp/l2tp_ppp.c b/net/l2tp/l2tp_ppp.c
index 286366e..054c069 100644
--- a/net/l2tp/l2tp_ppp.c
+++ b/net/l2tp/l2tp_ppp.c
@@ -222,6 +222,7 @@ static void pppol2tp_recv(struct l2tp_session *session, struct sk_buff *skb, int
 {
 	struct pppol2tp_session *ps = l2tp_session_priv(session);
 	struct sock *sk = NULL;
+	struct l2tp_stats *sstats = NULL;
 
 	/* If the socket is bound, send it in to PPP's input queue. Otherwise
 	 * queue it on the session socket.
@@ -259,7 +260,10 @@ 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++;
+		sstats = this_cpu_ptr(session->cpustats);
+		u64_stats_update_begin(&sstats->rx.syncp);
+		sstats->rx.errors++;
+		u64_stats_update_end(&sstats->rx.syncp);
 		kfree_skb(skb);
 	}
 
@@ -1028,16 +1032,21 @@ end:
  ****************************************************************************/
 
 static void pppol2tp_copy_stats(struct pppol2tp_ioc_stats *dest,
-				struct l2tp_stats *stats)
+				struct l2tp_stats *cpustats)
 {
-	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;
+	struct l2tp_stats tmp;
+
+	if (0 != l2tp_stats_copy(cpustats, &tmp))
+		return;
+
+	dest->tx_packets = tmp.tx.packets;
+	dest->tx_bytes = tmp.tx.bytes;
+	dest->tx_errors = tmp.tx.errors;
+	dest->rx_packets = tmp.rx.packets;
+	dest->rx_bytes = tmp.rx.bytes;
+	dest->rx_seq_discards = tmp.rx.seq_discards;
+	dest->rx_oos_packets = tmp.rx.oos_packets;
+	dest->rx_errors = tmp.rx.errors;
 }
 
 /* Session ioctl helper.
@@ -1151,7 +1160,7 @@ static int pppol2tp_session_ioctl(struct l2tp_session *session,
 		memset(&stats, 0, sizeof(stats));
 		stats.tunnel_id = tunnel->tunnel_id;
 		stats.session_id = session->session_id;
-		pppol2tp_copy_stats(&stats, &session->stats);
+		pppol2tp_copy_stats(&stats, session->cpustats);
 		if (copy_to_user((void __user *) arg, &stats,
 				 sizeof(stats)))
 			break;
@@ -1214,7 +1223,7 @@ static int pppol2tp_tunnel_ioctl(struct l2tp_tunnel *tunnel,
 #ifdef CONFIG_XFRM
 		stats.using_ipsec = (sk->sk_policy[0] || sk->sk_policy[1]) ? 1 : 0;
 #endif
-		pppol2tp_copy_stats(&stats, &tunnel->stats);
+		pppol2tp_copy_stats(&stats, tunnel->cpustats);
 		if (copy_to_user((void __user *) arg, &stats, sizeof(stats))) {
 			err = -EFAULT;
 			break;
@@ -1666,19 +1675,24 @@ static void pppol2tp_seq_stop(struct seq_file *p, void *v)
 static void pppol2tp_seq_tunnel_show(struct seq_file *m, void *v)
 {
 	struct l2tp_tunnel *tunnel = v;
+	struct l2tp_stats tmp;
 
 	seq_printf(m, "\nTUNNEL '%s', %c %d\n",
 		   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",
-		   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);
+	if (!l2tp_stats_copy(tunnel->cpustats, &tmp)) {
+		seq_printf(m, " %08x %llu/%llu/%llu %llu/%llu/%llu\n",
+				tunnel->debug,
+				(unsigned long long)tmp.tx.packets,
+				(unsigned long long)tmp.tx.bytes,
+				(unsigned long long)tmp.tx.errors,
+				(unsigned long long)tmp.rx.packets,
+				(unsigned long long)tmp.rx.bytes,
+				(unsigned long long)tmp.rx.errors);
+	} else {
+		seq_printf(m, " stats lookup failure\n");
+	}
 }
 
 static void pppol2tp_seq_session_show(struct seq_file *m, void *v)
@@ -1687,6 +1701,7 @@ static void pppol2tp_seq_session_show(struct seq_file *m, void *v)
 	struct l2tp_tunnel *tunnel = session->tunnel;
 	struct pppol2tp_session *ps = l2tp_session_priv(session);
 	struct pppox_sock *po = pppox_sk(ps->sock);
+	struct l2tp_stats tmp;
 	u32 ip = 0;
 	u16 port = 0;
 
@@ -1713,14 +1728,18 @@ 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",
-		   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);
+	if (!l2tp_stats_copy(session->cpustats, &tmp)) {
+		seq_printf(m, "   %hu/%hu %llu/%llu/%llu %llu/%llu/%llu\n",
+			   session->nr, session->ns,
+			   (unsigned long long)tmp.tx.packets,
+			   (unsigned long long)tmp.tx.bytes,
+			   (unsigned long long)tmp.tx.errors,
+			   (unsigned long long)tmp.rx.packets,
+			   (unsigned long long)tmp.rx.bytes,
+			   (unsigned long long)tmp.rx.errors);
+	} else {
+		seq_printf(m, " stats lookup failure\n");
+	}
 
 	if (po)
 		seq_printf(m, "   interface %s\n", ppp_dev_name(&po->chan));
-- 
1.7.9.5

--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ