All of lore.kernel.org
 help / color / mirror / Atom feed
From: Raphael Catolino <rca@koalox.com>
To: wireguard@lists.zx2c4.com
Subject: [PATCH 1/1] wireguard-linux: add netlink multicast group for notifications on peer change
Date: Tue, 03 Oct 2023 21:24:17 +0200	[thread overview]
Message-ID: <2328185.xqv9EDfTUt@desktop> (raw)

From: Linus Karl <linus@lotz.li>

Add a multicast group to the wireguard netlink family, and three message
types to notify subscribers when a peer has changed .

Signed-off-by: Linus Karl <linus@lotz.li>
Co-developed-by: Raphael Catolino <rca@koalox.com>
Signed-off-by: Raphael Catolino <rca@koalox.com>
---
 drivers/net/wireguard/device.h  |   2 +
 drivers/net/wireguard/netlink.c | 231 +++++++++++++++++++++++++++++++-
 drivers/net/wireguard/netlink.h |   6 +
 drivers/net/wireguard/peer.c    |   7 +-
 drivers/net/wireguard/socket.c  |   5 +
 include/uapi/linux/wireguard.h  |  69 ++++++++++
 6 files changed, 316 insertions(+), 4 deletions(-)

diff --git a/drivers/net/wireguard/device.h b/drivers/net/wireguard/device.h
index 43c7cebbf50b..137b1b517815 100644
--- a/drivers/net/wireguard/device.h
+++ b/drivers/net/wireguard/device.h
@@ -54,6 +54,8 @@ struct wg_device {
 	unsigned int num_peers, device_update_gen;
 	u32 fwmark;
 	u16 incoming_port;
+	bool endpoint_monitor;
+	bool peers_monitor;
 };
 
 int wg_device_init(void);
diff --git a/drivers/net/wireguard/netlink.c b/drivers/net/wireguard/netlink.c
index 43c8c84e7ea8..b40b56e697f2 100644
--- a/drivers/net/wireguard/netlink.c
+++ b/drivers/net/wireguard/netlink.c
@@ -27,7 +27,9 @@ static const struct nla_policy device_policy[WGDEVICE_A_MAX + 1] = {
 	[WGDEVICE_A_FLAGS]		= { .type = NLA_U32 },
 	[WGDEVICE_A_LISTEN_PORT]	= { .type = NLA_U16 },
 	[WGDEVICE_A_FWMARK]		= { .type = NLA_U32 },
-	[WGDEVICE_A_PEERS]		= { .type = NLA_NESTED }
+	[WGDEVICE_A_PEERS]		= { .type = NLA_NESTED },
+	[WGDEVICE_A_MONITOR]		= { .type = NLA_U8 },
+	[WGDEVICE_A_PEER]		= { .type = NLA_NESTED }
 };
 
 static const struct nla_policy peer_policy[WGPEER_A_MAX + 1] = {
@@ -233,7 +235,10 @@ static int wg_get_device_dump(struct sk_buff *skb, struct netlink_callback *cb)
 				wg->incoming_port) ||
 		    nla_put_u32(skb, WGDEVICE_A_FWMARK, wg->fwmark) ||
 		    nla_put_u32(skb, WGDEVICE_A_IFINDEX, wg->dev->ifindex) ||
-		    nla_put_string(skb, WGDEVICE_A_IFNAME, wg->dev->name))
+		    nla_put_string(skb, WGDEVICE_A_IFNAME, wg->dev->name) ||
+		    nla_put_u8(skb, WGDEVICE_A_MONITOR,
+			       (wg->endpoint_monitor ? WGDEVICE_MONITOR_F_ENDPOINT : 0) |
+			       (wg->endpoint_monitor ? WGDEVICE_MONITOR_F_PEERS : 0)))
 			goto out;
 
 		down_read(&wg->static_identity.lock);
@@ -482,6 +487,10 @@ static int set_peer(struct wg_device *wg, struct nlattr **attrs)
 	if (netif_running(wg->dev))
 		wg_packet_send_staged_packets(peer);
 
+	if (wg->peers_monitor) {
+		wg_genl_mcast_peer_set(peer);
+	}
+
 out:
 	wg_peer_put(peer);
 	if (attrs[WGPEER_A_PRESHARED_KEY])
@@ -537,6 +546,18 @@ static int wg_set_device(struct sk_buff *skb, struct genl_info *info)
 			goto out;
 	}
 
