[Openvpn-devel,net-next] ovpn: honor NLM_F_ECHO for peer deletion notifications

Message ID 20261014144756.511996-1-marco@mandelbit.com
State New
Headers
Series [Openvpn-devel,net-next] ovpn: honor NLM_F_ECHO for peer deletion notifications |

Commit Message

Marco Baffo Oct. 14, 2026, 2:47 p.m. UTC
  Peer deletion notifications are sent only to the multicast group, ignoring
NLM_F_ECHO on the triggering request. A requester without a multicast
subscription therefore receives no deletion notification.

Pass the request context through the peer deletion paths, including P2P
peer replacement, and send a unicast copy when NLM_F_ECHO is set. Send
the echo to the requester's network namespace with the request's port ID
and sequence number, while multicasting in the device's namespace.

Exclude the requesting socket from multicast delivery when sending an
echo to avoid duplicate notifications. Compare socket pointers rather
than port IDs, since distinct sockets in different network namespaces
can have the same port ID.

Signed-off-by: Marco Baffo <marco@mandelbit.com>
---

This patch depends on the ovpn foreign-netns support series:
https://lore.kernel.org/all/20260922142505.3138388-1-marco@mandelbit.com/

It builds on that series' notification routing: multicast notifications
go to the device's network namespace, while an NLM_F_ECHO response goes
to the requesting socket.

 drivers/net/ovpn/netlink.c | 34 +++++++++++++++++++++++++-----
 drivers/net/ovpn/netlink.h | 13 +++++++++++-
 drivers/net/ovpn/peer.c    | 43 ++++++++++++++++++++++++--------------
 drivers/net/ovpn/peer.h    |  8 +++++--
 drivers/net/ovpn/tcp.c     |  4 ++--
 5 files changed, 76 insertions(+), 26 deletions(-)
  

Patch

diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c
index 5c94d34219c73..c8c471a2959fe 100644
--- a/drivers/net/ovpn/netlink.c
+++ b/drivers/net/ovpn/netlink.c
@@ -370,6 +370,7 @@  static int ovpn_nl_peer_modify(struct ovpn_peer *peer, struct genl_info *info,
 
 int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info)
 {
+	struct ovpn_nl_request req = { .info = info, .sk = NETLINK_CB(skb).sk };
 	struct nlattr *attrs[OVPN_A_PEER_MAX + 1];
 	struct ovpn_priv *ovpn = info->user_ptr[0];
 	struct ovpn_socket *ovpn_sock;
@@ -473,7 +474,7 @@  int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info)
 	if (ret < 0)
 		goto sock_release;
 
