wireguard: Add netlink attrs for binding to address and netdev

Message ID 20231023160006.85992-1-dxld@darkboxed.org
State New
Headers
Series wireguard: Add netlink attrs for binding to address and netdev |

Commit Message

Daniel Gröber Oct. 23, 2023, 4 p.m. UTC
  Multihomed hosts may want to run distinct wg tunnels across all their
uplinks for redundant connectivity. Currently this entails picking
different ports for each wg tunnel since we allow only binding to the
wildcard address. Sharing a single port-number for all uplink
connections (but bound to a particular IP/netdev) simplifies managment
considerably.

A closely related use-case that also touches the socket binding code is
having a wg socket be part of a VRF. This mirrors how we support socket and
wg device in distinct namespaces. To make using VRFs with wg easy we want
to be able to bind to a particular device as this will cause the kernel to
automatically route all outgoing packets with the VRF's routing table
and (in the default udp_l3mdev_accept=0 config) only accept packets from
interfaces in the VRF without the need for netfilter rules.

While users can currently use VRFs for wg tunnel traffic by configuring
fwmark ip-rules and setting sysctl udp_l3mdev_accept=1 (with or without
additional nft filtering) this is at best a cludge. When VRF membership
changes it becomes a major hassle to keep ip-rules up to date.

Signed-off-by: Daniel Gröber <dxld@darkboxed.org>
---
 drivers/net/wireguard/device.c  |  4 +--
 drivers/net/wireguard/device.h  |  3 +-
 drivers/net/wireguard/netlink.c | 51 ++++++++++++++++++++++++++++-----
 drivers/net/wireguard/socket.c  | 41 +++++++++++++++++---------
 drivers/net/wireguard/socket.h  |  3 +-
 include/uapi/linux/wireguard.h  |  6 ++++
 6 files changed, 83 insertions(+), 25 deletions(-)
  

Comments

kernel test robot Oct. 24, 2023, 6:43 p.m. UTC | #1
Hi Daniel,

kernel test robot noticed the following build errors:

[auto build test ERROR on linus/master]
[also build test ERROR on v6.6-rc7 next-20231024]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Daniel-Gr-ber/wireguard-Add-netlink-attrs-for-binding-to-address-and-netdev/20231024-000158
base:   linus/master
patch link:    https://lore.kernel.org/r/20231023160006.85992-1-dxld%40darkboxed.org
patch subject: [PATCH] wireguard: Add netlink attrs for binding to address and netdev
config: um-randconfig-002-20231024 (https://download.01.org/0day-ci/archive/20231025/202310250256.kXWoLwJm-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.2.0-14) 12.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231025/202310250256.kXWoLwJm-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202310250256.kXWoLwJm-lkp@intel.com/

All errors (new ones prefixed by >>):

   drivers/net/wireguard/netlink.c: In function 'wg_get_device_dump':
>> drivers/net/wireguard/netlink.c:247:52: error: 'struct udp_port_cfg' has no member named 'local_ip6'; did you mean 'local_ip'?
     247 |                                      &wg->port_cfg.local_ip6))
         |                                                    ^~~~~~~~~
         |                                                    local_ip
   drivers/net/wireguard/netlink.c: In function 'set_port_cfg':
   drivers/net/wireguard/netlink.c:348:34: error: 'struct udp_port_cfg' has no member named 'local_ip6'; did you mean 'local_ip'?
     348 |                         port_cfg.local_ip6 = u_addr->addr6;
         |                                  ^~~~~~~~~
         |                                  local_ip


