unet-cli: strip initial newline in usage message
[project/unetd.git] / network.c
index a6259edd193250f02378e3f7563ac8eab727ef41..e1ea869d6df560c85e2be462fc8dc18fb3c9de46 100644 (file)
--- a/network.c
+++ b/network.c
@@ -1,9 +1,13 @@
-// SPDX-License-Identifier: GPL-2.0+
+// SPDX-License-Identifier: GPL-2.0-or-later
 /*
  * Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
  */
+#define _GNU_SOURCE
 #include <arpa/inet.h>
+#include <sys/types.h>
+#include <sys/socket.h>
 #include <sys/wait.h>
+#include <net/if.h>
 #include <libubox/avl-cmp.h>
 #include <libubox/utils.h>
 #include <libubox/blobmsg_json.h>
@@ -28,6 +32,7 @@ enum {
        NETCONF_ATTR_PORT,
        NETCONF_ATTR_PEX_PORT,
        NETCONF_ATTR_KEEPALIVE,
+       NETCONF_ATTR_STUN_SERVERS,
        __NETCONF_ATTR_MAX
 };
 
@@ -36,11 +41,13 @@ static const struct blobmsg_policy netconf_policy[__NETCONF_ATTR_MAX] = {
        [NETCONF_ATTR_PORT] = { "port", BLOBMSG_TYPE_INT32 },
        [NETCONF_ATTR_PEX_PORT] = { "peer-exchange-port", BLOBMSG_TYPE_INT32 },
        [NETCONF_ATTR_KEEPALIVE] = { "keepalive", BLOBMSG_TYPE_INT32 },
+       [NETCONF_ATTR_STUN_SERVERS] = { "stun-servers", BLOBMSG_TYPE_ARRAY },
 };
 
 const struct blobmsg_policy network_policy[__NETWORK_ATTR_MAX] = {
        [NETWORK_ATTR_NAME] = { "name", BLOBMSG_TYPE_STRING },
        [NETWORK_ATTR_TYPE] = { "type", BLOBMSG_TYPE_STRING },
+       [NETWORK_ATTR_AUTH_KEY] = { "auth_key", BLOBMSG_TYPE_STRING },
        [NETWORK_ATTR_KEY] = { "key", BLOBMSG_TYPE_STRING },
        [NETWORK_ATTR_FILE] = { "file", BLOBMSG_TYPE_STRING },
        [NETWORK_ATTR_DATA] = { "data", BLOBMSG_TYPE_TABLE },
@@ -48,11 +55,23 @@ const struct blobmsg_policy network_policy[__NETWORK_ATTR_MAX] = {
        [NETWORK_ATTR_KEEPALIVE] = { "keepalive", BLOBMSG_TYPE_INT32 },
        [NETWORK_ATTR_DOMAIN] = { "domain", BLOBMSG_TYPE_STRING },
        [NETWORK_ATTR_UPDATE_CMD] = { "update-cmd", BLOBMSG_TYPE_STRING },
+       [NETWORK_ATTR_TUNNELS] = { "tunnels", BLOBMSG_TYPE_TABLE },
+       [NETWORK_ATTR_AUTH_CONNECT] = { "auth_connect", BLOBMSG_TYPE_ARRAY },
+       [NETWORK_ATTR_PEER_DATA] = { "peer_data", BLOBMSG_TYPE_ARRAY },
 };
 
 AVL_TREE(networks, avl_strcmp, false, NULL);
 static struct blob_buf b;
 
+static void network_load_stun_servers(struct network *net, struct blob_attr *data)
+{
+       struct blob_attr *cur;
+       int rem;
+
+       blobmsg_for_each_attr(cur, data, rem)
+               network_stun_server_add(net, blobmsg_get_string(cur));
+}
+
 static void network_load_config_data(struct network *net, struct blob_attr *data)
 {
        struct blob_attr *tb[__NETCONF_ATTR_MAX];
@@ -74,8 +93,8 @@ static void network_load_config_data(struct network *net, struct blob_attr *data
                const char *id = blobmsg_get_string(cur);
                siphash_to_le64(&net->net_config.addr.network_id, id, strlen(id), &key);
        } else {
-               siphash_to_le64(&net->net_config.addr.network_id, &net->net_config.port,
-                               sizeof(net->net_config.port), &key);
+               uint32_t port = cpu_to_le32(net->net_config.port);
+               siphash_to_le64(&net->net_config.addr.network_id, &port, sizeof(port), &key);
        }
 
        net->net_config.addr.network_id[0] = 0xfd;
@@ -87,12 +106,18 @@ static void network_load_config_data(struct network *net, struct blob_attr *data
                net->net_config.keepalive = blobmsg_get_u32(cur);
        else
                net->net_config.keepalive = 0;
+
+       if ((cur = tb[NETCONF_ATTR_STUN_SERVERS]) != NULL &&
+           blobmsg_check_array(cur, BLOBMSG_TYPE_STRING) > 0)
+               network_load_stun_servers(net, cur);
 }
 
 static int network_load_data(struct network *net, struct blob_attr *data)
 {
        struct blob_attr *tb[__NETDATA_ATTR_MAX];
+       siphash_key_t key = {};
 
+       net->net_config.hash = siphash(data, blob_raw_len(data), &key);
        blobmsg_parse(netdata_policy, __NETDATA_ATTR_MAX, tb,
                      blobmsg_data(data), blobmsg_len(data));
 
@@ -113,6 +138,101 @@ static int network_load_file(struct network *net)
        return network_load_data(net, b.head);
 }
 
+static int network_load_dynamic(struct network *net)
+{
+       const char *json = NULL;
+       char *fname = NULL;
+       struct stat st;
+       FILE *f = NULL;
+       int ret = -1;
+
+       if (asprintf(&fname, "%s/%s.bin", data_dir, network_name(net)) < 0)
+               return -1;
+
+       f = fopen(fname, "r");
+       free(fname);
+
+       if (!f) {
+               D_NET(net, "failed to open %s/%s.bin\n", data_dir, network_name(net));
+               return -1;
+       }
+
+       if (fstat(fileno(f), &st) < 0)
+               goto out;
+
+       net->net_data_len = st.st_size;
+       net->net_data = realloc(net->net_data, net->net_data_len + 1);
+       memset(net->net_data + net->net_data_len, 0, 1);
+       if (fread(net->net_data, 1, net->net_data_len, f) != net->net_data_len ||
+           unet_auth_data_validate(net->config.auth_key, net->net_data,
+                                   net->net_data_len, &net->net_data_version, &json)) {
+               net->net_data_len = 0;
+               goto out;
+       }
+
+       fclose(f);
+       blob_buf_init(&b, 0);
+       if (!blobmsg_add_json_from_string(&b, json)) {
+               net->net_data_len = 0;
+               return -1;
+       }
+
+       return network_load_data(net, b.head);
+
+out:
+       fclose(f);
+       return ret;
+}
+
+int network_save_dynamic(struct network *net)
+{
+       char *fname = NULL, *fname2;
+       size_t len;
+       FILE *f;
+       int fd, ret;
+
+       if (net->config.type != NETWORK_TYPE_DYNAMIC ||
+           !net->net_data_len)
+               return -1;
+
+       if (asprintf(&fname, "%s/%s.bin.XXXXXXXX", data_dir, network_name(net)) < 0)
+               return -1;
+
+       fd = mkstemp(fname);
+       if (fd < 0)
+               goto error;
+
+       f = fdopen(fd, "w");
+       if (!f) {
+               close(fd);
+               goto error;
+       }
+
+       len = fwrite(net->net_data, 1, net->net_data_len, f);
+       fflush(f);
+       fdatasync(fd);
+       fclose(f);
+
+       if (len != net->net_data_len)
+               goto error;
+
+       fname2 = strdup(fname);
+       *strrchr(fname2, '.') = 0;
+       ret = rename(fname, fname2);
+       free(fname2);
+
+       if (ret)
+               unlink(fname);
+       free(fname);
+
+       return ret;
+
+error:
+       free(fname);
+       return -1;
+}
+
+
 static void
 network_fill_ip(struct blob_buf *buf, int af, union network_addr *addr, int mask)
 {
@@ -243,6 +363,84 @@ network_fill_subnets(struct network *net, struct blob_buf *buf)
        __network_fill_subnets(net, buf, true);
 }
 
+static bool
+__network_skip_endpoint_route(struct network *net, struct network_host *host,
+                             union network_endpoint *ep)
+{
+       bool ipv6 = ep->sa.sa_family == AF_INET6;
+       uint32_t *subnet32, *addr32, mask32;
+       union network_addr addr = {};
+       struct blob_attr *cur;
+       int mask, rem;
+
+       blobmsg_for_each_attr(cur, host->peer.ipaddr, rem) {
+               const char *str = blobmsg_get_string(cur);
+
+               if (!!strchr(str, ':') != ipv6)
+                       continue;
+
+               if (inet_pton(ep->sa.sa_family, str, &addr) != 1)
+                       continue;
+
+               if (ipv6) {
+                       if (!memcmp(&addr.in6, &ep->in6.sin6_addr, sizeof(addr.in6)))
+                               return true;
+               } else {
+                       if (!memcmp(&addr.in, &ep->in.sin_addr, sizeof(addr.in)))
+                               return true;
+               }
+       }
+
+       if (ipv6)
+               addr32 = (uint32_t *)&ep->in6.sin6_addr;
+       else
+               addr32 = (uint32_t *)&ep->in.sin_addr;
+
+       subnet32 = (uint32_t *)&addr;
+       blobmsg_for_each_attr(cur, host->peer.subnet, rem) {
+               const char *str = blobmsg_get_string(cur);
+               int i;
+
+               if (!!strchr(str, ':') != ipv6)
+                       continue;
+
+               if (network_get_subnet(ep->sa.sa_family, &addr, &mask, str))
+                       continue;
+
+               if (mask <= 1)
+                       continue;
+
+               for (i = 0; i < (ipv6 ? 4 : 1); i++) {
+                       int cur_mask = mask > 32 ? 32 : mask;
+
+                       if (mask > 32)
+                               mask -= 32;
+                       else
+                               mask = 0;
+
+                       mask32 = ~0ULL << (32 - cur_mask);
+                       if (ntohl(subnet32[i] ^ addr32[i]) & mask32)
+                               continue;
+               }
+
+               return true;
+       }
+
+       return false;
+}
+
+bool network_skip_endpoint_route(struct network *net, union network_endpoint *ep)
+{
+       struct network_host *host;
+
+       avl_for_each_element(&net->hosts, host, node)
+               if (__network_skip_endpoint_route(net, host, ep))
+                       return true;
+
+       return false;
+}
+
+
 static void
 network_do_update(struct network *net, bool up)
 {
@@ -285,27 +483,33 @@ network_do_update(struct network *net, bool up)
        unetd_ubus_netifd_update(b.head);
 }
 
-static int network_reload(struct network *net)
+static void network_reload(struct uloop_timeout *t)
 {
-       int ret;
+       struct network *net = container_of(t, struct network, reload_timer);
 
        net->prev_local_host = net->net_config.local_host;
 
        memset(&net->net_config, 0, sizeof(net->net_config));
 
+       network_stun_free(net);
        network_pex_close(net);
        network_services_free(net);
        network_hosts_update_start(net);
+       network_services_update_start(net);
 
        switch (net->config.type) {
        case NETWORK_TYPE_FILE:
-               ret = network_load_file(net);
+               network_load_file(net);
                break;
        case NETWORK_TYPE_INLINE:
-               ret = network_load_data(net, net->config.net_data);
+               network_load_data(net, net->config.net_data);
+               break;
+       case NETWORK_TYPE_DYNAMIC:
+               network_load_dynamic(net);
                break;
        }
 
+       network_services_update_done(net);
        network_hosts_update_done(net);
        uloop_timeout_set(&net->connect_timer, 10);
 
@@ -314,8 +518,29 @@ static int network_reload(struct network *net)
        unetd_write_hosts();
        network_do_update(net, true);
        network_pex_open(net);
+       network_stun_start(net);
+       unetd_ubus_notify(net);
+}
 
-       return ret;
+void network_soft_reload(struct network *net)
+{
+       siphash_key_t key = {};
+       uint64_t hash;
+
+       if (net->config.type == NETWORK_TYPE_FILE) {
+               blob_buf_init(&b, 0);
+
+               if (!blobmsg_add_json_from_file(&b, net->config.file))
+                       return;
+
+               hash = siphash(b.head, blob_raw_len(b.head), &key);
+               if (hash != net->net_config.hash) {
+                       uloop_timeout_set(&net->reload_timer, 1);
+                       return;
+               }
+       }
+
+       network_hosts_reload_dynamic_peers(net);
 }
 
 static int network_setup(struct network *net)
@@ -325,13 +550,23 @@ static int network_setup(struct network *net)
                return -1;
        }
 
+       net->ifindex = if_nametoindex(network_name(net));
+       if (!net->ifindex) {
+               fprintf(stderr, "Could not get ifindex for network %s\n", network_name(net));
+               return -1;
+       }
+
        return 0;
 }
 
 static void network_teardown(struct network *net)
 {
+       uloop_timeout_cancel(&net->connect_timer);
+       uloop_timeout_cancel(&net->reload_timer);
        network_do_update(net, false);
+       network_stun_free(net);
        network_pex_close(net);
+       network_pex_free(net);
        network_hosts_free(net);
        network_services_free(net);
        wg_cleanup_network(net);
@@ -342,6 +577,7 @@ network_destroy(struct network *net)
 {
        network_teardown(net);
        avl_delete(&networks, &net->node);
+       free(net->net_data);
        free(net->config.data);
        free(net);
 }
@@ -365,10 +601,11 @@ network_set_config(struct network *net, struct blob_attr *config)
                      blobmsg_data(net->config.data),
                      blobmsg_len(net->config.data));
 