-	ret = ovpn_peer_add(ovpn, peer);
+	ret = ovpn_peer_add(ovpn, peer, &req);
 	if (ret < 0) {
 		NL_SET_ERR_MSG_FMT_MOD(info->extack,
 				       "cannot add new peer (id=%u) to hashtable: %d",
@@ -817,6 +818,7 @@  int ovpn_nl_peer_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
 
 int ovpn_nl_peer_del_doit(struct sk_buff *skb, struct genl_info *info)
 {
+	struct ovpn_nl_request req = { .info = info, .sk = NETLINK_CB(skb).sk };
 	struct nlattr *attrs[OVPN_A_PEER_MAX + 1];
 	struct ovpn_priv *ovpn = info->user_ptr[0];
 	struct ovpn_peer *peer;
@@ -844,7 +846,7 @@  int ovpn_nl_peer_del_doit(struct sk_buff *skb, struct genl_info *info)
 	}
 
 	netdev_dbg(ovpn->dev, "del peer %u\n", peer->id);
-	ret = ovpn_peer_del(peer, OVPN_DEL_PEER_REASON_USERSPACE);
+	ret = ovpn_peer_del(peer, OVPN_DEL_PEER_REASON_USERSPACE, &req);
 	ovpn_peer_put(peer);
 
 	return ret;
@@ -1180,17 +1182,27 @@  int ovpn_nl_key_del_doit(struct sk_buff *skb, struct genl_info *info)
 	return 0;
 }
 
+static int ovpn_nl_echo_filter(struct sock *sk, struct sk_buff *skb, void *data)
+{
+	return sk == data;
+}
+
 /**
  * ovpn_nl_peer_del_notify - notify userspace about peer being deleted
  * @peer: the peer being deleted
+ * @req: request context, or NULL for unsolicited notifications
  *
  * Return: 0 on success or a negative error code otherwise
  */
-int ovpn_nl_peer_del_notify(struct ovpn_peer *peer)
+int ovpn_nl_peer_del_notify(struct ovpn_peer *peer,
+			    struct ovpn_nl_request *req)
 {
+	struct genl_info *info = req ? req->info : NULL;
+	struct net *net = dev_net(peer->ovpn->dev);
 	struct sk_buff *msg;
 	struct nlattr *attr;
 	int ret = -EMSGSIZE;
+	bool report;
 	void *hdr;
 
 	netdev_info(peer->ovpn->dev, "deleting peer with id %u, reason %d\n",
@@ -1221,8 +1233,20 @@  int ovpn_nl_peer_del_notify(struct ovpn_peer *peer)
 
 	nla_nest_end(msg, attr);
 	genlmsg_end(msg, hdr);
-	genlmsg_multicast_netns(&ovpn_nl_family, dev_net(peer->ovpn->dev), msg,
-				0, OVPN_NLGRP_PEERS, GFP_ATOMIC);
+	report = info && nlmsg_report(info->nlhdr);
+
+	if (report) {
+		nlmsg_hdr(msg)->nlmsg_pid = info->snd_portid;
+		nlmsg_hdr(msg)->nlmsg_seq = info->snd_seq;
+		/* the echo goes to the requester, the multicast to the device */
+		skb_get(msg);
+	}
+	genlmsg_multicast_netns_filtered(&ovpn_nl_family, net, msg, 0,
+					 OVPN_NLGRP_PEERS, GFP_ATOMIC,
+					 ovpn_nl_echo_filter,
+					 report ? req->sk : NULL);
+	if (report)
+		genlmsg_reply(msg, info);
 
 	return 0;
 
diff --git a/drivers/net/ovpn/netlink.h b/drivers/net/ovpn/netlink.h
index 11ee7c681885d..752dd96c48932 100644
--- a/drivers/net/ovpn/netlink.h
+++ b/drivers/net/ovpn/netlink.h
@@ -9,10 +9,21 @@ 
 #ifndef _NET_OVPN_NETLINK_H_
 #define _NET_OVPN_NETLINK_H_
 
+/**
+ * struct ovpn_nl_request - context for request-triggered notifications
+ * @info: generic netlink request information
+ * @sk: requesting netlink socket
+ */
+struct ovpn_nl_request {
+	struct genl_info *info;
+	struct sock *sk;
+};
+
 int ovpn_nl_register(void);
 void ovpn_nl_unregister(void);
 
-int ovpn_nl_peer_del_notify(struct ovpn_peer *peer);
+int ovpn_nl_peer_del_notify(struct ovpn_peer *peer,
+			    struct ovpn_nl_request *req);
 int ovpn_nl_peer_float_notify(struct ovpn_peer *peer,
 			      const struct sockaddr_storage *ss);
 int ovpn_nl_key_swap_notify(struct ovpn_peer *peer, u8 key_id);
diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
index c95656ca7c357..c610d9d00854c 100644
--- a/drivers/net/ovpn/peer.c
+++ b/drivers/net/ovpn/peer.c
@@ -684,7 +684,8 @@  struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_priv *ovpn, u32 peer_id)
 
 static void ovpn_peer_remove(struct ovpn_peer *peer,
 			     enum ovpn_del_peer_reason reason,
-			     struct llist_head *release_list)
+			     struct llist_head *release_list,
+			     struct ovpn_nl_request *req)
 {
 	lockdep_assert_held(&peer->ovpn->lock);
 
@@ -713,7 +714,7 @@  static void ovpn_peer_remove(struct ovpn_peer *peer,
 	}
 
 	peer->delete_reason = reason;
-	ovpn_nl_peer_del_notify(peer);
+	ovpn_nl_peer_del_notify(peer, req);
 
 	/* append to provided list for later socket release and ref drop */
 	llist_add(&peer->release_entry, release_list);
@@ -1082,10 +1083,12 @@  static int ovpn_peer_add_mp(struct ovpn_priv *ovpn, struct ovpn_peer *peer)
  * ovpn_peer_add_p2p - add peer to related tables in a P2P instance
  * @ovpn: the instance to add the peer to
  * @peer: the peer to add
+ * @req: request context
  *
  * Return: 0 on success or a negative error code otherwise
  */
-static int ovpn_peer_add_p2p(struct ovpn_priv *ovpn, struct ovpn_peer *peer)
+static int ovpn_peer_add_p2p(struct ovpn_priv *ovpn, struct ovpn_peer *peer,
+			     struct ovpn_nl_request *req)
 {
 	LLIST_HEAD(release_list);
 	struct ovpn_peer *tmp;
@@ -1098,7 +1101,7 @@  static int ovpn_peer_add_p2p(struct ovpn_priv *ovpn, struct ovpn_peer *peer)
 					lockdep_is_held(&ovpn->lock));
 	if (tmp)
 		ovpn_peer_remove(tmp, OVPN_DEL_PEER_REASON_TEARDOWN,
-				 &release_list);
+				 &release_list, req);
 
 	rcu_assign_pointer(ovpn->peer, peer);
 	/* in P2P mode the carrier is switched on when the peer is added */
@@ -1112,12 +1115,14 @@  static int ovpn_peer_add_p2p(struct ovpn_priv *ovpn, struct ovpn_peer *peer)
  * ovpn_peer_add - add peer to the related tables
  * @ovpn: the openvpn instance the peer belongs to
  * @peer: the peer object to add
+ * @req: request context
  *
  * Assume refcounter was increased by caller
  *
  * Return: 0 on success or a negative error code otherwise
  */
-int ovpn_peer_add(struct ovpn_priv *ovpn, struct ovpn_peer *peer)
+int ovpn_peer_add(struct ovpn_priv *ovpn, struct ovpn_peer *peer,
+		  struct ovpn_nl_request *req)
 {
 	int ret = -ENODEV;
 
@@ -1135,7 +1140,7 @@  int ovpn_peer_add(struct ovpn_priv *ovpn, struct ovpn_peer *peer)
 		ret = ovpn_peer_add_mp(ovpn, peer);
 		break;
 	case OVPN_MODE_P2P:
-		ret = ovpn_peer_add_p2p(ovpn, peer);
+		ret = ovpn_peer_add_p2p(ovpn, peer, req);
 		break;
 	}
 out:
@@ -1149,12 +1154,14 @@  int ovpn_peer_add(struct ovpn_priv *ovpn, struct ovpn_peer *peer)
  * @peer: the peer to delete
  * @reason: reason why the peer was deleted (sent to userspace)
  * @release_list: list where delete peer should be appended
+ * @req: request context, or NULL for unsolicited deletion
  *
  * Return: 0 on success or a negative error code otherwise
  */
 static int ovpn_peer_del_mp(struct ovpn_peer *peer,
 			    enum ovpn_del_peer_reason reason,
-			    struct llist_head *release_list)
+			    struct llist_head *release_list,
+			    struct ovpn_nl_request *req)
 {
 	struct ovpn_peer *tmp;
 	int ret = -ENOENT;
@@ -1163,7 +1170,7 @@  static int ovpn_peer_del_mp(struct ovpn_peer *peer,
 
 	tmp = ovpn_peer_get_by_id(peer->ovpn, peer->id);
 	if (tmp == peer) {
-		ovpn_peer_remove(peer, reason, release_list);
+		ovpn_peer_remove(peer, reason, release_list, req);
 		ret = 0;
 	}
 
@@ -1178,12 +1185,14 @@  static int ovpn_peer_del_mp(struct ovpn_peer *peer,
  * @peer: the peer to delete
  * @reason: reason why the peer was deleted (sent to userspace)
  * @release_list: list where delete peer should be appended
+ * @req: request context, or NULL for unsolicited deletion
  *
  * Return: 0 on success or a negative error code otherwise
  */
 static int ovpn_peer_del_p2p(struct ovpn_peer *peer,
 			     enum ovpn_del_peer_reason reason,
-			     struct llist_head *release_list)
+			     struct llist_head *release_list,
+			     struct ovpn_nl_request *req)
 {
 	struct ovpn_peer *tmp;
 
@@ -1194,7 +1203,7 @@  static int ovpn_peer_del_p2p(struct ovpn_peer *peer,
 	if (tmp != peer)
 		return -ENOENT;
 
-	ovpn_peer_remove(peer, reason, release_list);
+	ovpn_peer_remove(peer, reason, release_list, req);
 
 	return 0;
 }
@@ -1203,10 +1212,12 @@  static int ovpn_peer_del_p2p(struct ovpn_peer *peer,
  * ovpn_peer_del - delete peer from related tables
  * @peer: the peer object to delete
  * @reason: reason for deleting peer (will be sent to userspace)
+ * @req: request context, or NULL for unsolicited deletion
  *
  * Return: 0 on success or a negative error code otherwise
  */
-int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason)
+int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason,
+		  struct ovpn_nl_request *req)
 {
 	LLIST_HEAD(release_list);
 	int ret = -EOPNOTSUPP;
@@ -1214,10 +1225,10 @@  int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason)
 	spin_lock_bh(&peer->ovpn->lock);
 	switch (peer->ovpn->mode) {
 	case OVPN_MODE_MP:
-		ret = ovpn_peer_del_mp(peer, reason, &release_list);
+		ret = ovpn_peer_del_mp(peer, reason, &release_list, req);
 		break;
 	case OVPN_MODE_P2P:
-		ret = ovpn_peer_del_p2p(peer, reason, &release_list);
+		ret = ovpn_peer_del_p2p(peer, reason, &release_list, req);
 		break;
 	default:
 		break;
@@ -1256,7 +1267,7 @@  static void ovpn_peer_release_p2p(struct ovpn_priv *ovpn, struct sock *sk,
 		}
 	}
 
-	ovpn_peer_remove(peer, reason, &release_list);
+	ovpn_peer_remove(peer, reason, &release_list, NULL);
 	unlock_ovpn(ovpn, &release_list);
 }
 
@@ -1284,7 +1295,7 @@  static void ovpn_peers_release_mp(struct ovpn_priv *ovpn, struct sock *sk,
 		}
 
 		if (remove)
-			ovpn_peer_remove(peer, reason, &release_list);
+			ovpn_peer_remove(peer, reason, &release_list, NULL);
 	}
 	unlock_ovpn(ovpn, &release_list);
 }