vim +247 drivers/net/wireguard/netlink.c

   211	
   212	static int wg_get_device_dump(struct sk_buff *skb, struct netlink_callback *cb)
   213	{
   214		struct wg_peer *peer, *next_peer_cursor;
   215		struct dump_ctx *ctx = DUMP_CTX(cb);
   216		struct wg_device *wg = ctx->wg;
   217		struct nlattr *peers_nest;
   218		int ret = -EMSGSIZE;
   219		bool done = true;
   220		void *hdr;
   221	
   222		rtnl_lock();
   223		mutex_lock(&wg->device_update_lock);
   224		cb->seq = wg->device_update_gen;
   225		next_peer_cursor = ctx->next_peer;
   226	
   227		hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq,
   228				  &genl_family, NLM_F_MULTI, WG_CMD_GET_DEVICE);
   229		if (!hdr)
   230			goto out;
   231		genl_dump_check_consistent(cb, hdr);
   232	
   233		if (!ctx->next_peer) {
   234			if (nla_put_u16(skb, WGDEVICE_A_LISTEN_PORT,
   235					ntohs(wg->port_cfg.local_udp_port)) ||
   236			    nla_put_u32(skb, WGDEVICE_A_LISTEN_IFINDEX, wg->port_cfg.bind_ifindex) ||
   237			    nla_put_u32(skb, WGDEVICE_A_FWMARK, wg->fwmark) ||
   238			    nla_put_u32(skb, WGDEVICE_A_IFINDEX, wg->dev->ifindex) ||
   239			    nla_put_string(skb, WGDEVICE_A_IFNAME, wg->dev->name))
   240				goto out;
   241		        if (wg->port_cfg.family == AF_INET &&
   242			    nla_put_in_addr(skb, WGDEVICE_A_LISTEN_ADDR,
   243					    wg->port_cfg.local_ip.s_addr))
   244					goto out;
   245		        if (wg->port_cfg.family == AF_INET6 &&
   246			    nla_put_in6_addr(skb, WGDEVICE_A_LISTEN_ADDR,
 > 247					     &wg->port_cfg.local_ip6))
   248					goto out;
   249	
   250			down_read(&wg->static_identity.lock);
   251			if (wg->static_identity.has_identity) {
   252				if (nla_put(skb, WGDEVICE_A_PRIVATE_KEY,
   253					    NOISE_PUBLIC_KEY_LEN,
   254					    wg->static_identity.static_private) ||
   255				    nla_put(skb, WGDEVICE_A_PUBLIC_KEY,
   256					    NOISE_PUBLIC_KEY_LEN,
   257					    wg->static_identity.static_public)) {
   258					up_read(&wg->static_identity.lock);
   259					goto out;
   260				}
   261			}
   262			up_read(&wg->static_identity.lock);
   263		}
   264	
   265		peers_nest = nla_nest_start(skb, WGDEVICE_A_PEERS);
   266		if (!peers_nest)
   267			goto out;
   268		ret = 0;
   269		/* If the last cursor was removed via list_del_init in peer_remove, then
   270		 * we just treat this the same as there being no more peers left. The
   271		 * reason is that seq_nr should indicate to userspace that this isn't a
   272		 * coherent dump anyway, so they'll try again.
   273		 */
   274		if (list_empty(&wg->peer_list) ||
   275		    (ctx->next_peer && list_empty(&ctx->next_peer->peer_list))) {
   276			nla_nest_cancel(skb, peers_nest);
   277			goto out;
   278		}
   279		lockdep_assert_held(&wg->device_update_lock);
   280		peer = list_prepare_entry(ctx->next_peer, &wg->peer_list, peer_list);
   281		list_for_each_entry_continue(peer, &wg->peer_list, peer_list) {
   282			if (get_peer(peer, skb, ctx)) {
   283				done = false;
   284				break;
   285			}
   286			next_peer_cursor = peer;
   287		}
   288		nla_nest_end(skb, peers_nest);
   289	
   290	out:
   291		if (!ret && !done && next_peer_cursor)
   292			wg_peer_get(next_peer_cursor);
   293		wg_peer_put(ctx->next_peer);
   294		mutex_unlock(&wg->device_update_lock);
   295		rtnl_unlock();
   296	
   297		if (ret) {
   298			genlmsg_cancel(skb, hdr);
   299			return ret;
   300		}
   301		genlmsg_end(skb, hdr);
   302		if (done) {
   303			ctx->next_peer = NULL;
   304			return 0;
   305		}
   306		ctx->next_peer = next_peer_cursor;
   307		return skb->len;
   308	
   309		/* At this point, we can't really deal ourselves with safely zeroing out
   310		 * the private key material after usage. This will need an additional API
   311		 * in the kernel for marking skbs as zero_on_free.
   312		 */
   313	}
   314
  

Patch

diff --git a/drivers/net/wireguard/device.c b/drivers/net/wireguard/device.c
index 258dcc103921..fdaaf0238a49 100644
--- a/drivers/net/wireguard/device.c
+++ b/drivers/net/wireguard/device.c
@@ -48,7 +48,7 @@  static int wg_open(struct net_device *dev)
 		dev_v6->cnf.addr_gen_mode = IN6_ADDR_GEN_MODE_NONE;
 
 	mutex_lock(&wg->device_update_lock);
