SET(SOURCES
- main.c network.c host.c service.c pex.c utils.c
+ main.c network.c host.c service.c pex.c
wg.c wg-user.c
)
SET(ubus "")
ENDIF()
-ADD_LIBRARY(unet SHARED curve25519.c siphash.c sha512.c fprime.c f25519.c ed25519.c edsign.c auth-data.c chacha20.c)
+ADD_LIBRARY(unet SHARED curve25519.c siphash.c sha512.c fprime.c f25519.c ed25519.c edsign.c auth-data.c chacha20.c pex-msg.c utils.c)
TARGET_LINK_LIBRARIES(unet ubox)
ADD_EXECUTABLE(unetd ${SOURCES})
Response to PEX_MSG_PING.
No payload.
+## Unencrypted messages (outside of the tunnel)
+
+These are only supported for networks using signed network data that can be updated dynamically.
+The struct pex_hdr header is followed by a second header:
+
+ struct pex_ext_hdr {
+ uint64_t nonce;
+ uint8_t auth_id[8];
+ };
+
+- nonce: nonce for id hash
+- auth_id: first 8 bytes of the auth public key
+
+In these messages, pex_hdr::id is XORed with siphash(req_id || req_id, auth_key)
+
+### opcode=5: PEX_MSG_UPDATE_REQUEST
+
+This message can be used outside of the wireguard tunnel in order to request signed network data
+It is used to ask a peer for the latest signed network data
+
+Payload:
+ struct pex_update_request {
+ uint64_t cur_version;
+ uint32_t req_id;
+ };
+
+- cur_version: latest version of the network data that the sender already has
+- req_id: request id copied to response messages
+
+### opcode=6: PEX_MSG_UPDATE_RESPONSE
+
+Used to send updated signed network data to a peer
+
+Payload:
+ struct pex_update_response {
+ uint64_t req_id;
+ uint32_t data_len;
+ uint8_t e_key[32];
+ };
+
+followed by the first chunk of network data.
+
+- req_id: request id of the PEX_MSG_UPDATE_REQUEST message
+- data_len: total length of the network data
+- e_key: ephemeral curve25519 public key
+
+The network data is chacha20 encrypted with the following key:
+ DH(e_key_priv, peer_key)
+And using req_id as nonce.
+
+- e_key_priv: private key belonging to e_key
+- peer_key: public key belonging to the receiver (from the network data)
+
+### opcode=7: PEX_MSG_UPDATE_RESPONSE_DATA
+
+Continuation of PEX_MSG_UPDATE_RESPONSE network data
+
+Payload:
+ struct pex_update_response_data {
+ uint64_t req_id;
+ uint32_t offset;
+ };
+
+followed by encrypted network data
+
+### opcode=8: PEX_MSG_UPDATE_RESPONSE_NO_DATA
+
+Indicates that the network data with the timestamp given in PEX_MSG_UPDATE_REQUEST
+is up to date
+
+Payload:
+
+ struct pex_update_response_no_data {
+ uint64_t req_id;
+ uint64_t cur_version;
+ };
+
+- req_id: request id of the PEX_MSG_UPDATE_REQUEST message
+- cur_version: latest version of the network data
#include "auth-data.h"
int unet_auth_data_validate(const uint8_t *key, const void *buf, size_t len,
- const char **json_data)
+ uint64_t *timestamp, const char **json_data)
{
const struct unet_auth_hdr *hdr = buf;
const struct unet_auth_data *data = net_data_auth_data_hdr(buf);
data->timestamp == 0)
return -1;
- if (memcmp(data->pubkey, key, EDSIGN_PUBLIC_KEY_SIZE) != 0)
+ if (key && memcmp(data->pubkey, key, EDSIGN_PUBLIC_KEY_SIZE) != 0)
return -2;
edsign_verify_init(&vst, hdr->signature, data->pubkey);
if (*(char *)(data + len - 1) != 0)
return -2;
+ if (timestamp)
+ *timestamp = be64_to_cpu(data->timestamp);
+
if (json_data)
*json_data = (const char *)(data + 1);
} __packed;
int unet_auth_data_validate(const uint8_t *key, const void *buf, size_t len,
- const char **json_data);
+ uint64_t *timestamp, const char **json_data);
static inline const struct unet_auth_data *
net_data_auth_data_hdr(const void *net_data)
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
+#include <errno.h>
#include <libubox/utils.h>
+#include <libubox/uloop.h>
+#include <libubox/blobmsg.h>
+#include <libubox/blobmsg_json.h>
#include "edsign.h"
#include "ed25519.h"
#include "curve25519.h"
#include "auth-data.h"
+#include "pex-msg.h"
+static uint8_t peerkey[EDSIGN_PUBLIC_KEY_SIZE];
static uint8_t pubkey[EDSIGN_PUBLIC_KEY_SIZE];
static uint8_t seckey[EDSIGN_PUBLIC_KEY_SIZE];
+static void *net_data;
+static size_t net_data_len;
+static uint64_t net_data_version;
+static struct blob_attr *net_data_hosts;
+static uint64_t req_id;
+static struct blob_buf b;
static FILE *out_file;
+static bool quiet;
+static bool sync_done;
static enum {
CMD_UNKNOWN,
CMD_GENERATE,
CMD_HOST_PUBKEY,
CMD_VERIFY,
CMD_SIGN,
+ CMD_DOWNLOAD,
+ CMD_UPLOAD,
} cmd;
+#define INFO(...) \
+ do { \
+ if (quiet) \
+ break; \
+ fprintf(stderr, ##__VA_ARGS__); \
+ } while (0)
+
static void print_key(const uint8_t *key)
{
char keystr[B64_ENCODE_LEN(EDSIGN_PUBLIC_KEY_SIZE)];
" -P Get pulic signing key from secret key\n"
" -H Get pulic host key from secret key\n"
" -G Generate new private key\n"
+ " -D <host>[:<port>] Download network data from unetd\n"
+ " -U <host>[:<port>] Upload network data to unetd\n"
"\n"
"Options:\n"
+ " -q: Quiet mode - suppress error/info messages\n"
" -o <file>: Set output file to <file> (defaults to stdout)\n"
" -k <keyfile>|-: Set public key from file or stdin\n"
" -K <keyfile>|-: Set secret key from file or stdin\n"
+ " -h <keyfile>|- Set peer private key from file or stdin\n"
+ " (for network data down-/upload)\n"
"\n", progname);
return 1;
}
+static void pex_timeout(struct uloop_timeout *timeout)
+{
+ uloop_end();
+}
+
+static void
+pex_recv_update_response(const uint8_t *data, size_t len, enum pex_opcode op)
+{
+ int net_data_len = 0;
+ void *net_data;
+
+ net_data = pex_msg_update_response_recv(data, len, op, &net_data_len, NULL);
+ if (net_data_len < 0)
+ goto out;
+
+ if (!net_data)
+ return;
+
+ if (cmd == CMD_DOWNLOAD) {
+ fwrite(net_data, net_data_len, 1, out_file);
+ sync_done = true;
+ }
+
+ free(net_data);
+
+out:
+ if (cmd == CMD_DOWNLOAD)
+ uloop_end();
+}
+
+static bool
+pex_get_pubkey(uint8_t *pubkey, const uint8_t *id)
+{
+ static const struct blobmsg_policy policy = { "key", BLOBMSG_TYPE_STRING };
+ struct blob_attr *cur, *key;
+ int rem;
+
+ blobmsg_for_each_attr(cur, net_data_hosts, rem) {
+ const char *keystr;
+
+ blobmsg_parse(&policy, 1, &key, blobmsg_data(cur), blobmsg_len(cur));
+
+ if (!key)
+ continue;
+
+ keystr = blobmsg_get_string(key);
+ if (b64_decode(keystr, pubkey, CURVE25519_KEY_SIZE) != CURVE25519_KEY_SIZE)
+ continue;
+
+ if (!memcmp(pubkey, id, PEX_ID_LEN))
+ return true;
+ }
+
+ return false;
+}
+
+static void
+pex_handle_update_request(struct sockaddr_in6 *addr, const uint8_t *id, void *data, size_t len)
+{
+ struct pex_msg_update_send_ctx ctx = {};
+ static uint8_t empty_key[EDSIGN_PUBLIC_KEY_SIZE] = {};
+ uint8_t peerpubkey[EDSIGN_PUBLIC_KEY_SIZE];
+ bool done = false;
+
+ if (!pex_get_pubkey(peerpubkey, id)) {
+ INFO("Could not find public key\n");
+ return;
+ }
+
+ pex_msg_update_response_init(&ctx, empty_key, pubkey,
+ peerpubkey, true, data, net_data, net_data_len);
+ while (!done) {
+ __pex_msg_send(-1, NULL);
+ done = !pex_msg_update_response_continue(&ctx);
+ }
+ sync_done = true;
+ uloop_end();
+}
+
+static void pex_recv(struct pex_hdr *hdr, struct sockaddr_in6 *addr)
+{
+ struct pex_ext_hdr *ehdr = (void *)(hdr + 1);
+ void *data = (void *)(ehdr + 1);
+ uint32_t len = be32_to_cpu(hdr->len);
+ uint64_t *msg_req_id = data;
+
+ if (hdr->version != 0)
+ return;
+
+ if (memcmp(ehdr->auth_id, pubkey, sizeof(ehdr->auth_id)) != 0)
+ return;
+
+ *(uint64_t *)hdr->id ^= pex_network_hash(pubkey, ehdr->nonce);
+
+ switch (hdr->opcode) {
+ case PEX_MSG_UPDATE_REQUEST:
+ if (cmd != CMD_UPLOAD)
+ break;
+
+ pex_handle_update_request(addr, hdr->id, data, len);
+ break;
+ case PEX_MSG_UPDATE_RESPONSE:
+ case PEX_MSG_UPDATE_RESPONSE_DATA:
+ case PEX_MSG_UPDATE_RESPONSE_NO_DATA:
+ if (len < sizeof(*msg_req_id) || *msg_req_id != req_id)
+ break;
+
+ if (cmd == CMD_DOWNLOAD &&
+ hdr->opcode == PEX_MSG_UPDATE_RESPONSE_NO_DATA) {
+ INFO("No network data available\n");
+ uloop_end();
+ }
+
+ if (cmd == CMD_UPLOAD &&
+ hdr->opcode != PEX_MSG_UPDATE_RESPONSE_NO_DATA) {
+ INFO("Server has newer network data\n");
+ uloop_end();
+ }
+
+ pex_recv_update_response(data, hdr->len, hdr->opcode);
+ break;
+ }
+}
+
+static int load_network_data(const char *file)
+{
+ static const struct blobmsg_policy policy = { "hosts", BLOBMSG_TYPE_TABLE };
+ struct unet_auth_hdr *hdr;
+ struct unet_auth_data *data;
+ const char *json;
+
+ net_data_len = UNETD_NET_DATA_SIZE_MAX;
+ net_data = unet_read_file(file, &net_data_len);
+ if (!net_data) {
+ INFO("failed to read input file %s\n", file);
+ return 1;
+ }
+
+ if (unet_auth_data_validate(NULL, net_data, net_data_len, &net_data_version, &json) < 0) {
+ INFO("input data validation failed\n");
+ return 1;
+ }
+
+ hdr = net_data;
+ data = (struct unet_auth_data *)(hdr + 1);
+ memcpy(pubkey, data->pubkey, sizeof(pubkey));
+
+ blob_buf_init(&b, 0);
+ blobmsg_add_json_from_string(&b, json);
+
+ blobmsg_parse(&policy, 1, &net_data_hosts, blobmsg_data(b.head), blobmsg_len(b.head));
+ if (!net_data_hosts) {
+ INFO("network data is missing the hosts attribute\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+
+static int cmd_sync(const char *endpoint, int argc, char **argv)
+{
+ uint8_t peerpubkey[EDSIGN_PUBLIC_KEY_SIZE];
+ struct uloop_timeout timeout = {
+ .cb = pex_timeout
+ };
+ struct pex_update_request *req;
+ union network_endpoint ep = {};
+ int len;
+
+ if (cmd == CMD_UPLOAD) {
+ if (argc < 1) {
+ INFO("missing file argument\n");
+ return 1;
+ }
+
+ if (load_network_data(argv[0]))
+ return 1;
+ }
+
+ if (network_get_endpoint(&ep, endpoint, UNETD_GLOBAL_PEX_PORT, 0) < 0) {
+ INFO("Invalid hostname/port %s\n", endpoint);
+ return 1;
+ }
+
+ len = ep.sa.sa_family == AF_INET6 ? sizeof(ep.in6) : sizeof(ep.in);
+
+ uloop_init();
+
+ if (pex_open(&ep, len, pex_recv, false) < 0)
+ return 1;
+
+ uloop_timeout_set(&timeout, 5000);
+
+ curve25519_generate_public(peerpubkey, peerkey);
+ req = pex_msg_update_request_init(peerpubkey, peerkey, pubkey, &ep,
+ net_data_version, true);
+ if (!req)
+ return 1;
+
+ req_id = req->req_id;
+ if (__pex_msg_send(-1, NULL) < 0) {
+ if (!quiet)
+ perror("send");
+ return 1;
+ }
+
+ uloop_run();
+
+ return !sync_done;
+}
+
static int cmd_sign(int argc, char **argv)
{
struct unet_auth_hdr hdr = {
FILE *f;
if (argc != 1) {
- fprintf(stderr, "Missing filename\n");
+ INFO("Missing filename\n");
return 1;
}
if (gettimeofday(&tv, NULL)) {
- perror("gettimeofday");
+ if (!quiet)
+ perror("gettimeofday");
return 1;
}
if (stat(argv[0], &st) ||
(f = fopen(argv[0], "r")) == NULL) {
- fprintf(stderr, "Input file not found\n");
+ INFO("Input file not found\n");
return 1;
}
fclose(f);
if (len != st.st_size) {
- fprintf(stderr, "Error reading from input file\n");
+ INFO("Error reading from input file\n");
return 1;
}
- len += sizeof(*data);
+ len += sizeof(*data) + 1;
memcpy(data->pubkey, pubkey, sizeof(pubkey));
edsign_sign(hdr.signature, pubkey, seckey, (const void *)data, len);
int ret = 1;
if (argc != 1) {
- fprintf(stderr, "Missing filename\n");
+ INFO("Missing filename\n");
return 1;
}
if (stat(argv[0], &st) ||
(f = fopen(argv[0], "r")) == NULL) {
- fprintf(stderr, "Input file not found\n");
+ INFO("Input file not found\n");
return 1;
}
if (st.st_size <= sizeof(*hdr) + sizeof(*data)) {
- fprintf(stderr, "Input file too small\n");
+ INFO("Input file too small\n");
fclose(f);
return 1;
}
fclose(f);
if (len != st.st_size) {
- fprintf(stderr, "Error reading from input file\n");
+ INFO("Error reading from input file\n");
return 1;
}
- ret = unet_auth_data_validate(pubkey, hdr, len, NULL);
+ ret = unet_auth_data_validate(pubkey, hdr, len, NULL, NULL);
switch (ret) {
case -1:
- fprintf(stderr, "Invalid input data\n");
+ INFO("Invalid input data\n");
break;
case -2:
- fprintf(stderr, "Public key does not match\n");
+ INFO("Public key does not match\n");
break;
case -3:
- fprintf(stderr, "Signature verification failed\n");
+ INFO("Signature verification failed\n");
break;
}
f = fopen("/dev/urandom", "r");
if (!f) {
- fprintf(stderr, "Can't open /dev/urandom\n");
+ INFO("Can't open /dev/urandom\n");
return 1;
}
fclose(f);
if (ret != 1) {
- fprintf(stderr, "Can't read data from /dev/urandom\n");
+ INFO("Can't read data from /dev/urandom\n");
return 1;
}
f = fopen(str, "r");
if (!f) {
- fprintf(stderr, "Can't open key file for reading\n");
+ INFO("Can't open key file for reading\n");
return false;
}
keystr[len] = 0;
if (b64_decode(keystr, dest, EDSIGN_PUBLIC_KEY_SIZE) != EDSIGN_PUBLIC_KEY_SIZE) {
- fprintf(stderr, "Failed to parse key data\n");
+ INFO("Failed to parse key data\n");
return false;
}
return true;
}
+static bool cmd_needs_peerkey(void)
+{
+ switch (cmd) {
+ case CMD_DOWNLOAD:
+ return true;
+ default:
+ return false;
+ }
+}
+
static bool cmd_needs_pubkey(void)
{
switch (cmd) {
+ case CMD_DOWNLOAD:
case CMD_VERIFY:
return true;
default:
}
}
+static bool cmd_needs_outfile(void)
+{
+ switch (cmd) {
+ case CMD_SIGN:
+ case CMD_PUBKEY:
+ case CMD_GENERATE:
+ case CMD_DOWNLOAD:
+ return true;
+ default:
+ return false;
+ }
+}
+
int main(int argc, char **argv)
{
const char *progname = argv[0];
const char *out_filename = NULL;
+ const char *cmd_arg = NULL;
bool has_key = false, has_pubkey = false;
+ bool has_peerkey = false;
int ret, ch;
- while ((ch = getopt(argc, argv, "o:k:K:GHPSV")) != -1) {
+ while ((ch = getopt(argc, argv, "h:k:K:o:qD:GHPSU:V")) != -1) {
switch (ch) {
+ case 'D':
+ case 'U':
+ case 'G':
+ case 'H':
+ case 'S':
+ case 'P':
+ case 'V':
+ if (cmd != CMD_UNKNOWN)
+ return usage(progname);
+ break;
+ default:
+ break;
+ }
+
+ switch (ch) {
+ case 'q':
+ quiet = true;
+ break;
case 'o':
out_filename = optarg;
break;
+ case 'h':
+ if (has_peerkey)
+ return usage(progname);
+
+ if (!parse_key(peerkey, optarg)) {
+ return 1;
+ }
+
+ has_peerkey = true;
+ break;
case 'k':
if (has_pubkey)
return usage(progname);
edsign_sec_to_pub(pubkey, seckey);
has_pubkey = true;
break;
+ case 'U':
+ cmd = CMD_UPLOAD;
+ cmd_arg = optarg;
+ break;
+ case 'D':
+ cmd = CMD_DOWNLOAD;
+ cmd_arg = optarg;
+ break;
case 'G':
- if (cmd != CMD_UNKNOWN)
- return usage(progname);
-
cmd = CMD_GENERATE;
break;
case 'S':
- if (cmd != CMD_UNKNOWN)
- return usage(progname);
-
cmd = CMD_SIGN;
break;
case 'P':
- if (cmd != CMD_UNKNOWN)
- return usage(progname);
-
cmd = CMD_PUBKEY;
break;
case 'H':
- if (cmd != CMD_UNKNOWN)
- return usage(progname);
-
cmd = CMD_HOST_PUBKEY;
break;
case 'V':
- if (cmd != CMD_UNKNOWN)
- return usage(progname);
-
cmd = CMD_VERIFY;
break;
default:
}
}
+ if (!has_peerkey && cmd_needs_peerkey()) {
+ INFO("Missing -h <key> argument\n");
+ return 1;
+ }
+
if (!has_key && cmd_needs_key()) {
- fprintf(stderr, "Missing -K <key> argument\n");
+ INFO("Missing -K <key> argument\n");
return 1;
}
if (!has_pubkey && cmd_needs_pubkey()) {
- fprintf(stderr, "Missing -k <key> argument\n");
+ INFO("Missing -k <key> argument\n");
return 1;
}
argc -= optind;
argv += optind;
- if (out_filename) {
+ if (out_filename && cmd_needs_outfile()) {
out_file = fopen(out_filename, "w");
if (!out_file) {
- fprintf(stderr, "Failed to open output file\n");
+ INFO("Failed to open output file\n");
return 1;
}
} else {
ret = -1;
switch (cmd) {
+ case CMD_UPLOAD:
+ case CMD_DOWNLOAD:
+ ret = cmd_sync(cmd_arg, argc, argv);
+ break;
case CMD_GENERATE:
ret = cmd_generate(argc, argv);
break;
break;
}
+ if (net_data)
+ free(net_data);
+
+ blob_buf_free(&b);
+
if (out_file != stdout) {
fclose(out_file);
if (ret)
ip link add dev $ifname type wireguard > /dev/null 2>&1
-[ "$ifname" != "net0" ] && ln -sf net0.bin "${ifname}.bin"
+# [ "$ifname" != "net0" ] && ln -sf net0.bin "${ifname}.bin"
../unetd -D $PWD -d -h $PWD/hosts -N '{
"name": "'"$ifname"'",
struct network_host *host;
union network_endpoint *ep;
+ avl_for_each_element(&net->hosts, host, node)
+ host->peer.state.num_net_queries = 0;
+ net->num_net_queries = 0;
+
if (!net->net_config.keepalive)
return;
uint64_t last_handshake;
uint64_t last_request;
int idle;
+ int num_net_queries;
} state;
};
static const char *hosts_file;
const char *mssfix_path = UNETD_MSS_BPF_PATH;
const char *data_dir = UNETD_DATA_DIR;
+int global_pex_port = UNETD_GLOBAL_PEX_PORT;
bool debug;
static void
struct cmdline_network *net;
int ch;
- while ((ch = getopt(argc, argv, "D:dh:M:N:")) != -1) {
+ while ((ch = getopt(argc, argv, "D:dh:M:N:P:")) != -1) {
switch (ch) {
case 'D':
data_dir = optarg;
case 'M':
mssfix_path = optarg;
break;
+ case 'P':
+ global_pex_port = atoi(optarg);
+ break;
}
}
uloop_init();
unetd_ubus_init();
unetd_write_hosts();
+ global_pex_open();
add_networks();
uloop_run();
+ pex_close();
network_free_all();
uloop_done();
[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 },
};
AVL_TREE(networks, avl_strcmp, false, NULL);
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, &json)) {
+ net->net_data_len, &net->net_data_version, &json)) {
net->net_data_len = 0;
goto out;
}
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;
+
+ asprintf(&fname, "%s/%s.bin.XXXXXXXX", data_dir, network_name(net));
+ 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;
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:
- ret = network_load_dynamic(net);
+ network_load_dynamic(net);
break;
}
unetd_write_hosts();
network_do_update(net, true);
network_pex_open(net);
-
- return ret;
}
static int network_setup(struct network *net)
static void network_teardown(struct network *net)
{
+ uloop_timeout_cancel(&net->reload_timer);
network_do_update(net, false);
network_pex_close(net);
network_hosts_free(net);
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_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);
const char *domain;
struct blob_attr *tunnels;
struct blob_attr *net_data;
+ struct blob_attr *auth_connect;
} config;
struct {
void *net_data;
size_t net_data_len;
+ uint64_t net_data_version;
+ int num_net_queries;
+
+ struct uloop_timeout reload_timer;
int ifindex;
struct network_host *prev_local_host;
NETWORK_ATTR_KEEPALIVE,
NETWORK_ATTR_DOMAIN,
NETWORK_ATTR_TUNNELS,
+ NETWORK_ATTR_AUTH_CONNECT,
__NETWORK_ATTR_MAX,
};
}
void network_fill_host_addr(union network_addr *addr, uint8_t *key);
+int network_save_dynamic(struct network *net);
void network_free_all(void);
int unetd_network_add(const char *name, struct blob_attr *config);
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
+ */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libubox/list.h>
+#include <libubox/uloop.h>
+#include "pex-msg.h"
+#include "chacha20.h"
+#include "auth-data.h"
+
+static char pex_tx_buf[PEX_BUF_SIZE];
+static FILE *pex_urandom;
+static struct uloop_fd pex_fd;
+static LIST_HEAD(requests);
+static struct uloop_timeout gc_timer;
+
+static pex_recv_cb_t pex_recv_cb;
+
+struct pex_msg_update_recv_ctx {
+ struct list_head list;
+
+ union network_endpoint addr;
+
+ uint8_t priv_key[CURVE25519_KEY_SIZE];
+ uint8_t auth_key[CURVE25519_KEY_SIZE];
+ uint8_t e_key[CURVE25519_KEY_SIZE];
+
+ uint64_t req_id;
+
+ void *data;
+ int data_len;
+ int data_ofs;
+
+ int idle;
+};
+
+uint64_t pex_network_hash(const uint8_t *auth_key, uint64_t req_id)
+{
+ siphash_key_t key = {
+ be64_to_cpu(req_id),
+ be64_to_cpu(req_id)
+ };
+ uint64_t hash;
+
+ siphash_to_be64(&hash, auth_key, CURVE25519_KEY_SIZE, &key);
+
+ return hash;
+}
+
+
+struct pex_hdr *__pex_msg_init(const uint8_t *pubkey, uint8_t opcode)
+{
+ struct pex_hdr *hdr = (struct pex_hdr *)pex_tx_buf;
+
+ hdr->version = 0;
+ hdr->opcode = opcode;
+ hdr->len = 0;
+ memcpy(hdr->id, pubkey, sizeof(hdr->id));
+
+ return hdr;
+}
+
+struct pex_hdr *__pex_msg_init_ext(const uint8_t *pubkey, const uint8_t *auth_key,
+ uint8_t opcode, bool ext)
+{
+ struct pex_hdr *hdr = __pex_msg_init(pubkey, opcode);
+ struct pex_ext_hdr *ehdr = (struct pex_ext_hdr *)(hdr + 1);
+ uint64_t hash;
+
+ if (!ext)
+ return hdr;
+
+ hdr->len = sizeof(*ehdr);
+
+ fread(&ehdr->nonce, sizeof(ehdr->nonce), 1, pex_urandom);
+
+ hash = pex_network_hash(auth_key, ehdr->nonce);
+ *(uint64_t *)hdr->id ^= hash;
+ memcpy(ehdr->auth_id, auth_key, sizeof(ehdr->auth_id));
+
+ return hdr;
+}
+
+void *pex_msg_append(size_t len)
+{
+ struct pex_hdr *hdr = (struct pex_hdr *)pex_tx_buf;
+ int ofs = hdr->len + sizeof(struct pex_hdr);
+ void *buf = &pex_tx_buf[ofs];
+
+ if (sizeof(pex_tx_buf) - ofs < len)
+ return NULL;
+
+ hdr->len += len;
+ memset(buf, 0, len);
+
+ return buf;
+}
+
+static void
+pex_fd_cb(struct uloop_fd *fd, unsigned int events)
+{
+ struct sockaddr_in6 sin6;
+ static char buf[PEX_BUF_SIZE];
+ struct pex_hdr *hdr = (struct pex_hdr *)buf;
+ ssize_t len;
+
+ while (1) {
+ socklen_t slen = sizeof(sin6);
+
+ len = recvfrom(fd->fd, buf, sizeof(buf), 0, (struct sockaddr *)&sin6, &slen);
+ if (len < 0) {
+ if (errno == EINTR)
+ continue;
+
+ if (errno == EAGAIN)
+ break;
+
+ pex_close();
+ return;
+ }
+
+ if (!len)
+ continue;
+
+ if (len < sizeof(*hdr) + sizeof(struct pex_ext_hdr))
+ continue;
+
+ hdr->len = ntohs(hdr->len);
+ if (len - sizeof(hdr) - sizeof(struct pex_ext_hdr) < hdr->len)
+ continue;
+
+ pex_recv_cb(hdr, &sin6);
+ }
+}
+
+int __pex_msg_send(int fd, const void *addr)
+{
+ struct pex_hdr *hdr = (struct pex_hdr *)pex_tx_buf;
+ const struct sockaddr *sa = addr;
+ size_t tx_len = sizeof(*hdr) + hdr->len;
+ uint16_t orig_len = hdr->len;
+ size_t addr_len;
+ int ret;
+
+ if (fd < 0) {
+ hdr->len -= sizeof(struct pex_ext_hdr);
+ fd = pex_fd.fd;
+ }
+
+ hdr->len = htons(hdr->len);
+ if (addr) {
+ if (sa->sa_family == AF_INET6)
+ addr_len = sizeof(struct sockaddr_in6);
+ else
+ addr_len = sizeof(struct sockaddr_in);
+ ret = sendto(fd, pex_tx_buf, tx_len, 0, sa, addr_len);
+ } else {
+ ret = send(fd, pex_tx_buf, tx_len, 0);
+ }
+ hdr->len = orig_len;
+
+ return ret;
+}
+
+static void
+pex_msg_update_response_fill(struct pex_msg_update_send_ctx *ctx)
+{
+ struct pex_hdr *hdr = (struct pex_hdr *)pex_tx_buf;
+ int ofs = hdr->len + sizeof(struct pex_hdr);
+ int cur_len = ctx->rem;
+
+ if (cur_len > PEX_BUF_SIZE - ofs)
+ cur_len = PEX_BUF_SIZE - ofs;
+
+ memcpy(pex_msg_append(cur_len), ctx->cur, cur_len);
+ ctx->cur += cur_len;
+ ctx->rem -= cur_len;
+}
+
+void pex_msg_update_response_init(struct pex_msg_update_send_ctx *ctx,
+ const uint8_t *pubkey, const uint8_t *auth_key,
+ const uint8_t *peer_key, bool ext,
+ struct pex_update_request *req,
+ const void *data, int len)
+{
+ uint8_t e_key_priv[CURVE25519_KEY_SIZE];
+ uint8_t enc_key[CURVE25519_KEY_SIZE];
+ struct pex_update_response *res;
+
+ ctx->pubkey = pubkey;
+ ctx->auth_key = auth_key;
+ ctx->ext = ext;
+ ctx->req_id = req->req_id;
+
+ __pex_msg_init_ext(pubkey, auth_key, PEX_MSG_UPDATE_RESPONSE, ext);
+ res = pex_msg_append(sizeof(*res));
+ res->req_id = req->req_id;
+ res->data_len = len;
+
+ fread(e_key_priv, sizeof(e_key_priv), 1, pex_urandom);
+ curve25519_clamp_secret(e_key_priv);
+ curve25519_generate_public(res->e_key, e_key_priv);
+ curve25519(enc_key, e_key_priv, peer_key);
+
+ ctx->data = ctx->cur = malloc(len);
+ ctx->rem = len;
+
+ memcpy(ctx->data, data, len);
+ chacha20_encrypt_msg(ctx->data, len, &req->req_id, enc_key);
+
+ pex_msg_update_response_fill(ctx);
+}
+
+bool pex_msg_update_response_continue(struct pex_msg_update_send_ctx *ctx)
+{
+ struct pex_update_response_data *res_ext;
+
+ if (ctx->rem <= 0) {
+ free(ctx->data);
+ ctx->data = NULL;
+
+ return false;
+ }
+
+ __pex_msg_init_ext(ctx->pubkey, ctx->auth_key,
+ PEX_MSG_UPDATE_RESPONSE_DATA, ctx->ext);
+ res_ext = pex_msg_append(sizeof(*res_ext));
+ res_ext->req_id = ctx->req_id;
+ res_ext->offset = ctx->cur - ctx->data;
+ pex_msg_update_response_fill(ctx);
+
+ return true;
+}
+
+
+struct pex_update_request *
+pex_msg_update_request_init(const uint8_t *pubkey, const uint8_t *priv_key,
+ const uint8_t *auth_key, union network_endpoint *addr,
+ uint64_t cur_version, bool ext)
+{
+ struct pex_update_request *req;
+ struct pex_msg_update_recv_ctx *ctx;
+
+ list_for_each_entry(ctx, &requests, list) {
+ if (!memcmp(&ctx->addr, addr, sizeof(ctx->addr)))
+ return NULL;
+ }
+
+ ctx = calloc(1, sizeof(*ctx));
+ memcpy(&ctx->addr, addr, sizeof(ctx->addr));
+ memcpy(ctx->auth_key, auth_key, sizeof(ctx->auth_key));
+ memcpy(ctx->priv_key, priv_key, sizeof(ctx->priv_key));
+ fread(&ctx->req_id, sizeof(ctx->req_id), 1, pex_urandom);
+ list_add_tail(&ctx->list, &requests);
+ if (!gc_timer.pending)
+ uloop_timeout_set(&gc_timer, 1000);
+
+ __pex_msg_init_ext(pubkey, auth_key, PEX_MSG_UPDATE_REQUEST, ext);
+ req = pex_msg_append(sizeof(*req));
+ req->cur_version = cpu_to_be64(cur_version);
+ req->req_id = ctx->req_id;
+
+ return req;
+}
+
+static struct pex_msg_update_recv_ctx *
+pex_msg_update_recv_ctx_get(uint64_t req_id)
+{
+ struct pex_msg_update_recv_ctx *ctx;
+
+ list_for_each_entry(ctx, &requests, list) {
+ if (ctx->req_id == req_id) {
+ ctx->idle = 0;
+ return ctx;
+ }
+ }
+
+ return NULL;
+}
+
+static void pex_msg_update_ctx_free(struct pex_msg_update_recv_ctx *ctx)
+{
+ list_del(&ctx->list);
+ free(ctx->data);
+ free(ctx);
+}
+
+void *pex_msg_update_response_recv(const void *data, int len, enum pex_opcode op,
+ int *data_len, uint64_t *timestamp)
+{
+ struct pex_msg_update_recv_ctx *ctx;
+ uint8_t enc_key[CURVE25519_KEY_SIZE];
+ void *ret;
+
+ *data_len = 0;
+ if (op == PEX_MSG_UPDATE_RESPONSE) {
+ const struct pex_update_response *res = data;
+
+ if (len < sizeof(*res))
+ return NULL;
+
+ ctx = pex_msg_update_recv_ctx_get(res->req_id);
+ if (!ctx || ctx->data_len || !res->data_len ||
+ res->data_len > UNETD_NET_DATA_SIZE_MAX)
+ return NULL;
+
+ data += sizeof(*res);
+ len -= sizeof(*res);
+
+ ctx->data_len = res->data_len;
+ memcpy(ctx->e_key, res->e_key, sizeof(ctx->e_key));
+ ctx->data = malloc(ctx->data_len);
+ } else if (op == PEX_MSG_UPDATE_RESPONSE_DATA) {
+ const struct pex_update_response_data *res = data;
+
+ if (len <= sizeof(*res))
+ return NULL;
+
+ ctx = pex_msg_update_recv_ctx_get(res->req_id);
+ if (!ctx || ctx->data_ofs != res->offset)
+ return NULL;
+
+ data += sizeof(*res);
+ len -= sizeof(*res);
+ } else if (op == PEX_MSG_UPDATE_RESPONSE_NO_DATA) {
+ const struct pex_update_response_no_data *res = data;
+
+ if (len < sizeof(*res))
+ return NULL;
+
+ ctx = pex_msg_update_recv_ctx_get(res->req_id);
+ if (!ctx)
+ return NULL;
+
+ goto error;
+ } else {
+ return NULL;
+ }
+
+ if (ctx->data_ofs + len > ctx->data_len)
+ goto error;
+
+ memcpy(ctx->data + ctx->data_ofs, data, len);
+ ctx->data_ofs += len;
+ if (ctx->data_ofs < ctx->data_len)
+ return NULL;
+
+ curve25519(enc_key, ctx->priv_key, ctx->e_key);
+ chacha20_encrypt_msg(ctx->data, ctx->data_len, &ctx->req_id, enc_key);
+ if (unet_auth_data_validate(ctx->auth_key, ctx->data, ctx->data_len, timestamp, NULL))
+ goto error;
+
+ *data_len = ctx->data_len;
+ ret = ctx->data;
+ ctx->data = NULL;
+ pex_msg_update_ctx_free(ctx);
+
+ return ret;
+
+error:
+ pex_msg_update_ctx_free(ctx);
+ *data_len = -1;
+ return NULL;
+}
+
+static void
+pex_gc_cb(struct uloop_timeout *t)
+{
+ struct pex_msg_update_recv_ctx *ctx, *tmp;
+
+ list_for_each_entry_safe(ctx, tmp, &requests, list) {
+ if (++ctx->idle <= 3)
+ continue;
+
+ pex_msg_update_ctx_free(ctx);
+ }
+
+ if (!list_empty(&requests))
+ uloop_timeout_set(t, 1000);
+}
+
+int pex_open(void *addr, size_t addr_len, pex_recv_cb_t cb, bool server)
+{
+ struct sockaddr *sa = addr;
+ int yes = 1, no = 0;
+ int fd;
+
+ pex_recv_cb = cb;
+
+ pex_urandom = fopen("/dev/urandom", "r");
+ if (!pex_urandom)
+ return -1;
+
+ fd = socket(sa->sa_family == AF_INET ? PF_INET : PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (fd < 0)
+ goto close_urandom;
+
+ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
+ fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+
+ if (server) {
+ if (bind(fd, addr, addr_len) < 0) {
+ perror("bind");
+ goto close_socket;
+ }
+
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
+ setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes));
+ setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &no, sizeof(no));
+ } else {
+ if (connect(fd, addr, addr_len) < 0) {
+ perror("connect");
+ goto close_socket;
+ }
+ }
+
+ pex_fd.fd = fd;
+ pex_fd.cb = pex_fd_cb;
+ uloop_fd_add(&pex_fd, ULOOP_READ);
+
+ gc_timer.cb = pex_gc_cb;
+
+ return 0;
+
+close_socket:
+ close(fd);
+close_urandom:
+ fclose(pex_urandom);
+ return -1;
+}
+
+void pex_close(void)
+{
+ if (!pex_fd.cb)
+ return;
+
+ fclose(pex_urandom);
+ uloop_fd_delete(&pex_fd);
+ close(pex_fd.fd);
+ pex_fd.cb = NULL;
+ pex_urandom = NULL;
+}
--- /dev/null
+#ifndef __PEX_MSG_H
+#define __PEX_MSG_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include "curve25519.h"
+#include "siphash.h"
+
+#define UNETD_GLOBAL_PEX_PORT 51819
+#define PEX_BUF_SIZE 1024
+#define UNETD_NET_DATA_SIZE_MAX (128 * 1024)
+
+enum pex_opcode {
+ PEX_MSG_HELLO,
+ PEX_MSG_NOTIFY_PEERS,
+ PEX_MSG_QUERY,
+ PEX_MSG_PING,
+ PEX_MSG_PONG,
+ PEX_MSG_UPDATE_REQUEST,
+ PEX_MSG_UPDATE_RESPONSE,
+ PEX_MSG_UPDATE_RESPONSE_DATA,
+ PEX_MSG_UPDATE_RESPONSE_NO_DATA,
+};
+
+#define PEX_ID_LEN 8
+
+struct pex_hdr {
+ uint8_t version;
+ uint8_t opcode;
+ uint16_t len;
+ uint8_t id[PEX_ID_LEN];
+};
+
+struct pex_ext_hdr {
+ uint64_t nonce;
+ uint8_t auth_id[PEX_ID_LEN];
+};
+
+#define PEER_EP_F_IPV6 (1 << 0)
+#define PEER_EP_F_LOCAL (1 << 1)
+
+struct pex_peer_endpoint {
+ uint16_t flags;
+ uint16_t port;
+ uint8_t peer_id[PEX_ID_LEN];
+ uint8_t addr[16];
+};
+
+struct pex_hello {
+ uint16_t flags;
+ uint8_t local_addr[16];
+};
+
+struct pex_update_request {
+ uint64_t req_id; /* must be first */
+ uint64_t cur_version;
+};
+
+struct pex_update_response {
+ uint64_t req_id; /* must be first */
+ uint32_t data_len;
+ uint8_t e_key[CURVE25519_KEY_SIZE];
+};
+
+struct pex_update_response_data {
+ uint64_t req_id; /* must be first */
+ uint32_t offset;
+};
+
+struct pex_update_response_no_data {
+ uint64_t req_id; /* must be first */
+ uint64_t cur_version;
+};
+
+struct pex_msg_update_send_ctx {
+ const uint8_t *pubkey;
+ const uint8_t *auth_key;
+ uint64_t req_id;
+ bool ext;
+
+ void *data;
+ void *cur;
+ int rem;
+};
+
+typedef void (*pex_recv_cb_t)(struct pex_hdr *hdr, struct sockaddr_in6 *addr);
+
+int pex_open(void *addr, size_t addr_len, pex_recv_cb_t cb, bool server);
+void pex_close(void);
+
+uint64_t pex_network_hash(const uint8_t *auth_key, uint64_t req_id);
+struct pex_hdr *__pex_msg_init(const uint8_t *pubkey, uint8_t opcode);
+struct pex_hdr *__pex_msg_init_ext(const uint8_t *pubkey, const uint8_t *auth_key,
+ uint8_t opcode, bool ext);
+int __pex_msg_send(int fd, const void *addr);
+void *pex_msg_append(size_t len);
+
+struct pex_update_request *
+pex_msg_update_request_init(const uint8_t *pubkey, const uint8_t *priv_key,
+ const uint8_t *auth_key, union network_endpoint *addr,
+ uint64_t cur_version, bool ext);
+void *pex_msg_update_response_recv(const void *data, int len, enum pex_opcode op,
+ int *data_len, uint64_t *timestamp);
+
+void pex_msg_update_response_init(struct pex_msg_update_send_ctx *ctx,
+ const uint8_t *pubkey, const uint8_t *auth_key,
+ const uint8_t *peer_key, bool ext,
+ struct pex_update_request *req,
+ const void *data, int len);
+bool pex_msg_update_response_continue(struct pex_msg_update_send_ctx *ctx);
+
+#endif
#include <arpa/inet.h>
#include <fcntl.h>
#include <stdlib.h>
-#include <time.h>
+#include <inttypes.h>
#include "unetd.h"
-
-#define PEX_BUF_SIZE 1024
-
-enum pex_opcode {
- PEX_MSG_HELLO,
- PEX_MSG_NOTIFY_PEERS,
- PEX_MSG_QUERY,
- PEX_MSG_PING,
- PEX_MSG_PONG,
-};
-
-#define PEX_ID_LEN 8
-
-struct pex_hdr {
- uint8_t version;
- uint8_t opcode;
- uint16_t len;
- uint8_t id[PEX_ID_LEN];
-};
-
-#define PEER_EP_F_IPV6 (1 << 0)
-#define PEER_EP_F_LOCAL (1 << 1)
-
-struct pex_peer_endpoint {
- uint16_t flags;
- uint16_t port;
- uint8_t peer_id[PEX_ID_LEN];
- uint8_t addr[16];
-};
-
-struct pex_hello {
- uint16_t flags;
- uint8_t local_addr[16];
-};
-
-static char tx_buf[PEX_BUF_SIZE];
+#include "pex-msg.h"
static const char *pex_peer_id_str(const uint8_t *key)
{
return str;
}
+static struct pex_hdr *
+pex_msg_init(struct network *net, uint8_t opcode)
+{
+ return __pex_msg_init(net->config.pubkey, opcode);
+}
+
+static struct pex_hdr *
+pex_msg_init_ext(struct network *net, uint8_t opcode, bool ext)
+{
+ return __pex_msg_init_ext(net->config.pubkey, net->config.auth_key, opcode, ext);
+}
static struct network_peer *
pex_msg_peer(struct network *net, const uint8_t *id)
return peer;
}
-static struct pex_hdr *pex_msg_init(struct network *net, uint8_t opcode)
+static void
+pex_get_peer_addr(struct sockaddr_in6 *sin6, struct network *net,
+ struct network_peer *peer)
{
- struct pex_hdr *hdr = (struct pex_hdr *)tx_buf;
-
- hdr->version = 0;
- hdr->opcode = opcode;
- hdr->len = 0;
- memcpy(hdr->id, net->config.pubkey, sizeof(hdr->id));
-
- return hdr;
+ *sin6 = (struct sockaddr_in6){
+ .sin6_family = AF_INET6,
+ .sin6_addr = peer->local_addr.in6,
+ .sin6_port = htons(net->net_config.pex_port),
+ };
}
-static void *pex_msg_append(size_t len)
+static void pex_msg_send(struct network *net, struct network_peer *peer)
{
- struct pex_hdr *hdr = (struct pex_hdr *)tx_buf;
- int ofs = hdr->len + sizeof(struct pex_hdr);
- void *buf = &tx_buf[ofs];
-
- if (sizeof(tx_buf) - ofs < len)
- return NULL;
+ struct sockaddr_in6 sin6 = {};
- hdr->len += len;
- memset(buf, 0, len);
+ if (!peer || peer == &net->net_config.local_host->peer || !peer->state.connected)
+ return;
- return buf;
+ pex_get_peer_addr(&sin6, net, peer);
+ if (__pex_msg_send(net->pex.fd.fd, &sin6) < 0)
+ D_PEER(net, peer, "pex_msg_send failed: %s", strerror(errno));
}
-static void pex_msg_send(struct network *net, struct network_peer *peer)
+static void pex_msg_send_ext(struct network *net, struct network_peer *peer,
+ struct sockaddr_in6 *addr)
{
- struct sockaddr_in6 sin6 = {};
- struct pex_hdr *hdr = (struct pex_hdr *)tx_buf;
- size_t tx_len = sizeof(*hdr) + hdr->len;
- int ret;
+ char addrbuf[INET6_ADDRSTRLEN];
- if (peer == &net->net_config.local_host->peer || !peer->state.connected)
- return;
+ if (!addr)
+ return pex_msg_send(net, peer);
- sin6.sin6_family = AF_INET6;
- memcpy(&sin6.sin6_addr, &peer->local_addr.in6,
- sizeof(peer->local_addr.in6));
- sin6.sin6_port = htons(net->net_config.pex_port);
- hdr->len = htons(hdr->len);
- ret = sendto(net->pex.fd.fd, tx_buf, tx_len, 0, (struct sockaddr *)&sin6, sizeof(sin6));
- hdr->len = ntohs(hdr->len);
- if (ret < 0)
- D_PEER(net, peer, "pex_msg_send failed: %s", strerror(errno));
+ if (__pex_msg_send(-1, addr) < 0)
+ D_NET(net, "pex_msg_send_ext(%s) failed: %s",
+ inet_ntop(addr->sin6_family, (const void *)&addr->sin6_addr, addrbuf,
+ sizeof(addrbuf)),
+ strerror(errno));
}
static void
pex_msg_send(net, peer);
}
-
static int
pex_msg_add_peer_endpoint(struct network *net, struct network_peer *peer,
struct network_peer *receiver)
}
}
+static void
+network_pex_host_request_update(struct network *net, struct network_pex_host *host)
+{
+ char addrstr[INET6_ADDRSTRLEN];
+ uint64_t version = 0;
+
+ if (net->net_data_len)
+ version = net->net_data_version;
+
+ D("request network data from host %s",
+ inet_ntop(host->endpoint.sa.sa_family,
+ (host->endpoint.sa.sa_family == AF_INET6 ?
+ (const void *)&host->endpoint.in6.sin6_addr :
+ (const void *)&host->endpoint.in.sin_addr),
+ addrstr, sizeof(addrstr)));
+
+ if (!pex_msg_update_request_init(net->config.pubkey, net->config.key,
+ net->config.auth_key, &host->endpoint,
+ version, true))
+ return;
+ __pex_msg_send(-1, &host->endpoint);
+}
+
+static void
+network_pex_request_update_cb(struct uloop_timeout *t)
+{
+ struct network *net = container_of(t, struct network, pex.request_update_timer);
+ struct network_pex *pex = &net->pex;
+ struct network_pex_host *host;
+
+ uloop_timeout_set(t, 5000);
+
+ if (list_empty(&pex->hosts))
+ return;
+
+ host = list_first_entry(&pex->hosts, struct network_pex_host, list);
+ list_move_tail(&host->list, &pex->hosts);
+ network_pex_host_request_update(net, host);
+}
+
void network_pex_init(struct network *net)
{
struct network_pex *pex = &net->pex;
memset(pex, 0, sizeof(*pex));
pex->fd.fd = -1;
+ INIT_LIST_HEAD(&pex->hosts);
+ pex->request_update_timer.cb = network_pex_request_update_cb;
}
static void
pex_msg_send(net, peer);
}
+static void
+network_pex_send_update_request(struct network *net, struct network_peer *peer,
+ struct sockaddr_in6 *addr)
+{
+ union network_endpoint ep = {};
+ uint64_t version = 0;
+
+ if (addr)
+ memcpy(&ep.in6, addr, sizeof(ep.in6));
+ else
+ pex_get_peer_addr(&ep.in6, net, peer);
+
+ if (net->net_data_len)
+ version = net->net_data_version;
+
+ if (!pex_msg_update_request_init(net->config.pubkey, net->config.key,
+ net->config.auth_key, &ep,
+ version, !!addr))
+ return;
+
+ pex_msg_send_ext(net, peer, addr);
+}
+
void network_pex_event(struct network *net, struct network_peer *peer,
enum pex_event ev)
{
switch (ev) {
case PEX_EV_HANDSHAKE:
pex_send_hello(net, peer);
+ if (net->config.type == NETWORK_TYPE_DYNAMIC)
+ network_pex_send_update_request(net, peer, NULL);
break;
case PEX_EV_ENDPOINT_CHANGE:
network_pex_handle_endpoint_change(net, peer);
pex_msg_send(net, peer);
}
+static void
+network_pex_recv_update_request(struct network *net, struct network_peer *peer,
+ const uint8_t *data, size_t len,
+ struct sockaddr_in6 *addr)
+{
+ struct pex_update_request *req = (struct pex_update_request *)data;
+ struct pex_msg_update_send_ctx ctx = {};
+ uint64_t req_version = be64_to_cpu(req->cur_version);
+ int *query_count;
+ bool done = false;
+
+ if (len < sizeof(struct pex_update_request))
+ return;
+
+ if (net->config.type != NETWORK_TYPE_DYNAMIC)
+ return;
+
+ if (peer)
+ query_count = &peer->state.num_net_queries;
+ else
+ query_count = &net->num_net_queries;
+
+ if (++*query_count > 10)
+ return;
+
+ D("receive update request, local version=%"PRIu64", remote version=%"PRIu64, net->net_data_version, req_version);
+
+ if (req_version >= net->net_data_version) {
+ struct pex_update_response_no_data *res;
+
+ pex_msg_init_ext(net, PEX_MSG_UPDATE_RESPONSE_NO_DATA, !!addr);
+ res = pex_msg_append(sizeof(*res));
+ res->req_id = req->req_id;
+ res->cur_version = cpu_to_be64(net->net_data_version);
+ pex_msg_send_ext(net, peer, addr);
+ }
+
+ if (req_version > net->net_data_version)
+ network_pex_send_update_request(net, peer, addr);
+
+ if (!peer || !net->net_data_len)
+ return;
+
+ if (req_version >= net->net_data_version)
+ return;
+
+ pex_msg_update_response_init(&ctx, net->config.pubkey, net->config.auth_key,
+ peer->key, !!addr, (void *)data,
+ net->net_data, net->net_data_len);
+ while (!done) {
+ pex_msg_send_ext(net, peer, addr);
+ done = !pex_msg_update_response_continue(&ctx);
+ }
+}
+
+static void
+network_pex_recv_update_response(struct network *net, const uint8_t *data, size_t len,
+ struct sockaddr_in6 *addr, enum pex_opcode op)
+{
+ struct network_peer *peer;
+ void *net_data;
+ int net_data_len = 0;
+ uint64_t version = 0;
+ bool no_prev_data = !net->net_data_len;
+
+ if (net->config.type != NETWORK_TYPE_DYNAMIC)
+ return;
+
+ net_data = pex_msg_update_response_recv(data, len, op, &net_data_len, &version);
+ if (!net_data)
+ return;
+
+ if (version <= net->net_data_version) {
+ free(net_data);
+ return;
+ }
+
+ D_NET(net, "received updated network data, len=%d", net_data_len);
+ free(net->net_data);
+
+ net->net_data = net_data;
+ net->net_data_len = net_data_len;
+ net->net_data_version = version;
+ if (network_save_dynamic(net) < 0)
+ return;
+
+ uloop_timeout_set(&net->reload_timer, no_prev_data ? 1 : UNETD_DATA_UPDATE_DELAY);
+ vlist_for_each_element(&net->peers, peer, node) {
+ if (!peer->state.connected)
+ continue;
+ network_pex_send_update_request(net, peer, NULL);
+ }
+}
+
static void
network_pex_recv(struct network *net, struct network_peer *peer, struct pex_hdr *hdr)
{
break;
case PEX_MSG_PONG:
break;
+ case PEX_MSG_UPDATE_REQUEST:
+ network_pex_recv_update_request(net, peer, data, hdr->len,
+ NULL);
+ break;
+ case PEX_MSG_UPDATE_RESPONSE:
+ case PEX_MSG_UPDATE_RESPONSE_DATA:
+ case PEX_MSG_UPDATE_RESPONSE_NO_DATA:
+ network_pex_recv_update_response(net, data, hdr->len,
+ NULL, hdr->opcode);
+ break;
}
}
}
}
+static void
+network_pex_create_host(struct network *net, union network_endpoint *ep)
+{
+ struct network_pex *pex = &net->pex;
+ struct network_pex_host *host;
+
+ host = calloc(1, sizeof(*host));
+ memcpy(&host->endpoint, ep, sizeof(host->endpoint));
+ list_add_tail(&host->list, &pex->hosts);
+ network_pex_host_request_update(net, host);
+}
+
+static void
+network_pex_open_auth_connect(struct network *net)
+{
+ struct network_pex *pex = &net->pex;
+ struct network_peer *peer;
+ struct blob_attr *cur;
+ int rem;
+
+ if (net->config.type != NETWORK_TYPE_DYNAMIC)
+ return;
+
+ uloop_timeout_set(&pex->request_update_timer, 5000);
+
+ vlist_for_each_element(&net->peers, peer, node) {
+ union network_endpoint ep = {};
+
+ if (!peer->endpoint)
+ continue;
+
+ if (network_get_endpoint(&ep, peer->endpoint,
+ UNETD_GLOBAL_PEX_PORT, 0) < 0)
+ continue;
+
+ ep.in.sin_port = htons(UNETD_GLOBAL_PEX_PORT);
+ network_pex_create_host(net, &ep);
+ }
+
+ if (!net->config.auth_connect)
+ return;
+
+ blobmsg_for_each_attr(cur, net->config.auth_connect, rem) {
+ union network_endpoint ep = {};
+
+ if (network_get_endpoint(&ep, blobmsg_get_string(cur),
+ UNETD_GLOBAL_PEX_PORT, 0) < 0)
+ continue;
+
+ network_pex_create_host(net, &ep);
+ }
+}
+
+
int network_pex_open(struct network *net)
{
struct network_host *local_host = net->net_config.local_host;
int yes = 1;
int fd;
+ network_pex_open_auth_connect(net);
+
if (!local_host || !net->net_config.pex_port)
return 0;
void network_pex_close(struct network *net)
{
struct network_pex *pex = &net->pex;
+ struct network_pex_host *host, *tmp;
+
+ uloop_timeout_cancel(&pex->request_update_timer);
+ list_for_each_entry_safe(host, tmp, &pex->hosts, list) {
+ list_del(&host->list);
+ free(host);
+ }
if (pex->fd.fd < 0)
return;
close(pex->fd.fd);
network_pex_init(net);
}
+
+static struct network *
+global_pex_find_network(const uint8_t *id)
+{
+ struct network *net;
+
+ avl_for_each_element(&networks, net, node) {
+ if (!memcmp(id, net->config.auth_key, PEX_ID_LEN))
+ return net;
+ }
+
+ return NULL;
+}
+
+static void
+global_pex_recv(struct pex_hdr *hdr, struct sockaddr_in6 *addr)
+{
+ struct pex_ext_hdr *ehdr = (void *)(hdr + 1);
+ struct network_peer *peer;
+ struct network *net;
+ void *data = (void *)(ehdr + 1);
+
+ if (hdr->version != 0)
+ return;
+
+ net = global_pex_find_network(ehdr->auth_id);
+ if (!net || net->config.type != NETWORK_TYPE_DYNAMIC)
+ return;
+
+ *(uint64_t *)hdr->id ^= pex_network_hash(net->config.auth_key, ehdr->nonce);
+
+ D("PEX global rx op=%d", hdr->opcode);
+ switch (hdr->opcode) {
+ case PEX_MSG_HELLO:
+ case PEX_MSG_NOTIFY_PEERS:
+ case PEX_MSG_QUERY:
+ case PEX_MSG_PING:
+ case PEX_MSG_PONG:
+ break;
+ case PEX_MSG_UPDATE_REQUEST:
+ peer = pex_msg_peer(net, hdr->id);
+ network_pex_recv_update_request(net, peer, data, hdr->len,
+ addr);
+ break;
+ case PEX_MSG_UPDATE_RESPONSE:
+ case PEX_MSG_UPDATE_RESPONSE_DATA:
+ case PEX_MSG_UPDATE_RESPONSE_NO_DATA:
+ network_pex_recv_update_response(net, data, hdr->len, addr, hdr->opcode);
+ break;
+ }
+}
+
+int global_pex_open(void)
+{
+ struct sockaddr_in6 sin6 = {};
+
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_port = htons(global_pex_port);
+
+ return pex_open(&sin6, sizeof(sin6), global_pex_recv, true);
+}
struct network;
+struct network_pex_host {
+ struct list_head list;
+ union network_endpoint endpoint;
+};
+
struct network_pex {
struct uloop_fd fd;
+ struct list_head hosts;
+ struct uloop_timeout request_update_timer;
};
enum pex_event {
return pex->fd.fd >= 0;
}
+int global_pex_open(void);
+
#endif
#include "utils.h"
#include "siphash.h"
#include "wg.h"
+#include "pex-msg.h"
#include "pex.h"
#include "network.h"
#include "host.h"
#include "service.h"
#include "ubus.h"
#include "auth-data.h"
+#include "chacha20.h"
extern const char *mssfix_path;
extern const char *data_dir;
extern bool debug;
+extern int global_pex_port;
#define D(format, ...) \
do { \
#define UNETD_MSS_BPF_PATH "/lib/bpf/mss.o"
#define UNETD_MSS_PRIO_BASE 0x130
+#define UNETD_DATA_UPDATE_DELAY (10 * 1000)
+
void unetd_write_hosts(void);
int unetd_attach_mssfix(int ifindex, int mtu);
*/
#include <sys/types.h>
#include <sys/socket.h>
+#include <sys/stat.h>
#include <arpa/inet.h>
#include <netdb.h>
+#include <stdio.h>
#include "unetd.h"
int network_get_endpoint(union network_endpoint *dest, const char *str,
close(fd);
return ret;
}
+
+void *unet_read_file(const char *name, size_t *len)
+{
+ struct stat st;
+ void *data;
+ FILE *f;
+
+ f = fopen(name, "r");
+ if (!f)
+ goto error;
+
+ if (fstat(fileno(f), &st) < 0)
+ goto close;
+
+ if (*len && st.st_size > *len)
+ goto close;
+
+ data = malloc(st.st_size);
+ if (!data)
+ goto close;
+
+ if (fread(data, 1, st.st_size, f) != st.st_size) {
+ free(data);
+ goto close;
+ }
+ fclose(f);
+
+ *len = st.st_size;
+ return data;
+
+close:
+ fclose(f);
+error:
+ *len = 0;
+ return NULL;
+}
const char *str);
int network_get_local_addr(void *local, const union network_endpoint *target);
+void *unet_read_file(const char *name, size_t *len);
+
#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
#define bitmask_size(len) (4 * DIV_ROUND_UP(len, 32))