From bf43cce3830426f5a4faf78dc38d02cc063e0263 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sat, 13 Aug 2022 14:57:43 +0200 Subject: [PATCH] add protocol for exchanging signed network data Signed-off-by: Felix Fietkau --- CMakeLists.txt | 4 +- PEX.md | 79 ++++++++ auth-data.c | 7 +- auth-data.h | 2 +- cli.c | 379 +++++++++++++++++++++++++++++++---- examples/net0.bin | Bin 909 -> 909 bytes examples/test-net0.sh | 2 +- host.c | 4 + host.h | 1 + main.c | 8 +- network.c | 70 ++++++- network.h | 7 + pex-msg.c | 447 ++++++++++++++++++++++++++++++++++++++++++ pex-msg.h | 113 +++++++++++ pex.c | 406 +++++++++++++++++++++++++++++++------- pex.h | 9 + unetd.h | 5 + utils.c | 38 ++++ utils.h | 2 + 19 files changed, 1457 insertions(+), 126 deletions(-) create mode 100644 pex-msg.c create mode 100644 pex-msg.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c3f670..a7e3b8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ PROJECT(unetd C) 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 ) @@ -30,7 +30,7 @@ ELSE() 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}) diff --git a/PEX.md b/PEX.md index 4d42ad4..241c6be 100644 --- a/PEX.md +++ b/PEX.md @@ -76,3 +76,82 @@ No payload. 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 diff --git a/auth-data.c b/auth-data.c index c5873db..3d66213 100644 --- a/auth-data.c +++ b/auth-data.c @@ -7,7 +7,7 @@ #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); @@ -23,7 +23,7 @@ int unet_auth_data_validate(const uint8_t *key, const void *buf, size_t len, 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); @@ -34,6 +34,9 @@ int unet_auth_data_validate(const uint8_t *key, const void *buf, size_t len, 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); diff --git a/auth-data.h b/auth-data.h index 111db98..ddb73e9 100644 --- a/auth-data.h +++ b/auth-data.h @@ -30,7 +30,7 @@ struct unet_auth_data { } __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) diff --git a/cli.c b/cli.c index 3715220..43b58e3 100644 --- a/cli.c +++ b/cli.c @@ -5,19 +5,36 @@ #include #include #include +#include +#include +#include #include #include #include #include +#include #include +#include +#include +#include #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, @@ -25,8 +42,17 @@ static enum { 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)]; @@ -46,15 +72,232 @@ static int usage(const char *progname) " -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 [:] Download network data from unetd\n" + " -U [:] Upload network data to unetd\n" "\n" "Options:\n" + " -q: Quiet mode - suppress error/info messages\n" " -o : Set output file to (defaults to stdout)\n" " -k |-: Set public key from file or stdin\n" " -K |-: Set secret key from file or stdin\n" + " -h |- 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 = { @@ -67,18 +310,19 @@ static int cmd_sign(int argc, char **argv) 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; } @@ -88,11 +332,11 @@ static int cmd_sign(int argc, char **argv) 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); @@ -115,18 +359,18 @@ static int cmd_verify(int argc, char **argv) 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; } @@ -136,20 +380,20 @@ static int cmd_verify(int argc, char **argv) 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; } @@ -179,7 +423,7 @@ static int cmd_generate(int argc, char **argv) f = fopen("/dev/urandom", "r"); if (!f) { - fprintf(stderr, "Can't open /dev/urandom\n"); + INFO("Can't open /dev/urandom\n"); return 1; } @@ -187,7 +431,7 @@ static int cmd_generate(int argc, char **argv) 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; } @@ -209,7 +453,7 @@ static bool parse_key(uint8_t *dest, const char *str) 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; } @@ -220,16 +464,27 @@ static bool parse_key(uint8_t *dest, const char *str) 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: @@ -249,18 +504,61 @@ static bool cmd_needs_key(void) } } +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); @@ -284,34 +582,27 @@ int main(int argc, char **argv) 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: @@ -319,23 +610,28 @@ int main(int argc, char **argv) } } + if (!has_peerkey && cmd_needs_peerkey()) { + INFO("Missing -h argument\n"); + return 1; + } + if (!has_key && cmd_needs_key()) { - fprintf(stderr, "Missing -K argument\n"); + INFO("Missing -K argument\n"); return 1; } if (!has_pubkey && cmd_needs_pubkey()) { - fprintf(stderr, "Missing -k argument\n"); + INFO("Missing -k 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 { @@ -344,6 +640,10 @@ int main(int argc, char **argv) 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; @@ -364,6 +664,11 @@ int main(int argc, char **argv) break; } + if (net_data) + free(net_data); + + blob_buf_free(&b); + if (out_file != stdout) { fclose(out_file); if (ret) diff --git a/examples/net0.bin b/examples/net0.bin index 4249d8517f6d56ea9ab1d7c7307bc2d6b5f37c5c..bcc4f8ca5d9225ceb4dce43dfe5d83bfbbe5989c 100644 GIT binary patch delta 89 zcmeBW?`01t^-C>b00M#S&(~|;e4xMb#;Z!d-aS>0yBDmmJ*fW8I{3Zdsb80G=O-3* t8l|pGo-l2K){Qz9nWC77xkb0D`>dxr}-~`%_yA7YK /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"'", diff --git a/host.c b/host.c index ff3725c..6f5c0af 100644 --- a/host.c +++ b/host.c @@ -230,6 +230,10 @@ network_hosts_connect_cb(struct uloop_timeout *t) 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; diff --git a/host.h b/host.h index 76c52de..bf61e75 100644 --- a/host.h +++ b/host.h @@ -29,6 +29,7 @@ struct network_peer { uint64_t last_handshake; uint64_t last_request; int idle; + int num_net_queries; } state; }; diff --git a/main.c b/main.c index f3b05d2..c9ce132 100644 --- a/main.c +++ b/main.c @@ -18,6 +18,7 @@ static struct cmdline_network *cmd_nets; 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 @@ -98,7 +99,7 @@ int main(int argc, char **argv) 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; @@ -118,14 +119,19 @@ int main(int argc, char **argv) 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(); diff --git a/network.c b/network.c index 165c22c..80909c4 100644 --- a/network.c +++ b/network.c @@ -53,6 +53,7 @@ const struct blobmsg_policy network_policy[__NETWORK_ATTR_MAX] = { [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); @@ -143,7 +144,7 @@ static int network_load_dynamic(struct network *net) 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; } @@ -162,6 +163,53 @@ 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) { @@ -334,9 +382,9 @@ 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; @@ -349,13 +397,13 @@ static int network_reload(struct network *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: - ret = network_load_dynamic(net); + network_load_dynamic(net); break; } @@ -368,8 +416,6 @@ static int network_reload(struct network *net) unetd_write_hosts(); network_do_update(net, true); network_pex_open(net); - - return ret; } static int network_setup(struct network *net) @@ -390,6 +436,7 @@ 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); @@ -475,6 +522,10 @@ network_set_config(struct network *net, struct blob_attr *config) 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; @@ -488,7 +539,7 @@ network_set_config(struct network *net, struct blob_attr *config) goto invalid; reload: - network_reload(net); + network_reload(&net->reload_timer); return 0; @@ -505,6 +556,7 @@ 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); diff --git a/network.h b/network.h index 8a824b9..933921a 100644 --- a/network.h +++ b/network.h @@ -37,6 +37,7 @@ struct network { const char *domain; struct blob_attr *tunnels; struct blob_attr *net_data; + struct blob_attr *auth_connect; } config; struct { @@ -50,6 +51,10 @@ struct network { 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; @@ -76,6 +81,7 @@ enum { NETWORK_ATTR_KEEPALIVE, NETWORK_ATTR_DOMAIN, NETWORK_ATTR_TUNNELS, + NETWORK_ATTR_AUTH_CONNECT, __NETWORK_ATTR_MAX, }; @@ -88,6 +94,7 @@ static inline const char *network_name(struct network *net) } 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); diff --git a/pex-msg.c b/pex-msg.c new file mode 100644 index 0000000..16c1acb --- /dev/null +++ b/pex-msg.c @@ -0,0 +1,447 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2022 Felix Fietkau + */ +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/pex-msg.h b/pex-msg.h new file mode 100644 index 0000000..7928f02 --- /dev/null +++ b/pex-msg.h @@ -0,0 +1,113 @@ +#ifndef __PEX_MSG_H +#define __PEX_MSG_H + +#include +#include +#include +#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 diff --git a/pex.c b/pex.c index 0e66d24..5bb51ca 100644 --- a/pex.c +++ b/pex.c @@ -7,44 +7,9 @@ #include #include #include -#include +#include #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) { @@ -57,6 +22,17 @@ 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) @@ -74,52 +50,42 @@ 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 @@ -137,7 +103,6 @@ pex_send_hello(struct network *net, struct network_peer *peer) pex_msg_send(net, peer); } - static int pex_msg_add_peer_endpoint(struct network *net, struct network_peer *peer, struct network_peer *receiver) @@ -195,12 +160,54 @@ network_pex_handle_endpoint_change(struct network *net, struct network_peer *pee } } +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 @@ -264,6 +271,29 @@ network_pex_send_ping(struct network *net, struct network_peer *peer) 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) { @@ -278,6 +308,8 @@ void network_pex_event(struct network *net, struct network_peer *peer, 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); @@ -382,6 +414,100 @@ network_pex_recv_ping(struct network *net, struct network_peer *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) { @@ -406,6 +532,16 @@ network_pex_recv(struct network *net, struct network_peer *peer, struct pex_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; } } @@ -460,6 +596,60 @@ network_pex_fd_cb(struct uloop_fd *fd, unsigned int events) } } +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; @@ -469,6 +659,8 @@ int network_pex_open(struct network *net) int yes = 1; int fd; + network_pex_open_auth_connect(net); + if (!local_host || !net->net_config.pex_port) return 0; @@ -511,6 +703,13 @@ close: 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; @@ -519,3 +718,64 @@ void network_pex_close(struct network *net) 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); +} diff --git a/pex.h b/pex.h index aaf828f..11f783f 100644 --- a/pex.h +++ b/pex.h @@ -9,8 +9,15 @@ 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 { @@ -32,4 +39,6 @@ static inline bool network_pex_active(struct network_pex *pex) return pex->fd.fd >= 0; } +int global_pex_open(void); + #endif diff --git a/unetd.h b/unetd.h index 84e8a06..6b9ce92 100644 --- a/unetd.h +++ b/unetd.h @@ -13,16 +13,19 @@ #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 { \ @@ -40,6 +43,8 @@ extern bool debug; #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); diff --git a/utils.c b/utils.c index 083e452..95ab08b 100644 --- a/utils.c +++ b/utils.c @@ -4,8 +4,10 @@ */ #include #include +#include #include #include +#include #include "unetd.h" int network_get_endpoint(union network_endpoint *dest, const char *str, @@ -141,3 +143,39 @@ out: 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; +} diff --git a/utils.h b/utils.h index 688f86c..94cf057 100644 --- a/utils.h +++ b/utils.h @@ -59,6 +59,8 @@ int network_get_subnet(int af, union network_addr *addr, int *mask, 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)) -- 2.30.2