-	ret = wg_socket_init(wg, wg->incoming_port);
+	ret = wg_socket_init(wg, wg->port_cfg);
 	if (ret < 0)
 		goto out;
 	list_for_each_entry(peer, &wg->peer_list, peer_list) {
@@ -249,7 +249,7 @@  static void wg_destruct(struct net_device *dev)
 	rtnl_unlock();
 	mutex_lock(&wg->device_update_lock);
 	rcu_assign_pointer(wg->creating_net, NULL);
-	wg->incoming_port = 0;
+	memzero_explicit(&wg->port_cfg, sizeof(wg->port_cfg));
 	wg_socket_reinit(wg, NULL, NULL);
 	/* The final references are cleared in the below calls to destroy_workqueue. */
 	wg_peer_remove_all(wg);
diff --git a/drivers/net/wireguard/device.h b/drivers/net/wireguard/device.h
index 43c7cebbf50b..ac4092d8c9d0 100644
--- a/drivers/net/wireguard/device.h
+++ b/drivers/net/wireguard/device.h
@@ -17,6 +17,7 @@ 
 #include <linux/mutex.h>
 #include <linux/net.h>
 #include <linux/ptr_ring.h>
+#include <net/udp_tunnel.h>
 
 struct wg_device;
 
@@ -53,7 +54,7 @@  struct wg_device {
 	atomic_t handshake_queue_len;
 	unsigned int num_peers, device_update_gen;
 	u32 fwmark;
-	u16 incoming_port;
+	struct udp_port_cfg port_cfg;
 };
 
 int wg_device_init(void);
diff --git a/drivers/net/wireguard/netlink.c b/drivers/net/wireguard/netlink.c
index dc09b75a3248..28e5b8668e72 100644
--- a/drivers/net/wireguard/netlink.c
+++ b/drivers/net/wireguard/netlink.c
@@ -26,6 +26,8 @@  static const struct nla_policy device_policy[WGDEVICE_A_MAX + 1] = {
 	[WGDEVICE_A_PUBLIC_KEY]		= NLA_POLICY_EXACT_LEN(NOISE_PUBLIC_KEY_LEN),
 	[WGDEVICE_A_FLAGS]		= { .type = NLA_U32 },
 	[WGDEVICE_A_LISTEN_PORT]	= { .type = NLA_U16 },
+	[WGDEVICE_A_LISTEN_ADDR]	= NLA_POLICY_MIN_LEN(sizeof(struct in_addr)),
+	[WGDEVICE_A_LISTEN_IFINDEX]	= { .type = NLA_U32 },
 	[WGDEVICE_A_FWMARK]		= { .type = NLA_U32 },
 	[WGDEVICE_A_PEERS]		= { .type = NLA_NESTED }
 };
@@ -230,11 +232,20 @@  static int wg_get_device_dump(struct sk_buff *skb, struct netlink_callback *cb)
 
 	if (!ctx->next_peer) {
 		if (nla_put_u16(skb, WGDEVICE_A_LISTEN_PORT,
-				wg->incoming_port) ||
+				ntohs(wg->port_cfg.local_udp_port)) ||
+		    nla_put_u32(skb, WGDEVICE_A_LISTEN_IFINDEX, wg->port_cfg.bind_ifindex) ||
 		    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))
 			goto out;
+	        if (wg->port_cfg.family == AF_INET &&
+		    nla_put_in_addr(skb, WGDEVICE_A_LISTEN_ADDR,
+				    wg->port_cfg.local_ip.s_addr))
+				goto out;
+	        if (wg->port_cfg.family == AF_INET6 &&
+		    nla_put_in6_addr(skb, WGDEVICE_A_LISTEN_ADDR,
+				     &wg->port_cfg.local_ip6))
+				goto out;
 
 		down_read(&wg->static_identity.lock);
 		if (wg->static_identity.has_identity) {
@@ -311,19 +322,46 @@  static int wg_get_device_done(struct netlink_callback *cb)
 	return 0;
 }
 
