1adf37648SKyle Evans // SPDX-License-Identifier: MIT
2adf37648SKyle Evans /*
3adf37648SKyle Evans * Copyright (C) 2015-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
4adf37648SKyle Evans *
5adf37648SKyle Evans */
6adf37648SKyle Evans
7*2cb43631SKyle Evans #include <assert.h>
8adf37648SKyle Evans #include <sys/nv.h>
9adf37648SKyle Evans #include <sys/sockio.h>
10adf37648SKyle Evans #include <dev/wg/if_wg.h>
11adf37648SKyle Evans
12adf37648SKyle Evans #define IPC_SUPPORTS_KERNEL_INTERFACE
13adf37648SKyle Evans
get_dgram_socket(void)14adf37648SKyle Evans static int get_dgram_socket(void)
15adf37648SKyle Evans {
16adf37648SKyle Evans static int sock = -1;
17adf37648SKyle Evans if (sock < 0)
18adf37648SKyle Evans sock = socket(AF_INET, SOCK_DGRAM, 0);
19adf37648SKyle Evans return sock;
20adf37648SKyle Evans }
21adf37648SKyle Evans
kernel_get_wireguard_interfaces(struct string_list * list)22adf37648SKyle Evans static int kernel_get_wireguard_interfaces(struct string_list *list)
23adf37648SKyle Evans {
24adf37648SKyle Evans struct ifgroupreq ifgr = { .ifgr_name = "wg" };
25adf37648SKyle Evans struct ifg_req *ifg;
26adf37648SKyle Evans int s = get_dgram_socket(), ret = 0;
27adf37648SKyle Evans
28adf37648SKyle Evans if (s < 0)
29adf37648SKyle Evans return -errno;
30adf37648SKyle Evans
31adf37648SKyle Evans if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) < 0)
32adf37648SKyle Evans return errno == ENOENT ? 0 : -errno;
33adf37648SKyle Evans
34adf37648SKyle Evans ifgr.ifgr_groups = calloc(1, ifgr.ifgr_len);
35adf37648SKyle Evans if (!ifgr.ifgr_groups)
36adf37648SKyle Evans return -errno;
37adf37648SKyle Evans if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) < 0) {
38adf37648SKyle Evans ret = -errno;
39adf37648SKyle Evans goto out;
40adf37648SKyle Evans }
41adf37648SKyle Evans
42adf37648SKyle Evans for (ifg = ifgr.ifgr_groups; ifg && ifgr.ifgr_len > 0; ++ifg) {
43adf37648SKyle Evans if ((ret = string_list_add(list, ifg->ifgrq_member)) < 0)
44adf37648SKyle Evans goto out;
45adf37648SKyle Evans ifgr.ifgr_len -= sizeof(struct ifg_req);
46adf37648SKyle Evans }
47adf37648SKyle Evans
48adf37648SKyle Evans out:
49adf37648SKyle Evans free(ifgr.ifgr_groups);
50adf37648SKyle Evans return ret;
51adf37648SKyle Evans }
52adf37648SKyle Evans
kernel_get_device(struct wgdevice ** device,const char * ifname)53adf37648SKyle Evans static int kernel_get_device(struct wgdevice **device, const char *ifname)
54adf37648SKyle Evans {
55adf37648SKyle Evans struct wg_data_io wgd = { 0 };
56adf37648SKyle Evans nvlist_t *nvl_device = NULL;
57adf37648SKyle Evans const nvlist_t *const *nvl_peers;
58adf37648SKyle Evans struct wgdevice *dev = NULL;
59adf37648SKyle Evans size_t size, peer_count, i;
60adf37648SKyle Evans uint64_t number;
61adf37648SKyle Evans const void *binary;
62adf37648SKyle Evans int ret = 0, s;
63adf37648SKyle Evans
64adf37648SKyle Evans *device = NULL;
65adf37648SKyle Evans s = get_dgram_socket();
66adf37648SKyle Evans if (s < 0)
67adf37648SKyle Evans goto err;
68adf37648SKyle Evans
69adf37648SKyle Evans strlcpy(wgd.wgd_name, ifname, sizeof(wgd.wgd_name));
70adf37648SKyle Evans if (ioctl(s, SIOCGWG, &wgd) < 0)
71adf37648SKyle Evans goto err;
72adf37648SKyle Evans
73adf37648SKyle Evans wgd.wgd_data = malloc(wgd.wgd_size);
74adf37648SKyle Evans if (!wgd.wgd_data)
75adf37648SKyle Evans goto err;
76adf37648SKyle Evans if (ioctl(s, SIOCGWG, &wgd) < 0)
77adf37648SKyle Evans goto err;
78adf37648SKyle Evans
79adf37648SKyle Evans dev = calloc(1, sizeof(*dev));
80adf37648SKyle Evans if (!dev)
81adf37648SKyle Evans goto err;
82adf37648SKyle Evans strlcpy(dev->name, ifname, sizeof(dev->name));
83adf37648SKyle Evans nvl_device = nvlist_unpack(wgd.wgd_data, wgd.wgd_size, 0);
84adf37648SKyle Evans if (!nvl_device)
85adf37648SKyle Evans goto err;
86adf37648SKyle Evans
87adf37648SKyle Evans if (nvlist_exists_number(nvl_device, "listen-port")) {
88adf37648SKyle Evans number = nvlist_get_number(nvl_device, "listen-port");
89adf37648SKyle Evans if (number <= UINT16_MAX) {
90adf37648SKyle Evans dev->listen_port = number;
91adf37648SKyle Evans dev->flags |= WGDEVICE_HAS_LISTEN_PORT;
92adf37648SKyle Evans }
93adf37648SKyle Evans }
94adf37648SKyle Evans if (nvlist_exists_number(nvl_device, "user-cookie")) {
95adf37648SKyle Evans number = nvlist_get_number(nvl_device, "user-cookie");
96adf37648SKyle Evans if (number <= UINT32_MAX) {
97adf37648SKyle Evans dev->fwmark = number;
98adf37648SKyle Evans dev->flags |= WGDEVICE_HAS_FWMARK;
99adf37648SKyle Evans }
100adf37648SKyle Evans }
101adf37648SKyle Evans if (nvlist_exists_binary(nvl_device, "public-key")) {
102adf37648SKyle Evans binary = nvlist_get_binary(nvl_device, "public-key", &size);
103adf37648SKyle Evans if (binary && size == sizeof(dev->public_key)) {
104adf37648SKyle Evans memcpy(dev->public_key, binary, sizeof(dev->public_key));
105adf37648SKyle Evans dev->flags |= WGDEVICE_HAS_PUBLIC_KEY;
106adf37648SKyle Evans }
107adf37648SKyle Evans }
108adf37648SKyle Evans if (nvlist_exists_binary(nvl_device, "private-key")) {
109adf37648SKyle Evans binary = nvlist_get_binary(nvl_device, "private-key", &size);
110adf37648SKyle Evans if (binary && size == sizeof(dev->private_key)) {
111adf37648SKyle Evans memcpy(dev->private_key, binary, sizeof(dev->private_key));
112adf37648SKyle Evans dev->flags |= WGDEVICE_HAS_PRIVATE_KEY;
113adf37648SKyle Evans }
114adf37648SKyle Evans }
115adf37648SKyle Evans if (!nvlist_exists_nvlist_array(nvl_device, "peers"))
116adf37648SKyle Evans goto skip_peers;
117adf37648SKyle Evans nvl_peers = nvlist_get_nvlist_array(nvl_device, "peers", &peer_count);
118adf37648SKyle Evans if (!nvl_peers)
119adf37648SKyle Evans goto skip_peers;
120adf37648SKyle Evans for (i = 0; i < peer_count; ++i) {
121adf37648SKyle Evans struct wgpeer *peer;
122*2cb43631SKyle Evans struct wgallowedip *aip = NULL;
123adf37648SKyle Evans const nvlist_t *const *nvl_aips;
124adf37648SKyle Evans size_t aip_count, j;
125adf37648SKyle Evans
126adf37648SKyle Evans peer = calloc(1, sizeof(*peer));
127adf37648SKyle Evans if (!peer)
128adf37648SKyle Evans goto err_peer;
129adf37648SKyle Evans if (nvlist_exists_binary(nvl_peers[i], "public-key")) {
130adf37648SKyle Evans binary = nvlist_get_binary(nvl_peers[i], "public-key", &size);
131adf37648SKyle Evans if (binary && size == sizeof(peer->public_key)) {
132adf37648SKyle Evans memcpy(peer->public_key, binary, sizeof(peer->public_key));
133adf37648SKyle Evans peer->flags |= WGPEER_HAS_PUBLIC_KEY;
134adf37648SKyle Evans }
135adf37648SKyle Evans }
136adf37648SKyle Evans if (nvlist_exists_binary(nvl_peers[i], "preshared-key")) {
137adf37648SKyle Evans binary = nvlist_get_binary(nvl_peers[i], "preshared-key", &size);
138adf37648SKyle Evans if (binary && size == sizeof(peer->preshared_key)) {
139adf37648SKyle Evans memcpy(peer->preshared_key, binary, sizeof(peer->preshared_key));
140adf37648SKyle Evans if (!key_is_zero(peer->preshared_key))
141adf37648SKyle Evans peer->flags |= WGPEER_HAS_PRESHARED_KEY;
142adf37648SKyle Evans }
143adf37648SKyle Evans }
144adf37648SKyle Evans if (nvlist_exists_number(nvl_peers[i], "persistent-keepalive-interval")) {
145adf37648SKyle Evans number = nvlist_get_number(nvl_peers[i], "persistent-keepalive-interval");
146adf37648SKyle Evans if (number <= UINT16_MAX) {
147adf37648SKyle Evans peer->persistent_keepalive_interval = number;
148adf37648SKyle Evans peer->flags |= WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL;
149adf37648SKyle Evans }
150adf37648SKyle Evans }
151adf37648SKyle Evans if (nvlist_exists_binary(nvl_peers[i], "endpoint")) {
152adf37648SKyle Evans const struct sockaddr *endpoint = nvlist_get_binary(nvl_peers[i], "endpoint", &size);
153adf37648SKyle Evans if (endpoint && size <= sizeof(peer->endpoint) && size >= sizeof(peer->endpoint.addr) &&
154adf37648SKyle Evans (endpoint->sa_family == AF_INET || endpoint->sa_family == AF_INET6))
155adf37648SKyle Evans memcpy(&peer->endpoint.addr, endpoint, size);
156adf37648SKyle Evans }
157adf37648SKyle Evans if (nvlist_exists_number(nvl_peers[i], "rx-bytes"))
158adf37648SKyle Evans peer->rx_bytes = nvlist_get_number(nvl_peers[i], "rx-bytes");
159adf37648SKyle Evans if (nvlist_exists_number(nvl_peers[i], "tx-bytes"))
160adf37648SKyle Evans peer->tx_bytes = nvlist_get_number(nvl_peers[i], "tx-bytes");
161adf37648SKyle Evans if (nvlist_exists_binary(nvl_peers[i], "last-handshake-time")) {
162adf37648SKyle Evans binary = nvlist_get_binary(nvl_peers[i], "last-handshake-time", &size);
163adf37648SKyle Evans if (binary && size == sizeof(peer->last_handshake_time))
164adf37648SKyle Evans memcpy(&peer->last_handshake_time, binary, sizeof(peer->last_handshake_time));
165adf37648SKyle Evans }
166adf37648SKyle Evans
167adf37648SKyle Evans if (!nvlist_exists_nvlist_array(nvl_peers[i], "allowed-ips"))
168adf37648SKyle Evans goto skip_allowed_ips;
169adf37648SKyle Evans nvl_aips = nvlist_get_nvlist_array(nvl_peers[i], "allowed-ips", &aip_count);
170adf37648SKyle Evans if (!aip_count || !nvl_aips)
171adf37648SKyle Evans goto skip_allowed_ips;
172adf37648SKyle Evans for (j = 0; j < aip_count; ++j) {
173*2cb43631SKyle Evans if (!nvlist_exists_number(nvl_aips[j], "cidr"))
174*2cb43631SKyle Evans continue;
175*2cb43631SKyle Evans if (!nvlist_exists_binary(nvl_aips[j], "ipv4") && !nvlist_exists_binary(nvl_aips[j], "ipv6"))
176*2cb43631SKyle Evans continue;
177adf37648SKyle Evans aip = calloc(1, sizeof(*aip));
178adf37648SKyle Evans if (!aip)
179adf37648SKyle Evans goto err_allowed_ips;
180adf37648SKyle Evans number = nvlist_get_number(nvl_aips[j], "cidr");
181adf37648SKyle Evans if (nvlist_exists_binary(nvl_aips[j], "ipv4")) {
182adf37648SKyle Evans binary = nvlist_get_binary(nvl_aips[j], "ipv4", &size);
183adf37648SKyle Evans if (!binary || number > 32) {
184adf37648SKyle Evans ret = EINVAL;
185adf37648SKyle Evans goto err_allowed_ips;
186adf37648SKyle Evans }
187adf37648SKyle Evans aip->family = AF_INET;
188adf37648SKyle Evans aip->cidr = number;
189adf37648SKyle Evans memcpy(&aip->ip4, binary, sizeof(aip->ip4));
190*2cb43631SKyle Evans } else {
191*2cb43631SKyle Evans assert(nvlist_exists_binary(nvl_aips[j], "ipv6"));
192adf37648SKyle Evans binary = nvlist_get_binary(nvl_aips[j], "ipv6", &size);
193adf37648SKyle Evans if (!binary || number > 128) {
194adf37648SKyle Evans ret = EINVAL;
195adf37648SKyle Evans goto err_allowed_ips;
196adf37648SKyle Evans }
197adf37648SKyle Evans aip->family = AF_INET6;
198adf37648SKyle Evans aip->cidr = number;
199adf37648SKyle Evans memcpy(&aip->ip6, binary, sizeof(aip->ip6));
200*2cb43631SKyle Evans }
201adf37648SKyle Evans
202adf37648SKyle Evans if (!peer->first_allowedip)
203adf37648SKyle Evans peer->first_allowedip = aip;
204adf37648SKyle Evans else
205adf37648SKyle Evans peer->last_allowedip->next_allowedip = aip;
206adf37648SKyle Evans peer->last_allowedip = aip;
207*2cb43631SKyle Evans aip = NULL;
208adf37648SKyle Evans continue;
209adf37648SKyle Evans
210adf37648SKyle Evans err_allowed_ips:
211adf37648SKyle Evans if (!ret)
212adf37648SKyle Evans ret = -errno;
213adf37648SKyle Evans free(aip);
214adf37648SKyle Evans goto err_peer;
215adf37648SKyle Evans }
216*2cb43631SKyle Evans
217*2cb43631SKyle Evans /* Nothing leaked, hopefully -- ownership transferred or aip freed. */
218*2cb43631SKyle Evans assert(aip == NULL);
219adf37648SKyle Evans skip_allowed_ips:
220adf37648SKyle Evans if (!dev->first_peer)
221adf37648SKyle Evans dev->first_peer = peer;
222adf37648SKyle Evans else
223adf37648SKyle Evans dev->last_peer->next_peer = peer;
224adf37648SKyle Evans dev->last_peer = peer;
225adf37648SKyle Evans continue;
226adf37648SKyle Evans
227adf37648SKyle Evans err_peer:
228adf37648SKyle Evans if (!ret)
229adf37648SKyle Evans ret = -errno;
230adf37648SKyle Evans free(peer);
231adf37648SKyle Evans goto err;
232adf37648SKyle Evans }
233adf37648SKyle Evans
234adf37648SKyle Evans skip_peers:
235adf37648SKyle Evans free(wgd.wgd_data);
236adf37648SKyle Evans nvlist_destroy(nvl_device);
237adf37648SKyle Evans *device = dev;
238adf37648SKyle Evans return 0;
239adf37648SKyle Evans
240adf37648SKyle Evans err:
241adf37648SKyle Evans if (!ret)
242adf37648SKyle Evans ret = -errno;
243adf37648SKyle Evans free(wgd.wgd_data);
244adf37648SKyle Evans nvlist_destroy(nvl_device);
245adf37648SKyle Evans free(dev);
246adf37648SKyle Evans return ret;
247adf37648SKyle Evans }
248adf37648SKyle Evans
249adf37648SKyle Evans
kernel_set_device(struct wgdevice * dev)250adf37648SKyle Evans static int kernel_set_device(struct wgdevice *dev)
251adf37648SKyle Evans {
252adf37648SKyle Evans struct wg_data_io wgd = { 0 };
253adf37648SKyle Evans nvlist_t *nvl_device = NULL, **nvl_peers = NULL;
254adf37648SKyle Evans size_t peer_count = 0, i = 0;
255adf37648SKyle Evans struct wgpeer *peer;
256adf37648SKyle Evans int ret = 0, s;
257adf37648SKyle Evans
258adf37648SKyle Evans strlcpy(wgd.wgd_name, dev->name, sizeof(wgd.wgd_name));
259adf37648SKyle Evans
260adf37648SKyle Evans nvl_device = nvlist_create(0);
261adf37648SKyle Evans if (!nvl_device)
262adf37648SKyle Evans goto err;
263adf37648SKyle Evans
264adf37648SKyle Evans for_each_wgpeer(dev, peer)
265adf37648SKyle Evans ++peer_count;
266adf37648SKyle Evans if (peer_count) {
267adf37648SKyle Evans nvl_peers = calloc(peer_count, sizeof(*nvl_peers));
268adf37648SKyle Evans if (!nvl_peers)
269adf37648SKyle Evans goto err;
270adf37648SKyle Evans }
271adf37648SKyle Evans if (dev->flags & WGDEVICE_HAS_PRIVATE_KEY)
272adf37648SKyle Evans nvlist_add_binary(nvl_device, "private-key", dev->private_key, sizeof(dev->private_key));
273adf37648SKyle Evans if (dev->flags & WGDEVICE_HAS_LISTEN_PORT)
274adf37648SKyle Evans nvlist_add_number(nvl_device, "listen-port", dev->listen_port);
275adf37648SKyle Evans if (dev->flags & WGDEVICE_HAS_FWMARK)
276adf37648SKyle Evans nvlist_add_number(nvl_device, "user-cookie", dev->fwmark);
277adf37648SKyle Evans if (dev->flags & WGDEVICE_REPLACE_PEERS)
278adf37648SKyle Evans nvlist_add_bool(nvl_device, "replace-peers", true);
279adf37648SKyle Evans
280adf37648SKyle Evans for_each_wgpeer(dev, peer) {
281adf37648SKyle Evans size_t aip_count = 0, j = 0;
282adf37648SKyle Evans nvlist_t **nvl_aips = NULL;
283adf37648SKyle Evans struct wgallowedip *aip;
284adf37648SKyle Evans
285adf37648SKyle Evans nvl_peers[i] = nvlist_create(0);
286adf37648SKyle Evans if (!nvl_peers[i])
287adf37648SKyle Evans goto err_peer;
288adf37648SKyle Evans for_each_wgallowedip(peer, aip)
289adf37648SKyle Evans ++aip_count;
290adf37648SKyle Evans if (aip_count) {
291adf37648SKyle Evans nvl_aips = calloc(aip_count, sizeof(*nvl_aips));
292adf37648SKyle Evans if (!nvl_aips)
293adf37648SKyle Evans goto err_peer;
294adf37648SKyle Evans }
295adf37648SKyle Evans nvlist_add_binary(nvl_peers[i], "public-key", peer->public_key, sizeof(peer->public_key));
296adf37648SKyle Evans if (peer->flags & WGPEER_HAS_PRESHARED_KEY)
297adf37648SKyle Evans nvlist_add_binary(nvl_peers[i], "preshared-key", peer->preshared_key, sizeof(peer->preshared_key));
298adf37648SKyle Evans if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL)
299adf37648SKyle Evans nvlist_add_number(nvl_peers[i], "persistent-keepalive-interval", peer->persistent_keepalive_interval);
300adf37648SKyle Evans if (peer->endpoint.addr.sa_family == AF_INET || peer->endpoint.addr.sa_family == AF_INET6)
301adf37648SKyle Evans nvlist_add_binary(nvl_peers[i], "endpoint", &peer->endpoint.addr, peer->endpoint.addr.sa_len);
302adf37648SKyle Evans if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS)
303adf37648SKyle Evans nvlist_add_bool(nvl_peers[i], "replace-allowedips", true);
304adf37648SKyle Evans if (peer->flags & WGPEER_REMOVE_ME)
305adf37648SKyle Evans nvlist_add_bool(nvl_peers[i], "remove", true);
306adf37648SKyle Evans for_each_wgallowedip(peer, aip) {
307adf37648SKyle Evans nvl_aips[j] = nvlist_create(0);
308adf37648SKyle Evans if (!nvl_aips[j])
309adf37648SKyle Evans goto err_peer;
310adf37648SKyle Evans nvlist_add_number(nvl_aips[j], "cidr", aip->cidr);
311adf37648SKyle Evans if (aip->family == AF_INET)
312adf37648SKyle Evans nvlist_add_binary(nvl_aips[j], "ipv4", &aip->ip4, sizeof(aip->ip4));
313adf37648SKyle Evans else if (aip->family == AF_INET6)
314adf37648SKyle Evans nvlist_add_binary(nvl_aips[j], "ipv6", &aip->ip6, sizeof(aip->ip6));
315adf37648SKyle Evans ++j;
316adf37648SKyle Evans }
317adf37648SKyle Evans if (j) {
318adf37648SKyle Evans nvlist_add_nvlist_array(nvl_peers[i], "allowed-ips", (const nvlist_t *const *)nvl_aips, j);
319adf37648SKyle Evans for (j = 0; j < aip_count; ++j)
320adf37648SKyle Evans nvlist_destroy(nvl_aips[j]);
321adf37648SKyle Evans free(nvl_aips);
322adf37648SKyle Evans }
323adf37648SKyle Evans ++i;
324adf37648SKyle Evans continue;
325adf37648SKyle Evans
326adf37648SKyle Evans err_peer:
327adf37648SKyle Evans ret = -errno;
328adf37648SKyle Evans for (j = 0; j < aip_count && nvl_aips; ++j)
329adf37648SKyle Evans nvlist_destroy(nvl_aips[j]);
330adf37648SKyle Evans free(nvl_aips);
331adf37648SKyle Evans nvlist_destroy(nvl_peers[i]);
332*2cb43631SKyle Evans nvl_peers[i] = NULL;
333adf37648SKyle Evans goto err;
334adf37648SKyle Evans }
335adf37648SKyle Evans if (i) {
336adf37648SKyle Evans nvlist_add_nvlist_array(nvl_device, "peers", (const nvlist_t *const *)nvl_peers, i);
337adf37648SKyle Evans for (i = 0; i < peer_count; ++i)
338adf37648SKyle Evans nvlist_destroy(nvl_peers[i]);
339adf37648SKyle Evans free(nvl_peers);
340*2cb43631SKyle Evans nvl_peers = NULL;
341adf37648SKyle Evans }
342adf37648SKyle Evans wgd.wgd_data = nvlist_pack(nvl_device, &wgd.wgd_size);
343adf37648SKyle Evans nvlist_destroy(nvl_device);
344*2cb43631SKyle Evans nvl_device = NULL;
345adf37648SKyle Evans if (!wgd.wgd_data)
346adf37648SKyle Evans goto err;
347adf37648SKyle Evans s = get_dgram_socket();
348adf37648SKyle Evans if (s < 0)
349adf37648SKyle Evans return -errno;
350adf37648SKyle Evans return ioctl(s, SIOCSWG, &wgd);
351adf37648SKyle Evans
352adf37648SKyle Evans err:
353adf37648SKyle Evans if (!ret)
354adf37648SKyle Evans ret = -errno;
355adf37648SKyle Evans for (i = 0; i < peer_count && nvl_peers; ++i)
356adf37648SKyle Evans nvlist_destroy(nvl_peers[i]);
357adf37648SKyle Evans free(nvl_peers);
358adf37648SKyle Evans nvlist_destroy(nvl_device);
359adf37648SKyle Evans return ret;
360adf37648SKyle Evans }
361