@@ -1345,7 +1356,7 @@  static time64_t ovpn_peer_keepalive_work_single(struct ovpn_peer *peer,
 		netdev_dbg(peer->ovpn->dev, "peer %u expired\n",
 			   peer->id);
 		ovpn_peer_remove(peer, OVPN_DEL_PEER_REASON_EXPIRED,
-				 release_list);
+				 release_list, NULL);
 		return 0;
 	}
 
diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h
index dfa5c0037e02b..4957c09aa0668 100644
--- a/drivers/net/ovpn/peer.h
+++ b/drivers/net/ovpn/peer.h
@@ -17,6 +17,8 @@ 
 #include "socket.h"
 #include "stats.h"
 
+struct ovpn_nl_request;
+
 /**
  * struct ovpn_peer - the main remote peer object
  * @ovpn: main openvpn instance this peer belongs to
@@ -139,8 +141,10 @@  static inline void ovpn_peer_put(struct ovpn_peer *peer)
 }
 
 struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *ovpn, u32 id);
-int ovpn_peer_add(struct ovpn_priv *ovpn, struct ovpn_peer *peer);
-int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason);
+int ovpn_peer_add(struct ovpn_priv *ovpn, struct ovpn_peer *peer,
+		  struct ovpn_nl_request *req);
+int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason,
+		  struct ovpn_nl_request *req);
 void ovpn_peers_free(struct ovpn_priv *ovpn, struct sock *sock,
 		     enum ovpn_del_peer_reason reason);
 
diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c
index 8fe8a8e750a40..c74f11af44074 100644
--- a/drivers/net/ovpn/tcp.c
+++ b/drivers/net/ovpn/tcp.c
@@ -502,7 +502,7 @@  static void ovpn_tcp_peer_del_work(struct work_struct *work)
 	struct ovpn_peer *peer = container_of(work, struct ovpn_peer,
 					      tcp.defer_del_work);
 
-	ovpn_peer_del(peer, OVPN_DEL_PEER_REASON_TRANSPORT_ERROR);
+	ovpn_peer_del(peer, OVPN_DEL_PEER_REASON_TRANSPORT_ERROR, NULL);
 	ovpn_peer_put(peer);
 }
 
@@ -594,7 +594,7 @@  static void ovpn_tcp_close(struct sock *sk, long timeout)
 	}
 	rcu_read_unlock();
 
-	ovpn_peer_del(peer, OVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT);
+	ovpn_peer_del(peer, OVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT, NULL);
 	peer->tcp.sk_cb.prot->close(sk, timeout);
 	ovpn_peer_put(peer);
 }