-static int set_port(struct wg_device *wg, u16 port)
+static int set_port_cfg(struct wg_device *wg, struct nlattr **attrs)
 {
 	struct wg_peer *peer;
+	struct udp_port_cfg port_cfg = {
+		.family = AF_UNSPEC,
+	};
+
+	if (attrs[WGDEVICE_A_LISTEN_PORT])
+		port_cfg.local_udp_port =
+			htons(nla_get_u16(attrs[WGDEVICE_A_LISTEN_PORT]));
+	if (attrs[WGDEVICE_A_LISTEN_ADDR]) {
+		union {
+			struct in_addr addr4;
+			struct in6_addr addr6;
+		} *u_addr = nla_data(attrs[WGDEVICE_A_LISTEN_ADDR]);
+		size_t len = nla_len(attrs[WGDEVICE_A_LISTEN_ADDR]);
+		if (len == sizeof(struct in_addr)) {
+			port_cfg.family = AF_INET;
+			port_cfg.local_ip = u_addr->addr4;
+		} else if (len == sizeof(struct in6_addr)) {
+			if (!IS_ENABLED(CONFIG_IPV6))
+				return -EAFNOSUPPORT;
+			port_cfg.family = AF_INET6;
+			port_cfg.local_ip6 = u_addr->addr6;
+		}
+	}
+	if (attrs[WGDEVICE_A_LISTEN_IFINDEX]) {
+		port_cfg.bind_ifindex =
+			nla_get_u32(attrs[WGDEVICE_A_LISTEN_IFINDEX]);
+	}
 
-	if (wg->incoming_port == port)
+	if (memcmp(&port_cfg, &wg->port_cfg, sizeof(port_cfg)) == 0)
 		return 0;
 	list_for_each_entry(peer, &wg->peer_list, peer_list)
 		wg_socket_clear_peer_endpoint_src(peer);
 	if (!netif_running(wg->dev)) {
-		wg->incoming_port = port;
+		wg->port_cfg = port_cfg;
 		return 0;
 	}
-	return wg_socket_init(wg, port);
+	return wg_socket_init(wg, port_cfg);
 }
 
 static int set_allowedip(struct wg_peer *peer, struct nlattr **attrs)
@@ -531,8 +569,7 @@  static int wg_set_device(struct sk_buff *skb, struct genl_info *info)
 	}
 
 	if (info->attrs[WGDEVICE_A_LISTEN_PORT]) {
-		ret = set_port(wg,
-			nla_get_u16(info->attrs[WGDEVICE_A_LISTEN_PORT]));
+		ret = set_port_cfg(wg, info->attrs);
 		if (ret)
 			goto out;
 	}
diff --git a/drivers/net/wireguard/socket.c b/drivers/net/wireguard/socket.c
index c35163f503e7..cbd3958af890 100644
--- a/drivers/net/wireguard/socket.c
+++ b/drivers/net/wireguard/socket.c
@@ -346,7 +346,7 @@  static void set_sock_opts(struct socket *sock)
 	sk_set_memalloc(sock->sk);
 }
 
-int wg_socket_init(struct wg_device *wg, u16 port)
+int wg_socket_init(struct wg_device *wg, struct udp_port_cfg port_cfg)
 {
 	struct net *net;
 	int ret;
@@ -356,12 +356,7 @@  int wg_socket_init(struct wg_device *wg, u16 port)
 		.encap_rcv = wg_receive
 	};
 	struct socket *new4 = NULL, *new6 = NULL;
-	struct udp_port_cfg port4 = {
-		.family = AF_INET,
-		.local_ip.s_addr = htonl(INADDR_ANY),
-		.local_udp_port = htons(port),
-		.use_udp_checksums = true
-	};
+	struct udp_port_cfg port4;
 #if IS_ENABLED(CONFIG_IPV6)
 	int retries = 0;
 	struct udp_port_cfg port6 = {
@@ -373,6 +368,23 @@  int wg_socket_init(struct wg_device *wg, u16 port)
 	};
 #endif
 
+	if (port_cfg.family == AF_UNSPEC) {
+		port4 = (struct udp_port_cfg) {
+			.family = AF_INET,
+			.local_ip.s_addr = htonl(INADDR_ANY),
+			.local_udp_port = port_cfg.local_udp_port,
+			.use_udp_checksums = true
+		};
+	} else {
+		port4 = port_cfg;
+		port4.use_udp_checksums = true;
+		if (IS_ENABLED(CONFIG_IPV6) && port_cfg.family == AF_INET6) {
+			port4.use_udp6_tx_checksums = true;
+			port4.use_udp6_rx_checksums = true;
+			port4.ipv6_v6only = true;
+		}
+	}
+
 	rcu_read_lock();
 	net = rcu_dereference(wg->creating_net);
 	net = net ? maybe_get_net(net) : NULL;
@@ -380,10 +392,6 @@  int wg_socket_init(struct wg_device *wg, u16 port)
 	if (unlikely(!net))
 		return -ENONET;
 
