add protocol for exchanging signed network data
authorFelix Fietkau <nbd@nbd.name>
Sat, 13 Aug 2022 12:57:43 +0000 (14:57 +0200)
committerFelix Fietkau <nbd@nbd.name>
Tue, 23 Aug 2022 21:20:25 +0000 (23:20 +0200)
Signed-off-by: Felix Fietkau <nbd@nbd.name>
19 files changed:
CMakeLists.txt
PEX.md
auth-data.c
auth-data.h
cli.c
examples/net0.bin
examples/test-net0.sh
host.c
host.h
main.c
network.c
network.h
pex-msg.c [new file with mode: 0644]
pex-msg.h [new file with mode: 0644]
pex.c
pex.h
unetd.h
utils.c
utils.h

index 4c3f67025bafb66298da7f4f477bd02f1ee39ba8..a7e3b8adb535958e8d4252f6f5a507185d65e1d5 100644 (file)
@@ -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 4d42ad4944c77135baeb736ce811d0e7b3d083c0..241c6be9af18e477a471ab45f3d0b030e950efa5 100644 (file)
--- 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
index c5873dba4f82ba75bebc97ac1569a1c5a06b508f..3d6621304705e880e972455da12a4cd2d5cef4c8 100644 (file)
@@ -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);
 
index 111db987e30c3a6a536ca1014ad0c005227ad7d0..ddb73e9b27d9149d73c6c4839249cc926a6bfba4 100644 (file)
@@ -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 371522097c42ccb4747e025506814ace4ca406e3..43b58e3e2382ccbbdb34ce7254afa967dadc77dd 100644 (file)
--- a/cli.c
+++ b/cli.c
@@ -5,19 +5,36 @@
 #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,
@@ -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 <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 = {
@@ -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 <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 {
@@ -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)
index 4249d8517f6d56ea9ab1d7c7307bc2d6b5f37c5c..bcc4f8ca5d9225ceb4dce43dfe5d83bfbbe5989c 100644 (file)
Binary files a/examples/net0.bin and b/examples/net0.bin differ
index 1c5159d09d760cd5dad85cfbc97ca3f371c0b558..449289929bcbb93dca6c495bece544fb795d6d7c 100755 (executable)
@@ -4,7 +4,7 @@ host="${2:-ap1}"
 
 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"'",
diff --git a/host.c b/host.c
index ff3725c519dab54be1ca4296a8368647a71c315a..6f5c0af45f358c07df01b45a1ff9cb5a75e7da5c 100644 (file)
--- 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 76c52de8db6ead245967276578b45c1988261cd8..bf61e75e9f4098c086a4a4375f5273339f5674ec 100644 (file)
--- 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 f3b05d22d288aae2f1f240ac4db4bef6e6652c1e..c9ce132a2cfa616d88f92c619cfd9d2b5cec4c14 100644 (file)
--- 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();
 
index 165c22ca5e61efd9debd8b50834e28f80de78134..80909c40103e9ae74344686f7fd1227827375712 100644 (file)
--- 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);
index 8a824b9547a3b1a70d5adf75e1d1a9f467b7269c..933921aaf3228e340a1370586aa5f8c3f56e134c 100644 (file)
--- 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 (file)
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 <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;
+}
diff --git a/pex-msg.h b/pex-msg.h
new file mode 100644 (file)
index 0000000..7928f02
--- /dev/null
+++ b/pex-msg.h
@@ -0,0 +1,113 @@
+#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
diff --git a/pex.c b/pex.c
index 0e66d24dc58f33b6f7e9723d0296f062d888250d..5bb51ca933aeb7111730dbf76cfc705f9df4b746 100644 (file)
--- a/pex.c
+++ b/pex.c
@@ -7,44 +7,9 @@
 #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)
 {
@@ -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 aaf828f3caf35ebf9d6af0bae107c5790e8e117c..11f783fc398d6846401a430829efbf8aa07b1570 100644 (file)
--- 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 84e8a06f68d8162d80463b0409fc53130bf0a84a..6b9ce926293f51c934a7ecf4e10cc218abf1b2a2 100644 (file)
--- a/unetd.h
+++ b/unetd.h
 #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 083e4527b01eb11abaa43c1cb661667ec33f576c..95ab08bb570b3d9360d96876eeb1a640242f4c48 100644 (file)
--- a/utils.c
+++ b/utils.c
@@ -4,8 +4,10 @@
  */
 #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,
@@ -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 688f86c8370ecd914974ef8335a4de01ee84ed91..94cf057d367a2b1ef946d1e92295c163a5707bc0 100644 (file)
--- 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))