-       if ((cur = tb[NETWORK_ATTR_TYPE]) == NULL)
-               goto invalid;
-
-       if (!strcmp(blobmsg_get_string(cur), "file"))
+       if ((cur = tb[NETWORK_ATTR_TYPE]) == NULL ||
+           !strlen(blobmsg_get_string(cur)) ||
+           !strcmp(blobmsg_get_string(cur), "dynamic"))
+               net->config.type = NETWORK_TYPE_DYNAMIC;
+       else if (!strcmp(blobmsg_get_string(cur), "file"))
                net->config.type = NETWORK_TYPE_FILE;
        else if (!strcmp(blobmsg_get_string(cur), "inline"))
                net->config.type = NETWORK_TYPE_INLINE;
@@ -392,17 +629,39 @@ network_set_config(struct network *net, struct blob_attr *config)
                if (!net->config.net_data)
                        goto invalid;
                break;
+       case NETWORK_TYPE_DYNAMIC:
+               if ((cur = tb[NETWORK_ATTR_AUTH_KEY]) == NULL)
+                       goto invalid;
+
+               if (b64_decode(blobmsg_get_string(cur), net->config.auth_key,
+                              sizeof(net->config.auth_key)) != sizeof(net->config.auth_key))
+                       goto invalid;
+               break;
        }
 
-       if ((cur = tb[NETWORK_ATTR_INTERFACE]) != NULL)
+       if ((cur = tb[NETWORK_ATTR_INTERFACE]) != NULL &&
+           strlen(blobmsg_get_string(cur)) > 0)
                net->config.interface = blobmsg_get_string(cur);
 
-       if ((cur = tb[NETWORK_ATTR_UPDATE_CMD]) != NULL)
+       if ((cur = tb[NETWORK_ATTR_UPDATE_CMD]) != NULL &&
+           strlen(blobmsg_get_string(cur)) > 0)
                net->config.update_cmd = blobmsg_get_string(cur);
 
-       if ((cur = tb[NETWORK_ATTR_DOMAIN]) != NULL)
+       if ((cur = tb[NETWORK_ATTR_DOMAIN]) != NULL &&
+           strlen(blobmsg_get_string(cur)) > 0)
                net->config.domain = blobmsg_get_string(cur);
 
+       if ((cur = tb[NETWORK_ATTR_TUNNELS]) != NULL)
+               net->config.tunnels = cur;
+
+       if ((cur = tb[NETWORK_ATTR_AUTH_CONNECT]) != NULL &&
+           blobmsg_check_array(cur, BLOBMSG_TYPE_STRING) > 0)
+               net->config.auth_connect = cur;
+
+       if ((cur = tb[NETWORK_ATTR_PEER_DATA]) != NULL &&
+           blobmsg_check_array(cur, BLOBMSG_TYPE_STRING) > 0)
+               net->config.peer_data = cur;
+
        if ((cur = tb[NETWORK_ATTR_KEY]) == NULL)
                goto invalid;
 
@@ -416,7 +675,7 @@ network_set_config(struct network *net, struct blob_attr *config)
                goto invalid;
 
 reload:
-       network_reload(net);
+       network_reload(&net->reload_timer);
 
        return 0;
 
@@ -433,9 +692,11 @@ network_alloc(const char *name)
 
        net = calloc_a(sizeof(*net), &name_buf, strlen(name) + 1);
        net->node.key = strcpy(name_buf, name);
+       net->reload_timer.cb = network_reload;
        avl_insert(&networks, &net->node);
 
        network_pex_init(net);
+       network_stun_init(net);
        network_hosts_init(net);
        network_services_init(net);