-#if IS_ENABLED(CONFIG_IPV6)
-retry:
-#endif
-
 	ret = udp_sock_create(net, &port4, &new4);
 	if (ret < 0) {
 		pr_err("%s: Could not create IPv4 socket\n", wg->dev->name);
@@ -392,13 +400,18 @@  int wg_socket_init(struct wg_device *wg, u16 port)
 	set_sock_opts(new4);
 	setup_udp_tunnel_sock(net, new4, &cfg);
 
+	if (port_cfg.family != AF_UNSPEC)
+		goto reinit;
+
 #if IS_ENABLED(CONFIG_IPV6)
+retry:
 	if (ipv6_mod_enabled()) {
 		port6.local_udp_port = inet_sk(new4->sk)->inet_sport;
 		ret = udp_sock_create(net, &port6, &new6);
 		if (ret < 0) {
 			udp_tunnel_sock_release(new4);
-			if (ret == -EADDRINUSE && !port && retries++ < 100)
+			if (ret == -EADDRINUSE && !port_cfg.local_udp_port &&
+			    retries++ < 100)
 				goto retry;
 			pr_err("%s: Could not create IPv6 socket\n",
 			       wg->dev->name);
@@ -409,6 +422,8 @@  int wg_socket_init(struct wg_device *wg, u16 port)
 	}
 #endif
 
+reinit:
+	wg->port_cfg = port_cfg;
 	wg_socket_reinit(wg, new4->sk, new6 ? new6->sk : NULL);
 	ret = 0;
 out:
@@ -433,8 +448,6 @@  void wg_socket_reinit(struct wg_device *wg, struct sock *new4,
 				lockdep_is_held(&wg->socket_update_lock));
 	rcu_assign_pointer(wg->sock4, new4);
 	rcu_assign_pointer(wg->sock6, new6);
-	if (new4)
-		wg->incoming_port = ntohs(inet_sk(new4)->inet_sport);
 	mutex_unlock(&wg->socket_update_lock);
 	synchronize_net();
 	sock_free(old4);
diff --git a/drivers/net/wireguard/socket.h b/drivers/net/wireguard/socket.h
index bab5848efbcd..1532a263c518 100644
--- a/drivers/net/wireguard/socket.h
+++ b/drivers/net/wireguard/socket.h
@@ -10,8 +10,9 @@ 
 #include <linux/udp.h>
 #include <linux/if_vlan.h>
 #include <linux/if_ether.h>
+#include <net/udp_tunnel.h>
 
-int wg_socket_init(struct wg_device *wg, u16 port);
+int wg_socket_init(struct wg_device *wg, struct udp_port_cfg port);
 void wg_socket_reinit(struct wg_device *wg, struct sock *new4,
 		      struct sock *new6);
 int wg_socket_send_buffer_to_peer(struct wg_peer *peer, void *data,
diff --git a/include/uapi/linux/wireguard.h b/include/uapi/linux/wireguard.h
index ae88be14c947..240d1c850dfd 100644
--- a/include/uapi/linux/wireguard.h
+++ b/include/uapi/linux/wireguard.h
@@ -28,6 +28,8 @@ 
  *    WGDEVICE_A_PRIVATE_KEY: NLA_EXACT_LEN, len WG_KEY_LEN
  *    WGDEVICE_A_PUBLIC_KEY: NLA_EXACT_LEN, len WG_KEY_LEN
  *    WGDEVICE_A_LISTEN_PORT: NLA_U16
+ *    WGDEVICE_A_LISTEN_ADDR : NLA_MIN_LEN(struct sockaddr), struct sockaddr_in or struct sockaddr_in6
+ *    WGDEVICE_A_LISTEN_IFINDEX : NLA_U32
  *    WGDEVICE_A_FWMARK: NLA_U32
  *    WGDEVICE_A_PEERS: NLA_NESTED
  *        0: NLA_NESTED
@@ -82,6 +84,8 @@ 
  *                      peers should be removed prior to adding the list below.
  *    WGDEVICE_A_PRIVATE_KEY: len WG_KEY_LEN, all zeros to remove
  *    WGDEVICE_A_LISTEN_PORT: NLA_U16, 0 to choose randomly
+ *    WGDEVICE_A_LISTEN_ADDR : struct sockaddr_in or struct sockaddr_in6.
+ *    WGDEVICE_A_LISTEN_IFINDEX : NLA_U32
  *    WGDEVICE_A_FWMARK: NLA_U32, 0 to disable
  *    WGDEVICE_A_PEERS: NLA_NESTED
  *        0: NLA_NESTED
@@ -157,6 +161,8 @@  enum wgdevice_attribute {
 	WGDEVICE_A_LISTEN_PORT,
 	WGDEVICE_A_FWMARK,
 	WGDEVICE_A_PEERS,
+	WGDEVICE_A_LISTEN_ADDR,
+	WGDEVICE_A_LISTEN_IFINDEX,
 	__WGDEVICE_A_LAST
 };
 #define WGDEVICE_A_MAX (__WGDEVICE_A_LAST - 1)