[Openvpn-devel] [PATCH ovpn net] ovpn: batch socket release to call synchronize_net() once

Marco Baffo <[email protected]> Wed, 29 Jul 2026 10:23:00 +0200
Newsgroups net.sourceforge.lists.openvpn-devel
Message-ID <[email protected]>
unlock_ovpn() released each queued peer's socket via ovpn_socket_release(),
which runs a synchronize_rcu() per call before freeing the socket.
Tearing down N peers serializes N grace periods.

Split ovpn_socket_release() around the grace period. The new
ovpn_socket_release_prepare() detaches peer->sock and drops the ref,
returning the socket once its refcount hits zero.
ovpn_socket_release_finish() frees it afterwards. unlock_ovpn() now
prepares all sockets, waits once, then finishes them and drops the peer
refs. The wait is skipped when nothing needs freeing. ovpn_socket_release()
chains both halves to keep its behaviour.

Switch synchronize_rcu() to synchronize_net() while at it.

Signed-off-by: Marco Baffo <[email protected]>
---
 drivers/net/ovpn/peer.c   | 22 +++++++--
 drivers/net/ovpn/socket.c | 96 +++++++++++++++++++++++++++------------
 drivers/net/ovpn/socket.h |  4 ++
 3 files changed, 87 insertions(+), 35 deletions(-)

diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
index a21d02ac715e..c2110d8435e6 100644
--- a/drivers/net/ovpn/peer.c
+++ b/drivers/net/ovpn/peer.c
@@ -26,15 +26,27 @@ static void unlock_ovpn(struct ovpn_priv *ovpn,
 			 struct llist_head *release_list)
 	__releases(&ovpn->lock)
 {
-	struct ovpn_peer *peer, *next;
+	struct ovpn_peer *peer, *next_peer;
+	struct ovpn_socket *sock, *next_sock;
+	LIST_HEAD(sock_release_list);
 
 	spin_unlock_bh(&ovpn->lock);
 
-	llist_for_each_entry_safe(peer, next, release_list->first,
-				  release_entry) {
-		ovpn_socket_release(peer);
-		ovpn_peer_put(peer);
+	llist_for_each_entry(peer, release_list->first, release_entry) {
+		sock = ovpn_socket_release_prepare(peer);
+		if (!sock)
+			continue;
+		list_add_tail(&sock->release_entry, &sock_release_list);
 	}
+
+	if (!list_empty(&sock_release_list))
+		synchronize_net();
+
+	list_for_each_entry_safe(sock, next_sock, &sock_release_list, release_entry)
+		ovpn_socket_release_finish(sock);
+
+	llist_for_each_entry_safe(peer, next_peer, release_list->first, release_entry)
+		ovpn_peer_put(peer);
 }
 
 /**
diff --git a/drivers/net/ovpn/socket.c b/drivers/net/ovpn/socket.c
index 517caa64a4fe..dd27b79a062e 100644
--- a/drivers/net/ovpn/socket.c
+++ b/drivers/net/ovpn/socket.c
@@ -45,6 +45,69 @@ static bool ovpn_socket_put(struct ovpn_peer *peer, struct ovpn_socket *sock)
 	return kref_put(&sock->refcount, ovpn_socket_release_kref);
 }
 
+/**
+ * ovpn_socket_release_prepare - detach peer from its socket
+ * @peer: peer whose socket should be detached
+ *
+ * Return: the detached socket if its refcount reached zero, NULL otherwise.
+ * If a socket is returned, the caller must call synchronize_net() before
+ * invoking ovpn_socket_release_finish().
+ */
+struct ovpn_socket *ovpn_socket_release_prepare(struct ovpn_peer *peer)
+{
+	struct ovpn_socket *sock;
+	bool released;
+
+	might_sleep();
+
+	sock = rcu_replace_pointer(peer->sock, NULL, true);
+	/* release may be invoked after socket was detached */
+	if (!sock)
+		return NULL;
+
+	/* Drop the reference while holding the sock lock to avoid
+	 * concurrent ovpn_socket_new call to mess up with a partially
+	 * detached socket.
+	 *
+	 * Holding the lock ensures that a socket with refcnt 0 is fully
+	 * detached before it can be picked by a concurrent reader.
+	 */
+	lock_sock(sock->sk);
+	released = ovpn_socket_put(peer, sock);
+	release_sock(sock->sk);
+
+	if (released)
+		return sock;
+
+	return NULL;
+}
+
+/**
+ * ovpn_socket_release_finish - complete release of a detached socket
+ * @sock: socket whose refcount reached zero
+ *
+ * The caller must have called synchronize_net() after
+ * ovpn_socket_release_prepare() returned this socket.
+ */
+void ovpn_socket_release_finish(struct ovpn_socket *sock)
+{
+	might_sleep();
+
+	if (sock->sk->sk_protocol == IPPROTO_UDP) {
+		netdev_put(sock->ovpn->dev, &sock->dev_tracker);
+	} else if (sock->sk->sk_protocol == IPPROTO_TCP) {
+		/* wait for TCP jobs to terminate */
+		ovpn_tcp_socket_wait_finish(sock);
+		ovpn_peer_put(sock->peer);
+	}
+	/* drop reference acquired in ovpn_socket_new() */
+	sock_put(sock->sk);
+	/* we can call plain kfree() because we already waited one RCU
+	 * period due to synchronize_net()
+	 */
+	kfree(sock);
+}
+
 /**
  * ovpn_socket_release - release resources owned by socket user
  * @peer: peer whose socket should be released
@@ -66,45 +129,18 @@ static bool ovpn_socket_put(struct ovpn_peer *peer, struct ovpn_socket *sock)
 void ovpn_socket_release(struct ovpn_peer *peer)
 {
 	struct ovpn_socket *sock;
-	bool released;
 
 	might_sleep();
 
-	sock = rcu_replace_pointer(peer->sock, NULL, true);
-	/* release may be invoked after socket was detached */
+	sock = ovpn_socket_release_prepare(peer);
 	if (!sock)
 		return;
 
-	/* Drop the reference while holding the sock lock to avoid
-	 * concurrent ovpn_socket_new call to mess up with a partially
-	 * detached socket.
-	 *
-	 * Holding the lock ensures that a socket with refcnt 0 is fully
-	 * detached before it can be picked by a concurrent reader.
-	 */
-	lock_sock(sock->sk);
-	released = ovpn_socket_put(peer, sock);
-	release_sock(sock->sk);
-
 	/* align all readers with sk_user_data being NULL */
-	synchronize_rcu();
+	synchronize_net();
 
 	/* following cleanup should happen with lock released */
-	if (released) {
-		if (sock->sk->sk_protocol == IPPROTO_UDP) {
-			netdev_put(sock->ovpn->dev, &sock->dev_tracker);
-		} else if (sock->sk->sk_protocol == IPPROTO_TCP) {
-			/* wait for TCP jobs to terminate */
-			ovpn_tcp_socket_wait_finish(sock);
-			ovpn_peer_put(sock->peer);
-		}
-		/* drop reference acquired in ovpn_socket_new() */
-		sock_put(sock->sk);
-		/* we can call plain kfree() because we already waited one RCU
-		 * period due to synchronize_rcu()
-		 */
-		kfree(sock);
-	}
+	ovpn_socket_release_finish(sock);
 }
 
 static bool ovpn_socket_hold(struct ovpn_socket *sock)
