unet-cli: strip initial newline in usage message
[project/unetd.git] / network.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
4 */
5 #define _GNU_SOURCE
6 #include <arpa/inet.h>
7 #include <sys/types.h>
8 #include <sys/socket.h>
9 #include <sys/wait.h>
10 #include <net/if.h>
11 #include <libubox/avl-cmp.h>
12 #include <libubox/utils.h>
13 #include <libubox/blobmsg_json.h>
14 #include "unetd.h"
15
16 enum {
17 NETDATA_ATTR_CONFIG,
18 NETDATA_ATTR_HOSTS,
19 NETDATA_ATTR_GROUPS,
20 NETDATA_ATTR_SERVICES,
21 __NETDATA_ATTR_MAX,
22 };
23
24 static const struct blobmsg_policy netdata_policy[__NETDATA_ATTR_MAX] = {
25 [NETDATA_ATTR_CONFIG] = { "config", BLOBMSG_TYPE_TABLE },
26 [NETDATA_ATTR_HOSTS] = { "hosts", BLOBMSG_TYPE_TABLE },
27 [NETDATA_ATTR_SERVICES] = { "services", BLOBMSG_TYPE_TABLE },
28 };
29
30 enum {
31 NETCONF_ATTR_ID,
32 NETCONF_ATTR_PORT,
33 NETCONF_ATTR_PEX_PORT,
34 NETCONF_ATTR_KEEPALIVE,
35 NETCONF_ATTR_STUN_SERVERS,
36 __NETCONF_ATTR_MAX
37 };
38
39 static const struct blobmsg_policy netconf_policy[__NETCONF_ATTR_MAX] = {
40 [NETCONF_ATTR_ID] = { "id", BLOBMSG_TYPE_STRING },
41 [NETCONF_ATTR_PORT] = { "port", BLOBMSG_TYPE_INT32 },
42 [NETCONF_ATTR_PEX_PORT] = { "peer-exchange-port", BLOBMSG_TYPE_INT32 },
43 [NETCONF_ATTR_KEEPALIVE] = { "keepalive", BLOBMSG_TYPE_INT32 },
44 [NETCONF_ATTR_STUN_SERVERS] = { "stun-servers", BLOBMSG_TYPE_ARRAY },
45 };
46
47 const struct blobmsg_policy network_policy[__NETWORK_ATTR_MAX] = {
48 [NETWORK_ATTR_NAME] = { "name", BLOBMSG_TYPE_STRING },
49 [NETWORK_ATTR_TYPE] = { "type", BLOBMSG_TYPE_STRING },
50 [NETWORK_ATTR_AUTH_KEY] = { "auth_key", BLOBMSG_TYPE_STRING },
51 [NETWORK_ATTR_KEY] = { "key", BLOBMSG_TYPE_STRING },
52 [NETWORK_ATTR_FILE] = { "file", BLOBMSG_TYPE_STRING },
53 [NETWORK_ATTR_DATA] = { "data", BLOBMSG_TYPE_TABLE },
54 [NETWORK_ATTR_INTERFACE] = { "interface", BLOBMSG_TYPE_STRING },
55 [NETWORK_ATTR_KEEPALIVE] = { "keepalive", BLOBMSG_TYPE_INT32 },
56 [NETWORK_ATTR_DOMAIN] = { "domain", BLOBMSG_TYPE_STRING },
57 [NETWORK_ATTR_UPDATE_CMD] = { "update-cmd", BLOBMSG_TYPE_STRING },
58 [NETWORK_ATTR_TUNNELS] = { "tunnels", BLOBMSG_TYPE_TABLE },
59 [NETWORK_ATTR_AUTH_CONNECT] = { "auth_connect", BLOBMSG_TYPE_ARRAY },
60 [NETWORK_ATTR_PEER_DATA] = { "peer_data", BLOBMSG_TYPE_ARRAY },
61 };
62
63 AVL_TREE(networks, avl_strcmp, false, NULL);
64 static struct blob_buf b;
65
66 static void network_load_stun_servers(struct network *net, struct blob_attr *data)
67 {
68 struct blob_attr *cur;
69 int rem;
70
71 blobmsg_for_each_attr(cur, data, rem)
72 network_stun_server_add(net, blobmsg_get_string(cur));
73 }
74
75 static void network_load_config_data(struct network *net, struct blob_attr *data)
76 {
77 struct blob_attr *tb[__NETCONF_ATTR_MAX];
78 struct blob_attr *cur;
79 siphash_key_t key = {};
80
81 blobmsg_parse(netconf_policy, __NETCONF_ATTR_MAX, tb,
82 blobmsg_data(data), blobmsg_len(data));
83
84 if ((cur = tb[NETCONF_ATTR_PORT]) != NULL)
85 net->net_config.port = blobmsg_get_u32(cur);
86 else
87 net->net_config.port = 51820;
88
89 if ((cur = tb[NETCONF_ATTR_PEX_PORT]) != NULL)
90 net->net_config.pex_port = blobmsg_get_u32(cur);
91
92 if ((cur = tb[NETCONF_ATTR_ID]) != NULL) {
93 const char *id = blobmsg_get_string(cur);
94 siphash_to_le64(&net->net_config.addr.network_id, id, strlen(id), &key);
95 } else {
96 uint32_t port = cpu_to_le32(net->net_config.port);
97 siphash_to_le64(&net->net_config.addr.network_id, &port, sizeof(port), &key);
98 }
99
100 net->net_config.addr.network_id[0] = 0xfd;
101 network_fill_host_addr(&net->net_config.addr, net->config.pubkey);
102
103 if (net->config.keepalive >= 0)
104 net->net_config.keepalive = net->config.keepalive;
105 else if ((cur = tb[NETCONF_ATTR_KEEPALIVE]) != NULL)
106 net->net_config.keepalive = blobmsg_get_u32(cur);
107 else
108 net->net_config.keepalive = 0;
109
110 if ((cur = tb[NETCONF_ATTR_STUN_SERVERS]) != NULL &&
111 blobmsg_check_array(cur, BLOBMSG_TYPE_STRING) > 0)
112 network_load_stun_servers(net, cur);
113 }
114
115 static int network_load_data(struct network *net, struct blob_attr *data)
116 {
117 struct blob_attr *tb[__NETDATA_ATTR_MAX];
118 siphash_key_t key = {};
119
120 net->net_config.hash = siphash(data, blob_raw_len(data), &key);
121 blobmsg_parse(netdata_policy, __NETDATA_ATTR_MAX, tb,
122 blobmsg_data(data), blobmsg_len(data));
123
124 network_load_config_data(net, tb[NETDATA_ATTR_CONFIG]);
125 network_hosts_add(net, tb[NETDATA_ATTR_HOSTS]);
126 network_services_add(net, tb[NETDATA_ATTR_SERVICES]);
127
128 return 0;
129 }
130
131 static int network_load_file(struct network *net)
132 {
133 blob_buf_init(&b, 0);
134
135 if (!blobmsg_add_json_from_file(&b, net->config.file))
136 return -1;
137
138 return network_load_data(net, b.head);
139 }
140
141 static int network_load_dynamic(struct network *net)
142 {
143 const char *json = NULL;
144 char *fname = NULL;
145 struct stat st;
146 FILE *f = NULL;
147 int ret = -1;
148
149 if (asprintf(&fname, "%s/%s.bin", data_dir, network_name(net)) < 0)
150 return -1;
151
152 f = fopen(fname, "r");
153 free(fname);
154
155 if (!f) {
156 D_NET(net, "failed to open %s/%s.bin\n", data_dir, network_name(net));
157 return -1;
158 }
159
160 if (fstat(fileno(f), &st) < 0)
161 goto out;
162
163 net->net_data_len = st.st_size;
164 net->net_data = realloc(net->net_data, net->net_data_len + 1);
165 memset(net->net_data + net->net_data_len, 0, 1);
166 if (fread(net->net_data, 1, net->net_data_len, f) != net->net_data_len ||
167 unet_auth_data_validate(net->config.auth_key, net->net_data,
168 net->net_data_len, &net->net_data_version, &json)) {
169 net->net_data_len = 0;
170 goto out;
171 }
172
173 fclose(f);
174 blob_buf_init(&b, 0);
175 if (!blobmsg_add_json_from_string(&b, json)) {
176 net->net_data_len = 0;
177 return -1;
178 }
179
180 return network_load_data(net, b.head);
181
182 out:
183 fclose(f);
184 return ret;
185 }
186
187 int network_save_dynamic(struct network *net)
188 {
189 char *fname = NULL, *fname2;
190 size_t len;
191 FILE *f;
192 int fd, ret;
193
194 if (net->config.type != NETWORK_TYPE_DYNAMIC ||
195 !net->net_data_len)
196 return -1;
197
198 if (asprintf(&fname, "%s/%s.bin.XXXXXXXX", data_dir, network_name(net)) < 0)
199 return -1;
200
201 fd = mkstemp(fname);
202 if (fd < 0)
203 goto error;
204
205 f = fdopen(fd, "w");
206 if (!f) {
207 close(fd);
208 goto error;
209 }
210
211 len = fwrite(net->net_data, 1, net->net_data_len, f);
212 fflush(f);
213 fdatasync(fd);
214 fclose(f);
215
216 if (len != net->net_data_len)
217 goto error;
218
219 fname2 = strdup(fname);
220 *strrchr(fname2, '.') = 0;
221 ret = rename(fname, fname2);
222 free(fname2);
223
224 if (ret)
225 unlink(fname);
226 free(fname);
227
228 return ret;
229
230 error:
231 free(fname);
232 return -1;
233 }
234
235
236 static void
237 network_fill_ip(struct blob_buf *buf, int af, union network_addr *addr, int mask)
238 {
239 char *str;
240 void *c;
241
242 c = blobmsg_open_table(buf, NULL);
243
244 blobmsg_printf(buf, "mask", "%d", mask);
245
246 str = blobmsg_alloc_string_buffer(buf, "ipaddr", INET6_ADDRSTRLEN);
247 inet_ntop(af, addr, str, INET6_ADDRSTRLEN);
248 blobmsg_add_string_buffer(buf);
249
250 blobmsg_close_table(buf, c);
251 }
252
253 static void
254 network_fill_ipaddr_list(struct network_host *host, struct blob_buf *b, bool ipv6)
255 {
256 union network_addr addr = {};
257 struct blob_attr *cur;
258 void *c;
259 int rem;
260 int af;
261
262 af = ipv6 ? AF_INET6 : AF_INET;
263 blobmsg_for_each_attr(cur, host->peer.ipaddr, rem) {
264 const char *str = blobmsg_get_string(cur);
265
266 if (!!strchr(str, ':') != ipv6)
267 continue;
268
269 if (inet_pton(af, str, &addr) != 1)
270 continue;
271
272 c = blobmsg_open_table(b, NULL);
273 blobmsg_add_string(b, "ipaddr", str);
274 blobmsg_add_string(b, "mask", ipv6 ? "128" : "32");
275 blobmsg_close_table(b, c);
276 }
277 }
278
279 static void
280 network_fill_ip_settings(struct network *net, struct blob_buf *buf)
281 {
282 struct network_host *host = net->net_config.local_host;
283 void *c;
284
285 c = blobmsg_open_array(buf, "ipaddr");
286 network_fill_ipaddr_list(host, buf, false);
287 blobmsg_close_array(buf, c);
288
289 c = blobmsg_open_array(buf, "ip6addr");
290 network_fill_ip(buf, AF_INET6, &host->peer.local_addr, 64);
291 network_fill_ipaddr_list(host, buf, true);
292 blobmsg_close_array(buf, c);
293 }
294
295 static void
296 __network_fill_host_subnets(struct network_host *host, struct blob_buf *b, bool ipv6)
297 {
298 union network_addr addr = {};
299 struct blob_attr *cur;
300 void *c;
301 int af;
302 int mask;
303 int rem;
304
305 af = ipv6 ? AF_INET6 : AF_INET;
306 blobmsg_for_each_attr(cur, host->peer.subnet, rem) {
307 const char *str = blobmsg_get_string(cur);
308 char *buf;
309
310 if (!!strchr(str, ':') != ipv6)
311 continue;
312
313 if (network_get_subnet(af, &addr, &mask, str))
314 continue;
315
316 c = blobmsg_open_table(b, NULL);
317
318 buf = blobmsg_alloc_string_buffer(b, "target", INET6_ADDRSTRLEN);
319 inet_ntop(af, &addr, buf, INET6_ADDRSTRLEN);
320 blobmsg_add_string_buffer(b);
321
322 blobmsg_printf(b, "netmask", "%d", mask);
323
324 blobmsg_close_table(b, c);
325 }
326
327 blobmsg_for_each_attr(cur, host->peer.ipaddr, rem) {
328 const char *str = blobmsg_get_string(cur);
329
330 if (!!strchr(str, ':') != ipv6)
331 continue;
332
333 if (inet_pton(af, str, &addr) != 1)
334 continue;
335
336 c = blobmsg_open_table(b, NULL);
337 blobmsg_add_string(b, "target", str);
338 blobmsg_add_string(b, "netmask", ipv6 ? "128" : "32");
339 blobmsg_close_table(b, c);
340 }
341 }
342
343 static void
344 __network_fill_subnets(struct network *net, struct blob_buf *buf, bool ipv6)
345 {
346 struct network_host *host;
347 void *c;
348
349 c = blobmsg_open_array(buf, ipv6 ? "routes6": "routes");
350 avl_for_each_element(&net->hosts, host, node) {
351 if (host == net->net_config.local_host)
352 continue;
353 __network_fill_host_subnets(host, buf, ipv6);
354 }
355 blobmsg_close_array(buf, c);
356 }
357
358
359 static void
360 network_fill_subnets(struct network *net, struct blob_buf *buf)
361 {
362 __network_fill_subnets(net, buf, false);
363 __network_fill_subnets(net, buf, true);
364 }
365
366 static bool
367 __network_skip_endpoint_route(struct network *net, struct network_host *host,
368 union network_endpoint *ep)
369 {
370 bool ipv6 = ep->sa.sa_family == AF_INET6;
371 uint32_t *subnet32, *addr32, mask32;
372 union network_addr addr = {};
373 struct blob_attr *cur;
374 int mask, rem;
375
376 blobmsg_for_each_attr(cur, host->peer.ipaddr, rem) {
377 const char *str = blobmsg_get_string(cur);
378
379 if (!!strchr(str, ':') != ipv6)
380 continue;
381
382 if (inet_pton(ep->sa.sa_family, str, &addr) != 1)
383 continue;
384
385 if (ipv6) {
386 if (!memcmp(&addr.in6, &ep->in6.sin6_addr, sizeof(addr.in6)))
387 return true;
388 } else {
389 if (!memcmp(&addr.in, &ep->in.sin_addr, sizeof(addr.in)))
390 return true;
391 }
392 }
393
394 if (ipv6)
395 addr32 = (uint32_t *)&ep->in6.sin6_addr;
396 else
397 addr32 = (uint32_t *)&ep->in.sin_addr;
398
399 subnet32 = (uint32_t *)&addr;
400 blobmsg_for_each_attr(cur, host->peer.subnet, rem) {
401 const char *str = blobmsg_get_string(cur);
402 int i;
403
404 if (!!strchr(str, ':') != ipv6)
405 continue;
406
407 if (network_get_subnet(ep->sa.sa_family, &addr, &mask, str))
408 continue;
409
410 if (mask <= 1)
411 continue;
412
413 for (i = 0; i < (ipv6 ? 4 : 1); i++) {
414 int cur_mask = mask > 32 ? 32 : mask;
415
416 if (mask > 32)
417 mask -= 32;
418 else
419 mask = 0;
420
421 mask32 = ~0ULL << (32 - cur_mask);
422 if (ntohl(subnet32[i] ^ addr32[i]) & mask32)
423 continue;
424 }
425
426 return true;
427 }
428
429 return false;
430 }
431
432 bool network_skip_endpoint_route(struct network *net, union network_endpoint *ep)
433 {
434 struct network_host *host;
435
436 avl_for_each_element(&net->hosts, host, node)
437 if (__network_skip_endpoint_route(net, host, ep))
438 return true;
439
440 return false;
441 }
442
443
444 static void
445 network_do_update(struct network *net, bool up)
446 {
447 if (!net->net_config.local_host)
448 up = false;
449
450 blob_buf_init(&b, 0);
451 blobmsg_add_u32(&b, "action", 0);
452 blobmsg_add_string(&b, "ifname", network_name(net));
453 blobmsg_add_u8(&b, "link-up", up);
454
455 if (up) {
456 network_fill_ip_settings(net, &b);
457 network_fill_subnets(net, &b);
458 }
459
460 if (debug) {
461 char *s = blobmsg_format_json(b.head, true);
462 D_NET(net, "update: %s", s);
463 free(s);
464 }
465
466 if (net->config.update_cmd) {
467 const char *argv[] = { net->config.update_cmd, NULL, NULL };
468 int pid, stat;
469
470 pid = fork();
471 if (pid == 0) {
472 argv[1] = blobmsg_format_json(b.head, true);
473 execvp(argv[0], (char **)argv);
474 exit(1);
475 }
476 waitpid(pid, &stat, 0);
477 }
478
479 if (!net->config.interface)
480 return;
481
482 blobmsg_add_string(&b, "interface", net->config.interface);
483 unetd_ubus_netifd_update(b.head);
484 }
485
486 static void network_reload(struct uloop_timeout *t)
487 {
488 struct network *net = container_of(t, struct network, reload_timer);
489
490 net->prev_local_host = net->net_config.local_host;
491
492 memset(&net->net_config, 0, sizeof(net->net_config));
493
494 network_stun_free(net);
495 network_pex_close(net);
496 network_services_free(net);
497 network_hosts_update_start(net);
498 network_services_update_start(net);
499
500 switch (net->config.type) {
501 case NETWORK_TYPE_FILE:
502 network_load_file(net);
503 break;
504 case NETWORK_TYPE_INLINE:
505 network_load_data(net, net->config.net_data);
506 break;
507 case NETWORK_TYPE_DYNAMIC:
508 network_load_dynamic(net);
509 break;
510 }
511
512 network_services_update_done(net);
513 network_hosts_update_done(net);
514 uloop_timeout_set(&net->connect_timer, 10);
515
516 net->prev_local_host = NULL;
517
518 unetd_write_hosts();
519 network_do_update(net, true);
520 network_pex_open(net);
521 network_stun_start(net);
522 unetd_ubus_notify(net);
523 }
524
525 void network_soft_reload(struct network *net)
526 {
527 siphash_key_t key = {};
528 uint64_t hash;
529
530 if (net->config.type == NETWORK_TYPE_FILE) {
531 blob_buf_init(&b, 0);
532
533 if (!blobmsg_add_json_from_file(&b, net->config.file))
534 return;
535
536 hash = siphash(b.head, blob_raw_len(b.head), &key);
537 if (hash != net->net_config.hash) {
538 uloop_timeout_set(&net->reload_timer, 1);
539 return;
540 }
541 }
542
543 network_hosts_reload_dynamic_peers(net);
544 }
545
546 static int network_setup(struct network *net)
547 {
548 if (wg_init_network(net)) {
549 fprintf(stderr, "Setup failed for network %s\n", network_name(net));
550 return -1;
551 }
552
553 net->ifindex = if_nametoindex(network_name(net));
554 if (!net->ifindex) {
555 fprintf(stderr, "Could not get ifindex for network %s\n", network_name(net));
556 return -1;
557 }
558
559 return 0;
560 }
561
562 static void network_teardown(struct network *net)
563 {
564 uloop_timeout_cancel(&net->connect_timer);
565 uloop_timeout_cancel(&net->reload_timer);
566 network_do_update(net, false);
567 network_stun_free(net);
568 network_pex_close(net);
569 network_pex_free(net);
570 network_hosts_free(net);
571 network_services_free(net);
572 wg_cleanup_network(net);
573 }
574
575 static void
576 network_destroy(struct network *net)
577 {
578 network_teardown(net);
579 avl_delete(&networks, &net->node);
580 free(net->net_data);
581 free(net->config.data);
582 free(net);
583 }
584
585 static int
586 network_set_config(struct network *net, struct blob_attr *config)
587 {
588 struct blob_attr *tb[__NETWORK_ATTR_MAX];
589 struct blob_attr *cur;
590
591 if (net->config.data && blob_attr_equal(net->config.data, config))
592 goto reload;
593
594 network_teardown(net);
595
596 free(net->config.data);
597 memset(&net->config, 0, sizeof(net->config));
598
599 net->config.data = blob_memdup(config);
600 blobmsg_parse(network_policy, __NETWORK_ATTR_MAX, tb,
601 blobmsg_data(net->config.data),
602 blobmsg_len(net->config.data));
603
604 if ((cur = tb[NETWORK_ATTR_TYPE]) == NULL ||
605 !strlen(blobmsg_get_string(cur)) ||
606 !strcmp(blobmsg_get_string(cur), "dynamic"))
607 net->config.type = NETWORK_TYPE_DYNAMIC;
608 else if (!strcmp(blobmsg_get_string(cur), "file"))
609 net->config.type = NETWORK_TYPE_FILE;
610 else if (!strcmp(blobmsg_get_string(cur), "inline"))
611 net->config.type = NETWORK_TYPE_INLINE;
612 else
613 goto invalid;
614
615 if ((cur = tb[NETWORK_ATTR_KEEPALIVE]) != NULL)
616 net->config.keepalive = blobmsg_get_u32(cur);
617 else
618 net->config.keepalive = -1;
619
620 switch (net->config.type) {
621 case NETWORK_TYPE_FILE:
622 if ((cur = tb[NETWORK_ATTR_FILE]) != NULL)
623 net->config.file = blobmsg_get_string(cur);
624 else
625 goto invalid;
626 break;
627 case NETWORK_TYPE_INLINE:
628 net->config.net_data = tb[NETWORK_ATTR_DATA];
629 if (!net->config.net_data)
630 goto invalid;
631 break;
632 case NETWORK_TYPE_DYNAMIC:
633 if ((cur = tb[NETWORK_ATTR_AUTH_KEY]) == NULL)
634 goto invalid;
635
636 if (b64_decode(blobmsg_get_string(cur), net->config.auth_key,
637 sizeof(net->config.auth_key)) != sizeof(net->config.auth_key))
638 goto invalid;
639 break;
640 }
641
642 if ((cur = tb[NETWORK_ATTR_INTERFACE]) != NULL &&
643 strlen(blobmsg_get_string(cur)) > 0)
644 net->config.interface = blobmsg_get_string(cur);
645
646 if ((cur = tb[NETWORK_ATTR_UPDATE_CMD]) != NULL &&
647 strlen(blobmsg_get_string(cur)) > 0)
648 net->config.update_cmd = blobmsg_get_string(cur);
649
650 if ((cur = tb[NETWORK_ATTR_DOMAIN]) != NULL &&
651 strlen(blobmsg_get_string(cur)) > 0)
652 net->config.domain = blobmsg_get_string(cur);
653
654 if ((cur = tb[NETWORK_ATTR_TUNNELS]) != NULL)
655 net->config.tunnels = cur;
656
657 if ((cur = tb[NETWORK_ATTR_AUTH_CONNECT]) != NULL &&
658 blobmsg_check_array(cur, BLOBMSG_TYPE_STRING) > 0)
659 net->config.auth_connect = cur;
660
661 if ((cur = tb[NETWORK_ATTR_PEER_DATA]) != NULL &&
662 blobmsg_check_array(cur, BLOBMSG_TYPE_STRING) > 0)
663 net->config.peer_data = cur;
664
665 if ((cur = tb[NETWORK_ATTR_KEY]) == NULL)
666 goto invalid;
667
668 if (b64_decode(blobmsg_get_string(cur), net->config.key, sizeof(net->config.key)) !=
669 sizeof(net->config.key))
670 goto invalid;
671
672 curve25519_generate_public(net->config.pubkey, net->config.key);
673
674 if (network_setup(net))
675 goto invalid;
676
677 reload:
678 network_reload(&net->reload_timer);
679
680 return 0;
681
682 invalid:
683 network_destroy(net);
684 return -1;
685 }
686
687 static struct network *
688 network_alloc(const char *name)
689 {
690 struct network *net;
691 char *name_buf;
692
693 net = calloc_a(sizeof(*net), &name_buf, strlen(name) + 1);
694 net->node.key = strcpy(name_buf, name);
695 net->reload_timer.cb = network_reload;
696 avl_insert(&networks, &net->node);
697
698 network_pex_init(net);
699 network_stun_init(net);
700 network_hosts_init(net);
701 network_services_init(net);
702
703 return net;
704 }
705
706 void network_fill_host_addr(union network_addr *addr, uint8_t *pubkey)
707 {
708 siphash_key_t key = {
709 .key = {
710 get_unaligned_le64(addr->network_id),
711 get_unaligned_le64(addr->network_id)
712 }
713 };
714
715 siphash_to_le64(&addr->host_addr, pubkey, CURVE25519_KEY_SIZE, &key);
716 }
717
718 int unetd_network_add(const char *name, struct blob_attr *config)
719 {
720 struct network *net;
721
722 if (strchr(name, '/'))
723 return -1;
724
725 net = avl_find_element(&networks, name, net, node);
726 if (!net)
727 net = network_alloc(name);
728
729 return network_set_config(net, config);
730 }
731
732 int unetd_network_remove(const char *name)
733 {
734 struct network *net;
735
736 net = avl_find_element(&networks, name, net, node);
737 if (!net)
738 return -1;
739
740 network_destroy(net);
741
742 return 0;
743 }
744
745 void network_free_all(void)
746 {
747 struct network *net, *tmp;
748
749 avl_for_each_element_safe(&networks, net, node, tmp)
750 network_destroy(net);
751 }