162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include "netlink.h"
762306a36Sopenharmony_ci#include "device.h"
862306a36Sopenharmony_ci#include "peer.h"
962306a36Sopenharmony_ci#include "socket.h"
1062306a36Sopenharmony_ci#include "queueing.h"
1162306a36Sopenharmony_ci#include "messages.h"
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <uapi/linux/wireguard.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <linux/if.h>
1662306a36Sopenharmony_ci#include <net/genetlink.h>
1762306a36Sopenharmony_ci#include <net/sock.h>
1862306a36Sopenharmony_ci#include <crypto/algapi.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistatic struct genl_family genl_family;
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistatic const struct nla_policy device_policy[WGDEVICE_A_MAX + 1] = {
2362306a36Sopenharmony_ci	[WGDEVICE_A_IFINDEX]		= { .type = NLA_U32 },
2462306a36Sopenharmony_ci	[WGDEVICE_A_IFNAME]		= { .type = NLA_NUL_STRING, .len = IFNAMSIZ - 1 },
2562306a36Sopenharmony_ci	[WGDEVICE_A_PRIVATE_KEY]	= NLA_POLICY_EXACT_LEN(NOISE_PUBLIC_KEY_LEN),
2662306a36Sopenharmony_ci	[WGDEVICE_A_PUBLIC_KEY]		= NLA_POLICY_EXACT_LEN(NOISE_PUBLIC_KEY_LEN),
2762306a36Sopenharmony_ci	[WGDEVICE_A_FLAGS]		= { .type = NLA_U32 },
2862306a36Sopenharmony_ci	[WGDEVICE_A_LISTEN_PORT]	= { .type = NLA_U16 },
2962306a36Sopenharmony_ci	[WGDEVICE_A_FWMARK]		= { .type = NLA_U32 },
3062306a36Sopenharmony_ci	[WGDEVICE_A_PEERS]		= { .type = NLA_NESTED }
3162306a36Sopenharmony_ci};
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic const struct nla_policy peer_policy[WGPEER_A_MAX + 1] = {
3462306a36Sopenharmony_ci	[WGPEER_A_PUBLIC_KEY]				= NLA_POLICY_EXACT_LEN(NOISE_PUBLIC_KEY_LEN),
3562306a36Sopenharmony_ci	[WGPEER_A_PRESHARED_KEY]			= NLA_POLICY_EXACT_LEN(NOISE_SYMMETRIC_KEY_LEN),
3662306a36Sopenharmony_ci	[WGPEER_A_FLAGS]				= { .type = NLA_U32 },
3762306a36Sopenharmony_ci	[WGPEER_A_ENDPOINT]				= NLA_POLICY_MIN_LEN(sizeof(struct sockaddr)),
3862306a36Sopenharmony_ci	[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]	= { .type = NLA_U16 },
3962306a36Sopenharmony_ci	[WGPEER_A_LAST_HANDSHAKE_TIME]			= NLA_POLICY_EXACT_LEN(sizeof(struct __kernel_timespec)),
4062306a36Sopenharmony_ci	[WGPEER_A_RX_BYTES]				= { .type = NLA_U64 },
4162306a36Sopenharmony_ci	[WGPEER_A_TX_BYTES]				= { .type = NLA_U64 },
4262306a36Sopenharmony_ci	[WGPEER_A_ALLOWEDIPS]				= { .type = NLA_NESTED },
4362306a36Sopenharmony_ci	[WGPEER_A_PROTOCOL_VERSION]			= { .type = NLA_U32 }
4462306a36Sopenharmony_ci};
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cistatic const struct nla_policy allowedip_policy[WGALLOWEDIP_A_MAX + 1] = {
4762306a36Sopenharmony_ci	[WGALLOWEDIP_A_FAMILY]		= { .type = NLA_U16 },
4862306a36Sopenharmony_ci	[WGALLOWEDIP_A_IPADDR]		= NLA_POLICY_MIN_LEN(sizeof(struct in_addr)),
4962306a36Sopenharmony_ci	[WGALLOWEDIP_A_CIDR_MASK]	= { .type = NLA_U8 }
5062306a36Sopenharmony_ci};
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic struct wg_device *lookup_interface(struct nlattr **attrs,
5362306a36Sopenharmony_ci					  struct sk_buff *skb)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	struct net_device *dev = NULL;
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	if (!attrs[WGDEVICE_A_IFINDEX] == !attrs[WGDEVICE_A_IFNAME])
5862306a36Sopenharmony_ci		return ERR_PTR(-EBADR);
5962306a36Sopenharmony_ci	if (attrs[WGDEVICE_A_IFINDEX])
6062306a36Sopenharmony_ci		dev = dev_get_by_index(sock_net(skb->sk),
6162306a36Sopenharmony_ci				       nla_get_u32(attrs[WGDEVICE_A_IFINDEX]));
6262306a36Sopenharmony_ci	else if (attrs[WGDEVICE_A_IFNAME])
6362306a36Sopenharmony_ci		dev = dev_get_by_name(sock_net(skb->sk),
6462306a36Sopenharmony_ci				      nla_data(attrs[WGDEVICE_A_IFNAME]));
6562306a36Sopenharmony_ci	if (!dev)
6662306a36Sopenharmony_ci		return ERR_PTR(-ENODEV);
6762306a36Sopenharmony_ci	if (!dev->rtnl_link_ops || !dev->rtnl_link_ops->kind ||
6862306a36Sopenharmony_ci	    strcmp(dev->rtnl_link_ops->kind, KBUILD_MODNAME)) {
6962306a36Sopenharmony_ci		dev_put(dev);
7062306a36Sopenharmony_ci		return ERR_PTR(-EOPNOTSUPP);
7162306a36Sopenharmony_ci	}
7262306a36Sopenharmony_ci	return netdev_priv(dev);
7362306a36Sopenharmony_ci}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_cistatic int get_allowedips(struct sk_buff *skb, const u8 *ip, u8 cidr,
7662306a36Sopenharmony_ci			  int family)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	struct nlattr *allowedip_nest;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	allowedip_nest = nla_nest_start(skb, 0);
8162306a36Sopenharmony_ci	if (!allowedip_nest)
8262306a36Sopenharmony_ci		return -EMSGSIZE;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	if (nla_put_u8(skb, WGALLOWEDIP_A_CIDR_MASK, cidr) ||
8562306a36Sopenharmony_ci	    nla_put_u16(skb, WGALLOWEDIP_A_FAMILY, family) ||
8662306a36Sopenharmony_ci	    nla_put(skb, WGALLOWEDIP_A_IPADDR, family == AF_INET6 ?
8762306a36Sopenharmony_ci		    sizeof(struct in6_addr) : sizeof(struct in_addr), ip)) {
8862306a36Sopenharmony_ci		nla_nest_cancel(skb, allowedip_nest);
8962306a36Sopenharmony_ci		return -EMSGSIZE;
9062306a36Sopenharmony_ci	}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	nla_nest_end(skb, allowedip_nest);
9362306a36Sopenharmony_ci	return 0;
9462306a36Sopenharmony_ci}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_cistruct dump_ctx {
9762306a36Sopenharmony_ci	struct wg_device *wg;
9862306a36Sopenharmony_ci	struct wg_peer *next_peer;
9962306a36Sopenharmony_ci	u64 allowedips_seq;
10062306a36Sopenharmony_ci	struct allowedips_node *next_allowedip;
10162306a36Sopenharmony_ci};
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci#define DUMP_CTX(cb) ((struct dump_ctx *)(cb)->args)
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_cistatic int
10662306a36Sopenharmony_ciget_peer(struct wg_peer *peer, struct sk_buff *skb, struct dump_ctx *ctx)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	struct nlattr *allowedips_nest, *peer_nest = nla_nest_start(skb, 0);
11062306a36Sopenharmony_ci	struct allowedips_node *allowedips_node = ctx->next_allowedip;
11162306a36Sopenharmony_ci	bool fail;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	if (!peer_nest)
11462306a36Sopenharmony_ci		return -EMSGSIZE;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	down_read(&peer->handshake.lock);
11762306a36Sopenharmony_ci	fail = nla_put(skb, WGPEER_A_PUBLIC_KEY, NOISE_PUBLIC_KEY_LEN,
11862306a36Sopenharmony_ci		       peer->handshake.remote_static);
11962306a36Sopenharmony_ci	up_read(&peer->handshake.lock);
12062306a36Sopenharmony_ci	if (fail)
12162306a36Sopenharmony_ci		goto err;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	if (!allowedips_node) {
12462306a36Sopenharmony_ci		const struct __kernel_timespec last_handshake = {
12562306a36Sopenharmony_ci			.tv_sec = peer->walltime_last_handshake.tv_sec,
12662306a36Sopenharmony_ci			.tv_nsec = peer->walltime_last_handshake.tv_nsec
12762306a36Sopenharmony_ci		};
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci		down_read(&peer->handshake.lock);
13062306a36Sopenharmony_ci		fail = nla_put(skb, WGPEER_A_PRESHARED_KEY,
13162306a36Sopenharmony_ci			       NOISE_SYMMETRIC_KEY_LEN,
13262306a36Sopenharmony_ci			       peer->handshake.preshared_key);
13362306a36Sopenharmony_ci		up_read(&peer->handshake.lock);
13462306a36Sopenharmony_ci		if (fail)
13562306a36Sopenharmony_ci			goto err;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci		if (nla_put(skb, WGPEER_A_LAST_HANDSHAKE_TIME,
13862306a36Sopenharmony_ci			    sizeof(last_handshake), &last_handshake) ||
13962306a36Sopenharmony_ci		    nla_put_u16(skb, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL,
14062306a36Sopenharmony_ci				peer->persistent_keepalive_interval) ||
14162306a36Sopenharmony_ci		    nla_put_u64_64bit(skb, WGPEER_A_TX_BYTES, peer->tx_bytes,
14262306a36Sopenharmony_ci				      WGPEER_A_UNSPEC) ||
14362306a36Sopenharmony_ci		    nla_put_u64_64bit(skb, WGPEER_A_RX_BYTES, peer->rx_bytes,
14462306a36Sopenharmony_ci				      WGPEER_A_UNSPEC) ||
14562306a36Sopenharmony_ci		    nla_put_u32(skb, WGPEER_A_PROTOCOL_VERSION, 1))
14662306a36Sopenharmony_ci			goto err;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci		read_lock_bh(&peer->endpoint_lock);
14962306a36Sopenharmony_ci		if (peer->endpoint.addr.sa_family == AF_INET)
15062306a36Sopenharmony_ci			fail = nla_put(skb, WGPEER_A_ENDPOINT,
15162306a36Sopenharmony_ci				       sizeof(peer->endpoint.addr4),
15262306a36Sopenharmony_ci				       &peer->endpoint.addr4);
15362306a36Sopenharmony_ci		else if (peer->endpoint.addr.sa_family == AF_INET6)
15462306a36Sopenharmony_ci			fail = nla_put(skb, WGPEER_A_ENDPOINT,
15562306a36Sopenharmony_ci				       sizeof(peer->endpoint.addr6),
15662306a36Sopenharmony_ci				       &peer->endpoint.addr6);
15762306a36Sopenharmony_ci		read_unlock_bh(&peer->endpoint_lock);
15862306a36Sopenharmony_ci		if (fail)
15962306a36Sopenharmony_ci			goto err;
16062306a36Sopenharmony_ci		allowedips_node =
16162306a36Sopenharmony_ci			list_first_entry_or_null(&peer->allowedips_list,
16262306a36Sopenharmony_ci					struct allowedips_node, peer_list);
16362306a36Sopenharmony_ci	}
16462306a36Sopenharmony_ci	if (!allowedips_node)
16562306a36Sopenharmony_ci		goto no_allowedips;
16662306a36Sopenharmony_ci	if (!ctx->allowedips_seq)
16762306a36Sopenharmony_ci		ctx->allowedips_seq = peer->device->peer_allowedips.seq;
16862306a36Sopenharmony_ci	else if (ctx->allowedips_seq != peer->device->peer_allowedips.seq)
16962306a36Sopenharmony_ci		goto no_allowedips;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	allowedips_nest = nla_nest_start(skb, WGPEER_A_ALLOWEDIPS);
17262306a36Sopenharmony_ci	if (!allowedips_nest)
17362306a36Sopenharmony_ci		goto err;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	list_for_each_entry_from(allowedips_node, &peer->allowedips_list,
17662306a36Sopenharmony_ci				 peer_list) {
17762306a36Sopenharmony_ci		u8 cidr, ip[16] __aligned(__alignof(u64));
17862306a36Sopenharmony_ci		int family;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci		family = wg_allowedips_read_node(allowedips_node, ip, &cidr);
18162306a36Sopenharmony_ci		if (get_allowedips(skb, ip, cidr, family)) {
18262306a36Sopenharmony_ci			nla_nest_end(skb, allowedips_nest);
18362306a36Sopenharmony_ci			nla_nest_end(skb, peer_nest);
18462306a36Sopenharmony_ci			ctx->next_allowedip = allowedips_node;
18562306a36Sopenharmony_ci			return -EMSGSIZE;
18662306a36Sopenharmony_ci		}
18762306a36Sopenharmony_ci	}
18862306a36Sopenharmony_ci	nla_nest_end(skb, allowedips_nest);
18962306a36Sopenharmony_cino_allowedips:
19062306a36Sopenharmony_ci	nla_nest_end(skb, peer_nest);
19162306a36Sopenharmony_ci	ctx->next_allowedip = NULL;
19262306a36Sopenharmony_ci	ctx->allowedips_seq = 0;
19362306a36Sopenharmony_ci	return 0;
19462306a36Sopenharmony_cierr:
19562306a36Sopenharmony_ci	nla_nest_cancel(skb, peer_nest);
19662306a36Sopenharmony_ci	return -EMSGSIZE;
19762306a36Sopenharmony_ci}
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_cistatic int wg_get_device_start(struct netlink_callback *cb)
20062306a36Sopenharmony_ci{
20162306a36Sopenharmony_ci	struct wg_device *wg;
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	wg = lookup_interface(genl_info_dump(cb)->attrs, cb->skb);
20462306a36Sopenharmony_ci	if (IS_ERR(wg))
20562306a36Sopenharmony_ci		return PTR_ERR(wg);
20662306a36Sopenharmony_ci	DUMP_CTX(cb)->wg = wg;
20762306a36Sopenharmony_ci	return 0;
20862306a36Sopenharmony_ci}
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_cistatic int wg_get_device_dump(struct sk_buff *skb, struct netlink_callback *cb)
21162306a36Sopenharmony_ci{
21262306a36Sopenharmony_ci	struct wg_peer *peer, *next_peer_cursor;
21362306a36Sopenharmony_ci	struct dump_ctx *ctx = DUMP_CTX(cb);
21462306a36Sopenharmony_ci	struct wg_device *wg = ctx->wg;
21562306a36Sopenharmony_ci	struct nlattr *peers_nest;
21662306a36Sopenharmony_ci	int ret = -EMSGSIZE;
21762306a36Sopenharmony_ci	bool done = true;
21862306a36Sopenharmony_ci	void *hdr;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	rtnl_lock();
22162306a36Sopenharmony_ci	mutex_lock(&wg->device_update_lock);
22262306a36Sopenharmony_ci	cb->seq = wg->device_update_gen;
22362306a36Sopenharmony_ci	next_peer_cursor = ctx->next_peer;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq,
22662306a36Sopenharmony_ci			  &genl_family, NLM_F_MULTI, WG_CMD_GET_DEVICE);
22762306a36Sopenharmony_ci	if (!hdr)
22862306a36Sopenharmony_ci		goto out;
22962306a36Sopenharmony_ci	genl_dump_check_consistent(cb, hdr);
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	if (!ctx->next_peer) {
23262306a36Sopenharmony_ci		if (nla_put_u16(skb, WGDEVICE_A_LISTEN_PORT,
23362306a36Sopenharmony_ci				wg->incoming_port) ||
23462306a36Sopenharmony_ci		    nla_put_u32(skb, WGDEVICE_A_FWMARK, wg->fwmark) ||
23562306a36Sopenharmony_ci		    nla_put_u32(skb, WGDEVICE_A_IFINDEX, wg->dev->ifindex) ||
23662306a36Sopenharmony_ci		    nla_put_string(skb, WGDEVICE_A_IFNAME, wg->dev->name))
23762306a36Sopenharmony_ci			goto out;
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci		down_read(&wg->static_identity.lock);
24062306a36Sopenharmony_ci		if (wg->static_identity.has_identity) {
24162306a36Sopenharmony_ci			if (nla_put(skb, WGDEVICE_A_PRIVATE_KEY,
24262306a36Sopenharmony_ci				    NOISE_PUBLIC_KEY_LEN,
24362306a36Sopenharmony_ci				    wg->static_identity.static_private) ||
24462306a36Sopenharmony_ci			    nla_put(skb, WGDEVICE_A_PUBLIC_KEY,
24562306a36Sopenharmony_ci				    NOISE_PUBLIC_KEY_LEN,
24662306a36Sopenharmony_ci				    wg->static_identity.static_public)) {
24762306a36Sopenharmony_ci				up_read(&wg->static_identity.lock);
24862306a36Sopenharmony_ci				goto out;
24962306a36Sopenharmony_ci			}
25062306a36Sopenharmony_ci		}
25162306a36Sopenharmony_ci		up_read(&wg->static_identity.lock);
25262306a36Sopenharmony_ci	}
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	peers_nest = nla_nest_start(skb, WGDEVICE_A_PEERS);
25562306a36Sopenharmony_ci	if (!peers_nest)
25662306a36Sopenharmony_ci		goto out;
25762306a36Sopenharmony_ci	ret = 0;
25862306a36Sopenharmony_ci	/* If the last cursor was removed via list_del_init in peer_remove, then
25962306a36Sopenharmony_ci	 * we just treat this the same as there being no more peers left. The
26062306a36Sopenharmony_ci	 * reason is that seq_nr should indicate to userspace that this isn't a
26162306a36Sopenharmony_ci	 * coherent dump anyway, so they'll try again.
26262306a36Sopenharmony_ci	 */
26362306a36Sopenharmony_ci	if (list_empty(&wg->peer_list) ||
26462306a36Sopenharmony_ci	    (ctx->next_peer && list_empty(&ctx->next_peer->peer_list))) {
26562306a36Sopenharmony_ci		nla_nest_cancel(skb, peers_nest);
26662306a36Sopenharmony_ci		goto out;
26762306a36Sopenharmony_ci	}
26862306a36Sopenharmony_ci	lockdep_assert_held(&wg->device_update_lock);
26962306a36Sopenharmony_ci	peer = list_prepare_entry(ctx->next_peer, &wg->peer_list, peer_list);
27062306a36Sopenharmony_ci	list_for_each_entry_continue(peer, &wg->peer_list, peer_list) {
27162306a36Sopenharmony_ci		if (get_peer(peer, skb, ctx)) {
27262306a36Sopenharmony_ci			done = false;
27362306a36Sopenharmony_ci			break;
27462306a36Sopenharmony_ci		}
27562306a36Sopenharmony_ci		next_peer_cursor = peer;
27662306a36Sopenharmony_ci	}
27762306a36Sopenharmony_ci	nla_nest_end(skb, peers_nest);
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ciout:
28062306a36Sopenharmony_ci	if (!ret && !done && next_peer_cursor)
28162306a36Sopenharmony_ci		wg_peer_get(next_peer_cursor);
28262306a36Sopenharmony_ci	wg_peer_put(ctx->next_peer);
28362306a36Sopenharmony_ci	mutex_unlock(&wg->device_update_lock);
28462306a36Sopenharmony_ci	rtnl_unlock();
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	if (ret) {
28762306a36Sopenharmony_ci		genlmsg_cancel(skb, hdr);
28862306a36Sopenharmony_ci		return ret;
28962306a36Sopenharmony_ci	}
29062306a36Sopenharmony_ci	genlmsg_end(skb, hdr);
29162306a36Sopenharmony_ci	if (done) {
29262306a36Sopenharmony_ci		ctx->next_peer = NULL;
29362306a36Sopenharmony_ci		return 0;
29462306a36Sopenharmony_ci	}
29562306a36Sopenharmony_ci	ctx->next_peer = next_peer_cursor;
29662306a36Sopenharmony_ci	return skb->len;
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	/* At this point, we can't really deal ourselves with safely zeroing out
29962306a36Sopenharmony_ci	 * the private key material after usage. This will need an additional API
30062306a36Sopenharmony_ci	 * in the kernel for marking skbs as zero_on_free.
30162306a36Sopenharmony_ci	 */
30262306a36Sopenharmony_ci}
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_cistatic int wg_get_device_done(struct netlink_callback *cb)
30562306a36Sopenharmony_ci{
30662306a36Sopenharmony_ci	struct dump_ctx *ctx = DUMP_CTX(cb);
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	if (ctx->wg)
30962306a36Sopenharmony_ci		dev_put(ctx->wg->dev);
31062306a36Sopenharmony_ci	wg_peer_put(ctx->next_peer);
31162306a36Sopenharmony_ci	return 0;
31262306a36Sopenharmony_ci}
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_cistatic int set_port(struct wg_device *wg, u16 port)
31562306a36Sopenharmony_ci{
31662306a36Sopenharmony_ci	struct wg_peer *peer;
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	if (wg->incoming_port == port)
31962306a36Sopenharmony_ci		return 0;
32062306a36Sopenharmony_ci	list_for_each_entry(peer, &wg->peer_list, peer_list)
32162306a36Sopenharmony_ci		wg_socket_clear_peer_endpoint_src(peer);
32262306a36Sopenharmony_ci	if (!netif_running(wg->dev)) {
32362306a36Sopenharmony_ci		wg->incoming_port = port;
32462306a36Sopenharmony_ci		return 0;
32562306a36Sopenharmony_ci	}
32662306a36Sopenharmony_ci	return wg_socket_init(wg, port);
32762306a36Sopenharmony_ci}
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_cistatic int set_allowedip(struct wg_peer *peer, struct nlattr **attrs)
33062306a36Sopenharmony_ci{
33162306a36Sopenharmony_ci	int ret = -EINVAL;
33262306a36Sopenharmony_ci	u16 family;
33362306a36Sopenharmony_ci	u8 cidr;
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	if (!attrs[WGALLOWEDIP_A_FAMILY] || !attrs[WGALLOWEDIP_A_IPADDR] ||
33662306a36Sopenharmony_ci	    !attrs[WGALLOWEDIP_A_CIDR_MASK])
33762306a36Sopenharmony_ci		return ret;
33862306a36Sopenharmony_ci	family = nla_get_u16(attrs[WGALLOWEDIP_A_FAMILY]);
33962306a36Sopenharmony_ci	cidr = nla_get_u8(attrs[WGALLOWEDIP_A_CIDR_MASK]);
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci	if (family == AF_INET && cidr <= 32 &&
34262306a36Sopenharmony_ci	    nla_len(attrs[WGALLOWEDIP_A_IPADDR]) == sizeof(struct in_addr))
34362306a36Sopenharmony_ci		ret = wg_allowedips_insert_v4(
34462306a36Sopenharmony_ci			&peer->device->peer_allowedips,
34562306a36Sopenharmony_ci			nla_data(attrs[WGALLOWEDIP_A_IPADDR]), cidr, peer,
34662306a36Sopenharmony_ci			&peer->device->device_update_lock);
34762306a36Sopenharmony_ci	else if (family == AF_INET6 && cidr <= 128 &&
34862306a36Sopenharmony_ci		 nla_len(attrs[WGALLOWEDIP_A_IPADDR]) == sizeof(struct in6_addr))
34962306a36Sopenharmony_ci		ret = wg_allowedips_insert_v6(
35062306a36Sopenharmony_ci			&peer->device->peer_allowedips,
35162306a36Sopenharmony_ci			nla_data(attrs[WGALLOWEDIP_A_IPADDR]), cidr, peer,
35262306a36Sopenharmony_ci			&peer->device->device_update_lock);
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci	return ret;
35562306a36Sopenharmony_ci}
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_cistatic int set_peer(struct wg_device *wg, struct nlattr **attrs)
35862306a36Sopenharmony_ci{
35962306a36Sopenharmony_ci	u8 *public_key = NULL, *preshared_key = NULL;
36062306a36Sopenharmony_ci	struct wg_peer *peer = NULL;
36162306a36Sopenharmony_ci	u32 flags = 0;
36262306a36Sopenharmony_ci	int ret;
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	ret = -EINVAL;
36562306a36Sopenharmony_ci	if (attrs[WGPEER_A_PUBLIC_KEY] &&
36662306a36Sopenharmony_ci	    nla_len(attrs[WGPEER_A_PUBLIC_KEY]) == NOISE_PUBLIC_KEY_LEN)
36762306a36Sopenharmony_ci		public_key = nla_data(attrs[WGPEER_A_PUBLIC_KEY]);
36862306a36Sopenharmony_ci	else
36962306a36Sopenharmony_ci		goto out;
37062306a36Sopenharmony_ci	if (attrs[WGPEER_A_PRESHARED_KEY] &&
37162306a36Sopenharmony_ci	    nla_len(attrs[WGPEER_A_PRESHARED_KEY]) == NOISE_SYMMETRIC_KEY_LEN)
37262306a36Sopenharmony_ci		preshared_key = nla_data(attrs[WGPEER_A_PRESHARED_KEY]);
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	if (attrs[WGPEER_A_FLAGS])
37562306a36Sopenharmony_ci		flags = nla_get_u32(attrs[WGPEER_A_FLAGS]);
37662306a36Sopenharmony_ci	ret = -EOPNOTSUPP;
37762306a36Sopenharmony_ci	if (flags & ~__WGPEER_F_ALL)
37862306a36Sopenharmony_ci		goto out;
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci	ret = -EPFNOSUPPORT;
38162306a36Sopenharmony_ci	if (attrs[WGPEER_A_PROTOCOL_VERSION]) {
38262306a36Sopenharmony_ci		if (nla_get_u32(attrs[WGPEER_A_PROTOCOL_VERSION]) != 1)
38362306a36Sopenharmony_ci			goto out;
38462306a36Sopenharmony_ci	}
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci	peer = wg_pubkey_hashtable_lookup(wg->peer_hashtable,
38762306a36Sopenharmony_ci					  nla_data(attrs[WGPEER_A_PUBLIC_KEY]));
38862306a36Sopenharmony_ci	ret = 0;
38962306a36Sopenharmony_ci	if (!peer) { /* Peer doesn't exist yet. Add a new one. */
39062306a36Sopenharmony_ci		if (flags & (WGPEER_F_REMOVE_ME | WGPEER_F_UPDATE_ONLY))
39162306a36Sopenharmony_ci			goto out;
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci		/* The peer is new, so there aren't allowed IPs to remove. */
39462306a36Sopenharmony_ci		flags &= ~WGPEER_F_REPLACE_ALLOWEDIPS;
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci		down_read(&wg->static_identity.lock);
39762306a36Sopenharmony_ci		if (wg->static_identity.has_identity &&
39862306a36Sopenharmony_ci		    !memcmp(nla_data(attrs[WGPEER_A_PUBLIC_KEY]),
39962306a36Sopenharmony_ci			    wg->static_identity.static_public,
40062306a36Sopenharmony_ci			    NOISE_PUBLIC_KEY_LEN)) {
40162306a36Sopenharmony_ci			/* We silently ignore peers that have the same public
40262306a36Sopenharmony_ci			 * key as the device. The reason we do it silently is
40362306a36Sopenharmony_ci			 * that we'd like for people to be able to reuse the
40462306a36Sopenharmony_ci			 * same set of API calls across peers.
40562306a36Sopenharmony_ci			 */
40662306a36Sopenharmony_ci			up_read(&wg->static_identity.lock);
40762306a36Sopenharmony_ci			ret = 0;
40862306a36Sopenharmony_ci			goto out;
40962306a36Sopenharmony_ci		}
41062306a36Sopenharmony_ci		up_read(&wg->static_identity.lock);
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci		peer = wg_peer_create(wg, public_key, preshared_key);
41362306a36Sopenharmony_ci		if (IS_ERR(peer)) {
41462306a36Sopenharmony_ci			ret = PTR_ERR(peer);
41562306a36Sopenharmony_ci			peer = NULL;
41662306a36Sopenharmony_ci			goto out;
41762306a36Sopenharmony_ci		}
41862306a36Sopenharmony_ci		/* Take additional reference, as though we've just been
41962306a36Sopenharmony_ci		 * looked up.
42062306a36Sopenharmony_ci		 */
42162306a36Sopenharmony_ci		wg_peer_get(peer);
42262306a36Sopenharmony_ci	}
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci	if (flags & WGPEER_F_REMOVE_ME) {
42562306a36Sopenharmony_ci		wg_peer_remove(peer);
42662306a36Sopenharmony_ci		goto out;
42762306a36Sopenharmony_ci	}
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci	if (preshared_key) {
43062306a36Sopenharmony_ci		down_write(&peer->handshake.lock);
43162306a36Sopenharmony_ci		memcpy(&peer->handshake.preshared_key, preshared_key,
43262306a36Sopenharmony_ci		       NOISE_SYMMETRIC_KEY_LEN);
43362306a36Sopenharmony_ci		up_write(&peer->handshake.lock);
43462306a36Sopenharmony_ci	}
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci	if (attrs[WGPEER_A_ENDPOINT]) {
43762306a36Sopenharmony_ci		struct sockaddr *addr = nla_data(attrs[WGPEER_A_ENDPOINT]);
43862306a36Sopenharmony_ci		size_t len = nla_len(attrs[WGPEER_A_ENDPOINT]);
43962306a36Sopenharmony_ci		struct endpoint endpoint = { { { 0 } } };
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci		if (len == sizeof(struct sockaddr_in) && addr->sa_family == AF_INET) {
44262306a36Sopenharmony_ci			endpoint.addr4 = *(struct sockaddr_in *)addr;
44362306a36Sopenharmony_ci			wg_socket_set_peer_endpoint(peer, &endpoint);
44462306a36Sopenharmony_ci		} else if (len == sizeof(struct sockaddr_in6) && addr->sa_family == AF_INET6) {
44562306a36Sopenharmony_ci			endpoint.addr6 = *(struct sockaddr_in6 *)addr;
44662306a36Sopenharmony_ci			wg_socket_set_peer_endpoint(peer, &endpoint);
44762306a36Sopenharmony_ci		}
44862306a36Sopenharmony_ci	}
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci	if (flags & WGPEER_F_REPLACE_ALLOWEDIPS)
45162306a36Sopenharmony_ci		wg_allowedips_remove_by_peer(&wg->peer_allowedips, peer,
45262306a36Sopenharmony_ci					     &wg->device_update_lock);
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ci	if (attrs[WGPEER_A_ALLOWEDIPS]) {
45562306a36Sopenharmony_ci		struct nlattr *attr, *allowedip[WGALLOWEDIP_A_MAX + 1];
45662306a36Sopenharmony_ci		int rem;
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_ci		nla_for_each_nested(attr, attrs[WGPEER_A_ALLOWEDIPS], rem) {
45962306a36Sopenharmony_ci			ret = nla_parse_nested(allowedip, WGALLOWEDIP_A_MAX,
46062306a36Sopenharmony_ci					       attr, allowedip_policy, NULL);
46162306a36Sopenharmony_ci			if (ret < 0)
46262306a36Sopenharmony_ci				goto out;
46362306a36Sopenharmony_ci			ret = set_allowedip(peer, allowedip);
46462306a36Sopenharmony_ci			if (ret < 0)
46562306a36Sopenharmony_ci				goto out;
46662306a36Sopenharmony_ci		}
46762306a36Sopenharmony_ci	}
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci	if (attrs[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]) {
47062306a36Sopenharmony_ci		const u16 persistent_keepalive_interval = nla_get_u16(
47162306a36Sopenharmony_ci				attrs[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]);
47262306a36Sopenharmony_ci		const bool send_keepalive =
47362306a36Sopenharmony_ci			!peer->persistent_keepalive_interval &&
47462306a36Sopenharmony_ci			persistent_keepalive_interval &&
47562306a36Sopenharmony_ci			netif_running(wg->dev);
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci		peer->persistent_keepalive_interval = persistent_keepalive_interval;
47862306a36Sopenharmony_ci		if (send_keepalive)
47962306a36Sopenharmony_ci			wg_packet_send_keepalive(peer);
48062306a36Sopenharmony_ci	}
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci	if (netif_running(wg->dev))
48362306a36Sopenharmony_ci		wg_packet_send_staged_packets(peer);
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ciout:
48662306a36Sopenharmony_ci	wg_peer_put(peer);
48762306a36Sopenharmony_ci	if (attrs[WGPEER_A_PRESHARED_KEY])
48862306a36Sopenharmony_ci		memzero_explicit(nla_data(attrs[WGPEER_A_PRESHARED_KEY]),
48962306a36Sopenharmony_ci				 nla_len(attrs[WGPEER_A_PRESHARED_KEY]));
49062306a36Sopenharmony_ci	return ret;
49162306a36Sopenharmony_ci}
49262306a36Sopenharmony_ci
49362306a36Sopenharmony_cistatic int wg_set_device(struct sk_buff *skb, struct genl_info *info)
49462306a36Sopenharmony_ci{
49562306a36Sopenharmony_ci	struct wg_device *wg = lookup_interface(info->attrs, skb);
49662306a36Sopenharmony_ci	u32 flags = 0;
49762306a36Sopenharmony_ci	int ret;
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci	if (IS_ERR(wg)) {
50062306a36Sopenharmony_ci		ret = PTR_ERR(wg);
50162306a36Sopenharmony_ci		goto out_nodev;
50262306a36Sopenharmony_ci	}
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_ci	rtnl_lock();
50562306a36Sopenharmony_ci	mutex_lock(&wg->device_update_lock);
50662306a36Sopenharmony_ci
50762306a36Sopenharmony_ci	if (info->attrs[WGDEVICE_A_FLAGS])
50862306a36Sopenharmony_ci		flags = nla_get_u32(info->attrs[WGDEVICE_A_FLAGS]);
50962306a36Sopenharmony_ci	ret = -EOPNOTSUPP;
51062306a36Sopenharmony_ci	if (flags & ~__WGDEVICE_F_ALL)
51162306a36Sopenharmony_ci		goto out;
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci	if (info->attrs[WGDEVICE_A_LISTEN_PORT] || info->attrs[WGDEVICE_A_FWMARK]) {
51462306a36Sopenharmony_ci		struct net *net;
51562306a36Sopenharmony_ci		rcu_read_lock();
51662306a36Sopenharmony_ci		net = rcu_dereference(wg->creating_net);
51762306a36Sopenharmony_ci		ret = !net || !ns_capable(net->user_ns, CAP_NET_ADMIN) ? -EPERM : 0;
51862306a36Sopenharmony_ci		rcu_read_unlock();
51962306a36Sopenharmony_ci		if (ret)
52062306a36Sopenharmony_ci			goto out;
52162306a36Sopenharmony_ci	}
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_ci	++wg->device_update_gen;
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_ci	if (info->attrs[WGDEVICE_A_FWMARK]) {
52662306a36Sopenharmony_ci		struct wg_peer *peer;
52762306a36Sopenharmony_ci
52862306a36Sopenharmony_ci		wg->fwmark = nla_get_u32(info->attrs[WGDEVICE_A_FWMARK]);
52962306a36Sopenharmony_ci		list_for_each_entry(peer, &wg->peer_list, peer_list)
53062306a36Sopenharmony_ci			wg_socket_clear_peer_endpoint_src(peer);
53162306a36Sopenharmony_ci	}
53262306a36Sopenharmony_ci
53362306a36Sopenharmony_ci	if (info->attrs[WGDEVICE_A_LISTEN_PORT]) {
53462306a36Sopenharmony_ci		ret = set_port(wg,
53562306a36Sopenharmony_ci			nla_get_u16(info->attrs[WGDEVICE_A_LISTEN_PORT]));
53662306a36Sopenharmony_ci		if (ret)
53762306a36Sopenharmony_ci			goto out;
53862306a36Sopenharmony_ci	}
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci	if (flags & WGDEVICE_F_REPLACE_PEERS)
54162306a36Sopenharmony_ci		wg_peer_remove_all(wg);
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci	if (info->attrs[WGDEVICE_A_PRIVATE_KEY] &&
54462306a36Sopenharmony_ci	    nla_len(info->attrs[WGDEVICE_A_PRIVATE_KEY]) ==
54562306a36Sopenharmony_ci		    NOISE_PUBLIC_KEY_LEN) {
54662306a36Sopenharmony_ci		u8 *private_key = nla_data(info->attrs[WGDEVICE_A_PRIVATE_KEY]);
54762306a36Sopenharmony_ci		u8 public_key[NOISE_PUBLIC_KEY_LEN];
54862306a36Sopenharmony_ci		struct wg_peer *peer, *temp;
54962306a36Sopenharmony_ci		bool send_staged_packets;
55062306a36Sopenharmony_ci
55162306a36Sopenharmony_ci		if (!crypto_memneq(wg->static_identity.static_private,
55262306a36Sopenharmony_ci				   private_key, NOISE_PUBLIC_KEY_LEN))
55362306a36Sopenharmony_ci			goto skip_set_private_key;
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_ci		/* We remove before setting, to prevent race, which means doing
55662306a36Sopenharmony_ci		 * two 25519-genpub ops.
55762306a36Sopenharmony_ci		 */
55862306a36Sopenharmony_ci		if (curve25519_generate_public(public_key, private_key)) {
55962306a36Sopenharmony_ci			peer = wg_pubkey_hashtable_lookup(wg->peer_hashtable,
56062306a36Sopenharmony_ci							  public_key);
56162306a36Sopenharmony_ci			if (peer) {
56262306a36Sopenharmony_ci				wg_peer_put(peer);
56362306a36Sopenharmony_ci				wg_peer_remove(peer);
56462306a36Sopenharmony_ci			}
56562306a36Sopenharmony_ci		}
56662306a36Sopenharmony_ci
56762306a36Sopenharmony_ci		down_write(&wg->static_identity.lock);
56862306a36Sopenharmony_ci		send_staged_packets = !wg->static_identity.has_identity && netif_running(wg->dev);
56962306a36Sopenharmony_ci		wg_noise_set_static_identity_private_key(&wg->static_identity, private_key);
57062306a36Sopenharmony_ci		send_staged_packets = send_staged_packets && wg->static_identity.has_identity;
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_ci		wg_cookie_checker_precompute_device_keys(&wg->cookie_checker);
57362306a36Sopenharmony_ci		list_for_each_entry_safe(peer, temp, &wg->peer_list, peer_list) {
57462306a36Sopenharmony_ci			wg_noise_precompute_static_static(peer);
57562306a36Sopenharmony_ci			wg_noise_expire_current_peer_keypairs(peer);
57662306a36Sopenharmony_ci			if (send_staged_packets)
57762306a36Sopenharmony_ci				wg_packet_send_staged_packets(peer);
57862306a36Sopenharmony_ci		}
57962306a36Sopenharmony_ci		up_write(&wg->static_identity.lock);
58062306a36Sopenharmony_ci	}
58162306a36Sopenharmony_ciskip_set_private_key:
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_ci	if (info->attrs[WGDEVICE_A_PEERS]) {
58462306a36Sopenharmony_ci		struct nlattr *attr, *peer[WGPEER_A_MAX + 1];
58562306a36Sopenharmony_ci		int rem;
58662306a36Sopenharmony_ci
58762306a36Sopenharmony_ci		nla_for_each_nested(attr, info->attrs[WGDEVICE_A_PEERS], rem) {
58862306a36Sopenharmony_ci			ret = nla_parse_nested(peer, WGPEER_A_MAX, attr,
58962306a36Sopenharmony_ci					       peer_policy, NULL);
59062306a36Sopenharmony_ci			if (ret < 0)
59162306a36Sopenharmony_ci				goto out;
59262306a36Sopenharmony_ci			ret = set_peer(wg, peer);
59362306a36Sopenharmony_ci			if (ret < 0)
59462306a36Sopenharmony_ci				goto out;
59562306a36Sopenharmony_ci		}
59662306a36Sopenharmony_ci	}
59762306a36Sopenharmony_ci	ret = 0;
59862306a36Sopenharmony_ci
59962306a36Sopenharmony_ciout:
60062306a36Sopenharmony_ci	mutex_unlock(&wg->device_update_lock);
60162306a36Sopenharmony_ci	rtnl_unlock();
60262306a36Sopenharmony_ci	dev_put(wg->dev);
60362306a36Sopenharmony_ciout_nodev:
60462306a36Sopenharmony_ci	if (info->attrs[WGDEVICE_A_PRIVATE_KEY])
60562306a36Sopenharmony_ci		memzero_explicit(nla_data(info->attrs[WGDEVICE_A_PRIVATE_KEY]),
60662306a36Sopenharmony_ci				 nla_len(info->attrs[WGDEVICE_A_PRIVATE_KEY]));
60762306a36Sopenharmony_ci	return ret;
60862306a36Sopenharmony_ci}
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_cistatic const struct genl_ops genl_ops[] = {
61162306a36Sopenharmony_ci	{
61262306a36Sopenharmony_ci		.cmd = WG_CMD_GET_DEVICE,
61362306a36Sopenharmony_ci		.start = wg_get_device_start,
61462306a36Sopenharmony_ci		.dumpit = wg_get_device_dump,
61562306a36Sopenharmony_ci		.done = wg_get_device_done,
61662306a36Sopenharmony_ci		.flags = GENL_UNS_ADMIN_PERM
61762306a36Sopenharmony_ci	}, {
61862306a36Sopenharmony_ci		.cmd = WG_CMD_SET_DEVICE,
61962306a36Sopenharmony_ci		.doit = wg_set_device,
62062306a36Sopenharmony_ci		.flags = GENL_UNS_ADMIN_PERM
62162306a36Sopenharmony_ci	}
62262306a36Sopenharmony_ci};
62362306a36Sopenharmony_ci
62462306a36Sopenharmony_cistatic struct genl_family genl_family __ro_after_init = {
62562306a36Sopenharmony_ci	.ops = genl_ops,
62662306a36Sopenharmony_ci	.n_ops = ARRAY_SIZE(genl_ops),
62762306a36Sopenharmony_ci	.resv_start_op = WG_CMD_SET_DEVICE + 1,
62862306a36Sopenharmony_ci	.name = WG_GENL_NAME,
62962306a36Sopenharmony_ci	.version = WG_GENL_VERSION,
63062306a36Sopenharmony_ci	.maxattr = WGDEVICE_A_MAX,
63162306a36Sopenharmony_ci	.module = THIS_MODULE,
63262306a36Sopenharmony_ci	.policy = device_policy,
63362306a36Sopenharmony_ci	.netnsok = true
63462306a36Sopenharmony_ci};
63562306a36Sopenharmony_ci
63662306a36Sopenharmony_ciint __init wg_genetlink_init(void)
63762306a36Sopenharmony_ci{
63862306a36Sopenharmony_ci	return genl_register_family(&genl_family);
63962306a36Sopenharmony_ci}
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_civoid __exit wg_genetlink_uninit(void)
64262306a36Sopenharmony_ci{
64362306a36Sopenharmony_ci	genl_unregister_family(&genl_family);
64462306a36Sopenharmony_ci}
645