+	if (info->attrs[WGDEVICE_A_MONITOR]) {
+		u8 monitor = nla_get_u8(info->attrs[WGDEVICE_A_MONITOR]);
+
+		if (monitor & ~__WGDEVICE_MONITOR_F_ALL)
+			goto out;
+
+		wg->endpoint_monitor =
+			(monitor & WGDEVICE_MONITOR_F_ENDPOINT) == WGDEVICE_MONITOR_F_ENDPOINT;
+		wg->peers_monitor =
+			(monitor & WGDEVICE_MONITOR_F_PEERS) == WGDEVICE_MONITOR_F_PEERS;
+	}
+
 	if (flags & WGDEVICE_F_REPLACE_PEERS)
 		wg_peer_remove_all(wg);
 
@@ -617,6 +638,12 @@ static const struct genl_ops genl_ops[] = {
 	}
 };
 
+static const struct genl_multicast_group wg_genl_mcgrps[] = {
+	{
+		.name = WG_MULTICAST_GROUP_PEERS
+	}
+};
+
 static struct genl_family genl_family __ro_after_init = {
 	.ops = genl_ops,
 	.n_ops = ARRAY_SIZE(genl_ops),
@@ -626,7 +653,9 @@ static struct genl_family genl_family __ro_after_init = {
 	.maxattr = WGDEVICE_A_MAX,
 	.module = THIS_MODULE,
 	.policy = device_policy,
-	.netnsok = true
+	.netnsok = true,
+	.mcgrps = wg_genl_mcgrps,
+	.n_mcgrps = ARRAY_SIZE(wg_genl_mcgrps)
 };
 
 int __init wg_genetlink_init(void)
@@ -638,3 +667,199 @@ void __exit wg_genetlink_uninit(void)
 {
 	genl_unregister_family(&genl_family);
 }
+
+int wg_genl_mcast_peer_set(struct wg_peer *peer)
+{
+	struct sk_buff *skb;
+	void *hdr;
+	struct nlattr *allowedips_nest, *peer_nest;
+	struct allowedips_node *allowedips_node;
+	int fail = 0;
+
+	skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+	if (skb == NULL)
+		return -ENOMEM;
+
+	hdr = genlmsg_put(skb, 0, 0, &genl_family, 0, WG_CMD_SET_PEER);
+	if (hdr == NULL) {
+		fail = -EMSGSIZE;
+		goto err;
+	}
+
+	if (nla_put_u32(skb, WGDEVICE_A_IFINDEX, peer->device->dev->ifindex) ||
+		nla_put_string(skb, WGDEVICE_A_IFNAME, peer->device->dev->name))
+		goto err;
+
+	peer_nest = nla_nest_start(skb, WGDEVICE_A_PEER);
+	if (!peer_nest) {
+		fail = -EMSGSIZE;
+		goto err;
+	}
+
+	if (nla_put_u16(skb, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL,
+		peer->persistent_keepalive_interval))
+		goto err;
+
+	down_read(&peer->handshake.lock);
+	fail = nla_put(skb, WGPEER_A_PUBLIC_KEY, NOISE_PUBLIC_KEY_LEN,
+		peer->handshake.remote_static);
+	up_read(&peer->handshake.lock);
+	if (fail)
+		goto err;
+
+	read_lock_bh(&peer->endpoint_lock);
+	if (peer->endpoint.addr.sa_family == AF_INET)
+		fail = nla_put(skb, WGPEER_A_ENDPOINT,
+				sizeof(peer->endpoint.addr4), &peer->endpoint.addr4);
+	else if (peer->endpoint.addr.sa_family == AF_INET6)
+		fail = nla_put(skb, WGPEER_A_ENDPOINT,
+				sizeof(peer->endpoint.addr6), &peer->endpoint.addr6);
+	read_unlock_bh(&peer->endpoint_lock);
+	if (fail)
+		goto err;
+
+	allowedips_node = list_first_entry_or_null(&peer->allowedips_list,
+			struct allowedips_node, peer_list);
+	if (!allowedips_node)
+		goto no_allowedips;
+
+	allowedips_nest = nla_nest_start(skb, WGPEER_A_ALLOWEDIPS);
+	if (!allowedips_nest) {
+		fail = -EMSGSIZE;
+		goto err;
+	}
+
+	list_for_each_entry_from(allowedips_node, &peer->allowedips_list,
+				 peer_list) {
+		u8 cidr, ip[16] __aligned(__alignof(u64));
+		int family;
+
+		family = wg_allowedips_read_node(allowedips_node, ip, &cidr);
+		if (get_allowedips(skb, ip, cidr, family)) {
+			nla_nest_end(skb, allowedips_nest);
+			goto err;
+		}
+	}
+
+	nla_nest_end(skb, allowedips_nest);
+
+no_allowedips:
+	nla_nest_end(skb, peer_nest);
+	genlmsg_end(skb, hdr);
+	fail = genlmsg_multicast_netns(&genl_family, dev_net(peer->device->dev),
+			skb, 0, 0, GFP_KERNEL);
+	return fail;
+
+err:
+	nlmsg_free(skb);
+	return fail;
+}
+
+int wg_genl_mcast_peer_remove(struct wg_peer *peer)
+{
+	struct sk_buff *skb;
+	void *hdr;
+	int fail = 0;
+	struct nlattr *peer_nest;
+
+	skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+	if (skb == NULL)
+		return -ENOMEM;
+
+	hdr = genlmsg_put(skb, 0, 0, &genl_family, 0, WG_CMD_REMOVED_PEER);
+	if (hdr == NULL) {
+		fail = -EMSGSIZE;
+		goto err;
+	}
+
+	if (nla_put_u32(skb, WGDEVICE_A_IFINDEX, peer->device->dev->ifindex) ||
+		nla_put_string(skb, WGDEVICE_A_IFNAME, peer->device->dev->name)) {
+		fail = -EMSGSIZE;
+		goto err;
+	}
+
+	peer_nest = nla_nest_start(skb, WGDEVICE_A_PEER);
+	if (!peer_nest) {
+		fail = -EMSGSIZE;
+		goto err;
+	}
+
+	down_read(&peer->handshake.lock);
+	fail = nla_put(skb, WGPEER_A_PUBLIC_KEY, NOISE_PUBLIC_KEY_LEN,
+		peer->handshake.remote_static);
+	up_read(&peer->handshake.lock);
+	if (fail) {
+		goto err;
+	}
+
+	nla_nest_end(skb, peer_nest);
+	genlmsg_end(skb, hdr);
+	fail = genlmsg_multicast_netns(&genl_family, dev_net(peer->device->dev),
+			skb, 0, 0, GFP_KERNEL);
+	return fail;
+
+err:
+	nlmsg_free(skb);
+	return fail;
+}
+
+int wg_genl_mcast_peer_endpoint_change(struct wg_peer *peer)
+{
+	struct sk_buff *skb;
+	struct nlattr *peer_nest;
+	void *hdr;
+	int fail = 0;
+
+	skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+	if (skb == NULL)
+		return -ENOMEM;
+
+	hdr = genlmsg_put(skb, 0, 0, &genl_family, 0, WG_CMD_CHANGED_ENDPOINT);
+	if (hdr == NULL) {
+		fail = -EMSGSIZE;
+		goto err;
+	}
+
+	if (nla_put_u32(skb, WGDEVICE_A_IFINDEX, peer->device->dev->ifindex) ||
+		nla_put_string(skb, WGDEVICE_A_IFNAME, peer->device->dev->name)) {
+		fail = -EMSGSIZE;
+		goto err;
+	}
+
+	peer_nest = nla_nest_start(skb, WGDEVICE_A_PEER);
+	if (!peer_nest) {
+		fail = -EMSGSIZE;
+		goto err;
+	}
+
+	down_read(&peer->handshake.lock);
+	fail = nla_put(skb, WGPEER_A_PUBLIC_KEY, NOISE_PUBLIC_KEY_LEN,
+		peer->handshake.remote_static);
+	up_read(&peer->handshake.lock);
+	if (fail)
+		goto err;
+
+	read_lock_bh(&peer->endpoint_lock);
+	if (peer->endpoint.addr.sa_family == AF_INET)
+		fail = nla_put(skb, WGPEER_A_ENDPOINT,
+				     sizeof(peer->endpoint.addr4),
+				     &peer->endpoint.addr4);
+	else if (peer->endpoint.addr.sa_family == AF_INET6)
+		fail = nla_put(skb, WGPEER_A_ENDPOINT,
+				     sizeof(peer->endpoint.addr6),
+				     &peer->endpoint.addr6);
+	read_unlock_bh(&peer->endpoint_lock);
+	if (fail)
+		goto err;
+
+	nla_nest_end(skb, peer_nest);
+
+	genlmsg_end(skb, hdr);
+	fail = genlmsg_multicast_netns(&genl_family, dev_net(peer->device->dev),
+			skb, 0, 0, GFP_KERNEL);
+	return fail;
+
+err:
+	nlmsg_free(skb);
+	return fail;
+}
diff --git a/drivers/net/wireguard/netlink.h b/drivers/net/wireguard/netlink.h
index 15100d92e2e3..28e2e307c838 100644
--- a/drivers/net/wireguard/netlink.h
+++ b/drivers/net/wireguard/netlink.h
@@ -6,6 +6,12 @@
 #ifndef _WG_NETLINK_H
 #define _WG_NETLINK_H
 
+#include "peer.h"
+
+int wg_genl_mcast_peer_endpoint_change(struct wg_peer *peer);
+int wg_genl_mcast_peer_remove(struct wg_peer *peer);
+int wg_genl_mcast_peer_set(struct wg_peer *peer);
+
 int wg_genetlink_init(void);
 void wg_genetlink_uninit(void);
 
diff --git a/drivers/net/wireguard/peer.c b/drivers/net/wireguard/peer.c
index 1cb502a932e0..97407304152d 100644
--- a/drivers/net/wireguard/peer.c
+++ b/drivers/net/wireguard/peer.c
@@ -9,6 +9,7 @@
 #include "timers.h"
 #include "peerlookup.h"
 #include "noise.h"
+#include "netlink.h"
 
 #include <linux/kref.h>
 #include <linux/lockdep.h>
@@ -157,8 +158,12 @@ void wg_peer_remove(struct wg_peer *peer)
 {
 	if (unlikely(!peer))
 		return;
-	lockdep_assert_held(&peer->device->device_update_lock);
 
+	if (peer->device->peers_monitor) {
+		wg_genl_mcast_peer_remove(peer);
+	}
+
+	lockdep_assert_held(&peer->device->device_update_lock);
 	peer_make_dead(peer);
 	synchronize_net();
 	peer_remove_after_dead(peer);
diff --git a/drivers/net/wireguard/socket.c b/drivers/net/wireguard/socket.c
index 0414d7a6ce74..33e4da2a37ee 100644
--- a/drivers/net/wireguard/socket.c
+++ b/drivers/net/wireguard/socket.c
@@ -8,6 +8,7 @@
 #include "socket.h"
 #include "queueing.h"
 #include "messages.h"
+#include "netlink.h"
 
 #include <linux/ctype.h>
 #include <linux/net.h>
@@ -294,6 +295,10 @@ void wg_socket_set_peer_endpoint(struct wg_peer *peer,
 	dst_cache_reset(&peer->endpoint_cache);
 out:
 	write_unlock_bh(&peer->endpoint_lock);
+
+	if (peer->device->endpoint_monitor) {
+		wg_genl_mcast_peer_endpoint_change(peer);
+	}
 }
 
 void wg_socket_set_peer_endpoint_from_skb(struct wg_peer *peer,
diff --git a/include/uapi/linux/wireguard.h b/include/uapi/linux/wireguard.h
index ae88be14c947..b28368773f74 100644
--- a/include/uapi/linux/wireguard.h
+++ b/include/uapi/linux/wireguard.h
@@ -29,6 +29,7 @@
  *    WGDEVICE_A_PUBLIC_KEY: NLA_EXACT_LEN, len WG_KEY_LEN
  *    WGDEVICE_A_LISTEN_PORT: NLA_U16
  *    WGDEVICE_A_FWMARK: NLA_U32
+ *    WGDEVICE_A_MONITOR: NLA_U8
  *    WGDEVICE_A_PEERS: NLA_NESTED
  *        0: NLA_NESTED
  *            WGPEER_A_PUBLIC_KEY: NLA_EXACT_LEN, len WG_KEY_LEN
@@ -83,6 +84,9 @@
  *    WGDEVICE_A_PRIVATE_KEY: len WG_KEY_LEN, all zeros to remove
  *    WGDEVICE_A_LISTEN_PORT: NLA_U16, 0 to choose randomly
  *    WGDEVICE_A_FWMARK: NLA_U32, 0 to disable
+ *    WGDEVICE_A_MONITOR: NLA_U8, set to a value of wgdevice_monitor_flag to
+ *                      enable monitoring of events using multicast messages
+ *                      over netlink
  *    WGDEVICE_A_PEERS: NLA_NESTED
  *        0: NLA_NESTED
  *            WGPEER_A_PUBLIC_KEY: len WG_KEY_LEN
@@ -126,6 +130,59 @@
  * of a peer, it likely should not be specified in subsequent fragments.
  *
  * If an error occurs, NLMSG_ERROR will reply containing an errno.
+ *
+ * WG_CMD_CHANGED_ENDPOINT
+ * ----------------------
+ *
+ * This command is sent on the multicast group WG_MULTICAST_GROUP_PEERS
+ * when the endpoint of a peer is changed, either administratively or because
+ * of roaming.
+ * The kernel will send a single message containing the
+ * following tree of nested items:
+ *
+ *    WGDEVICE_A_IFINDEX: NLA_U32
+ *    WGDEVICE_A_IFNAME: NLA_NUL_STRING, maxlen IFNAMSIZ - 1
+ *    WGDEVICE_A_PEER: NLA_NESTED
+ *        WGPEER_A_PUBLIC_KEY: NLA_EXACT_LEN, len WG_KEY_LEN
+ *        WGPEER_A_ENDPOINT: NLA_MIN_LEN(struct sockaddr), struct sockaddr_in or struct sockaddr_in6
+ *
+ * WG_CMD_REMOVED_PEER
+ * -------------------
+ *
+ * This command is sent on the multicast group WG_MULTICAST_GROUP_PEERS
+ * when a peer is removed.
+ * The kernel will send a single message containing the
+ * following tree of nested items:
+ *
+ *    WGDEVICE_A_IFINDEX: NLA_U32
+ *    WGDEVICE_A_IFNAME: NLA_NUL_STRING, maxlen IFNAMSIZ - 1
+ *    WGDEVICE_A_PEER: NLA_NESTED
+ *        WGPEER_A_PUBLIC_KEY: NLA_EXACT_LEN, len WG_KEY_LEN
+ *
+ * WG_CMD_SET_PEER
+ * ---------------
+ *
+ * This command is sent on the multicast group WG_MULTICAST_GROUP_PEERS
+ * when a peer is added or changed.
+ * The kernel will send a single message containing the
+ * following tree of nested items:
+ *
+ *    WGDEVICE_A_IFINDEX: NLA_U32
+ *    WGDEVICE_A_IFNAME: NLA_NUL_STRING, maxlen IFNAMSIZ - 1
+ *    WGDEVICE_A_PEER: NLA_NESTED
+ *        WGPEER_A_PUBLIC_KEY: NLA_EXACT_LEN, len WG_KEY_LEN
+ *        WGPEER_A_ENDPOINT: NLA_MIN_LEN(struct sockaddr), struct sockaddr_in or struct sockaddr_in6
+ *        WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL: NLA_U16
+ *        WGPEER_A_ALLOWEDIPS: NLA_NESTED
+ *            0: NLA_NESTED
+ *                WGALLOWEDIP_A_FAMILY: NLA_U16
+ *                WGALLOWEDIP_A_IPADDR: NLA_MIN_LEN(struct in_addr), struct in_addr or struct in6_addr
+ *                WGALLOWEDIP_A_CIDR_MASK: NLA_U8
+ *            0: NLA_NESTED
+ *                ...
+ *            0: NLA_NESTED
+ *                ...
+ *
  */
 
 #ifndef _WG_UAPI_WIREGUARD_H
@@ -136,9 +193,14 @@
 
 #define WG_KEY_LEN 32
 
+#define WG_MULTICAST_GROUP_PEERS          "peers"
+
 enum wg_cmd {
 	WG_CMD_GET_DEVICE,
 	WG_CMD_SET_DEVICE,
+	WG_CMD_CHANGED_ENDPOINT,
+	WG_CMD_REMOVED_PEER,
+	WG_CMD_SET_PEER,
 	__WG_CMD_MAX
 };
 #define WG_CMD_MAX (__WG_CMD_MAX - 1)
@@ -157,9 +219,16 @@ enum wgdevice_attribute {
 	WGDEVICE_A_LISTEN_PORT,
 	WGDEVICE_A_FWMARK,
 	WGDEVICE_A_PEERS,
+	WGDEVICE_A_MONITOR,
+	WGDEVICE_A_PEER,
 	__WGDEVICE_A_LAST
 };
 #define WGDEVICE_A_MAX (__WGDEVICE_A_LAST - 1)
+enum wgdevice_monitor_flag {
+	WGDEVICE_MONITOR_F_ENDPOINT = 1U << 0,
+	WGDEVICE_MONITOR_F_PEERS = 1U << 1,
+	__WGDEVICE_MONITOR_F_ALL = WGDEVICE_MONITOR_F_ENDPOINT | WGDEVICE_MONITOR_F_PEERS
+};
 
 enum wgpeer_flag {
 	WGPEER_F_REMOVE_ME = 1U << 0,

base-commit: 0cf9deb3005f552a3d125436fc8ccedd31a925a9
-- 
2.42.0





             reply	other threads:[~2023-12-20  5:55 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-10-03 19:24 Raphael Catolino [this message]
2023-12-20 15:04 ` [PATCH 1/1] wireguard-linux: add netlink multicast group for notifications on peer change Daniel Gröber

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=2328185.xqv9EDfTUt@desktop \
    --to=rca@koalox.com \
    --cc=wireguard@lists.zx2c4.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.