[PATCH 1/1] wireguard-linux: add netlink multicast group for notifications on peer change
Raphael Catolino
rca at koalox.com
Tue Oct 3 19:24:18 UTC 2023
From: Linus Karl <linus at 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 at lotz.li>
Co-developed-by: Raphael Catolino <rca at koalox.com>
Signed-off-by: Raphael Catolino <rca at 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
More information about the WireGuard
mailing list