diff --git a/drivers/net/ovpn/socket.h b/drivers/net/ovpn/socket.h
index 4afcec71040d..b4c7aa9b6179 100644
--- a/drivers/net/ovpn/socket.h
+++ b/drivers/net/ovpn/socket.h
@@ -24,6 +24,7 @@ struct ovpn_peer;
  * @peer: unique peer transmitting over this socket (TCP only)
  * @sk: the low level sock object
  * @refcount: amount of contexts currently referencing this object
+ * @release_entry: entry for the deferred release list
  * @work: member used to schedule release routine (it may block)
  * @tcp_tx_work: work for deferring outgoing packet processing (TCP only)
  */
@@ -38,6 +39,7 @@ struct ovpn_socket {
 
 	struct sock *sk;
 	struct kref refcount;
+	struct list_head release_entry;
 	struct work_struct work;
 	struct work_struct tcp_tx_work;
 };
@@ -45,5 +47,7 @@ struct ovpn_socket {
 struct ovpn_socket *ovpn_socket_new(struct socket *sock,
 				    struct ovpn_peer *peer);
 void ovpn_socket_release(struct ovpn_peer *peer);
+struct ovpn_socket *ovpn_socket_release_prepare(struct ovpn_peer *peer);
+void ovpn_socket_release_finish(struct ovpn_socket *sock);
 
 #endif /* _NET_OVPN_SOCK_H_ */
-- 
2.43.0



_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel