-// 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>
NETCONF_ATTR_PORT,
NETCONF_ATTR_PEX_PORT,
NETCONF_ATTR_KEEPALIVE,
+ NETCONF_ATTR_STUN_SERVERS,
__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 },
[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];
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));
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)
{
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);
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;
}
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)
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);
{
network_teardown(net);
avl_delete(&networks, &net->node);
+ free(net->net_data);
free(net->config.data);
free(net);
}
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;
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;
goto invalid;
reload:
- network_reload(net);
+ network_reload(&net->reload_timer);
return 0;
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);