--- /dev/null
+# Configuration files
+
+There currently are three different types of configuration files in MeshLink:
+
+- the main configuration file
+- the host config files
+- pending invitation files
+
+All configuration files are in PackMessage format. The contents will be
+described below.
+
+There are three different ways envisioned to store configuration data:
+
+- unencrypted files
+- encrypted files
+- ephemeral, in-memory storage
+
+To keep things as simple and flexible as possible, there are some restrictions:
+
+- Configuration files are read from and written to memory in one go.
+ When a configuration files is changed, it is not modified in place,
+ but has to be written from scratch.
+- When in-memory, functions from `packmsg.h` can be used to parse and generate the configuration data.
+- Configuration files are read and written only via functions declared in `conf.h`.
+ This also includes creating and destroying the configuration directory.
+
+## Main configuration file
+
+When stored on disk, it's in `meshlink.conf`. The contents are:
+
+- uint32: configuration format version
+- str: name of the local node
+- bin: private Ed25519 key
+- bin: private Ed25519 key for invitations
+- uint16: port number of the local node
+
+More information about the local node is stored in its host config file.
+
+## Host configuration files
+
+When stored on disk, there is one file per node in the mesh, inside the directory `hosts/`.
+The contents of a host configuration are:
+
+- uint32: configuration format version
+- str: name of the node
+- int32: device class
+- bool: blacklisted
+- bin: public Ed25519 key
+- str: canonical address (may be zero length if unset)
+- arr[ext]: recent addresses
+
+## Invitation files
+
+When stored on disk, there is one file per pending invitation, inside the directory `invitations/`.
+The contents of an invitation file are:
+
+- uint32: invitation format version
+- str: name of the invitee
+- int32: device class of the invitee (may be unused)
+- arr[bin]: one or more host config files
+
+## Encryption
+
+When encryption is enabled, each file is individually encrypted using Chacha20-Poly1305.
+A unique counter stored at the start of the file. A (master) key must be provided by the application.
+
+## Exporting configuration
+
+Calling `meshlink_export()` will return an array of host config files:
+
+- arr[bin]: one or more host config files
+
+## Loading, saving and unloading configuration data to/from memory
+
+### Main configuration file
+
+Created during first setup.
+Loaded at start.
+Never unloaded.
+Might have to be saved again when port number or private key for invitations changes.
+
+### Host configuration files
+
+Can be loaded partially into memory:
+- devclass+blacklist status only, always required in memory, so loaded in `load_all_nodes()`.
+- public key, needed whenever SPTPS is required, or when the application wants a fingerprint or verify signed data from that node.
+- canonical and recent addresses, needed whenever we want to make outgoing connections to this node.
+
+Furthermore, in order to properly merge new data, we need to load the whole config file into memory when:
+- updating recent addresses
+- exporting this node's information (no API for this yet)
+
+Whenever a node's information is updated, we mark it dirty. It is written out at a convenient time.
+
+
--- /dev/null
+Master key is a public/private keypair (RSA? We don't really care.)
+
+File format:
+<encrypted chacha-poly1305 key>
+<configuration-data>
+
+++ /dev/null
-MeshLink is based on tinc, and tinc had the following structure for its
-directory with configuration files:
-
-/tinc.conf Main configuration file
-/tinc-up Script to configure the virtual network interface
-/ecdsa_key.priv This node's private key
-/hosts/node1 Host configuration file, containing the node's public key,
- address, and perhaps other information.
-/hosts/node2 Another host's configuration file,
-/hosts/... et cetera.
-
-For MeshLink, we don't have any scripts, and neither the application nor the
-user is supposed to read or edit the configuration files in any way. Therefore,
-it might be a good idea to simplify them to the point that we are left
-with:
-
-/meshlink.conf Contains only the local node's Name and Port.
-/ecdsa_key.priv Contains the private key, in binary.
-/nodes/node1 Contains only the public key and a list of known addresses.
-/nodes/node2 Another node's key and addresses,
-/nodes/... et cetera.
-
-Example /meshlink.conf:
-
-Name = foo
-Port = 12345
-
-Example /nodes/foo:
-
-PublicKey = 19fj193f12d1m02dj12089cn
-Address = foo.example.com 18529
-Address = 93.184.216.119 18529
-Address = 2606:2800:220:6d:26bf:1447:1097:aa7 18529
return true;
}
+
+bool chacha_poly1305_encrypt_iv96(chacha_poly1305_ctx_t *ctx, const uint8_t *seqbuf, const void *indata, size_t inlen, void *outdata, size_t *outlen) {
+ const uint8_t one[4] = { 1, 0, 0, 0 }; /* NB little-endian */
+ uint8_t poly_key[POLY1305_KEYLEN];
+
+ /*
+ * Run ChaCha20 once to generate the Poly1305 key. The IV is the
+ * packet sequence number.
+ */
+ memset(poly_key, 0, sizeof(poly_key));
+ chacha_ivsetup_96(&ctx->main_ctx, seqbuf, NULL);
+ chacha_encrypt_bytes(&ctx->main_ctx, poly_key, poly_key, sizeof(poly_key));
+
+ /* Set Chacha's block counter to 1 */
+ chacha_ivsetup_96(&ctx->main_ctx, seqbuf, one);
+
+ chacha_encrypt_bytes(&ctx->main_ctx, indata, outdata, inlen);
+ poly1305_auth((uint8_t *)outdata + inlen, outdata, inlen, poly_key);
+
+ if (outlen)
+ *outlen = inlen + POLY1305_TAGLEN;
+
+ return true;
+}
+
+bool chacha_poly1305_decrypt_iv96(chacha_poly1305_ctx_t *ctx, const uint8_t *seqbuf, const void *indata, size_t inlen, void *outdata, size_t *outlen) {
+ const uint8_t one[4] = { 1, 0, 0, 0 }; /* NB little-endian */
+ uint8_t expected_tag[POLY1305_TAGLEN], poly_key[POLY1305_KEYLEN];
+
+ /*
+ * Run ChaCha20 once to generate the Poly1305 key. The IV is the
+ * packet sequence number.
+ */
+ memset(poly_key, 0, sizeof(poly_key));
+ chacha_ivsetup_96(&ctx->main_ctx, seqbuf, NULL);
+ chacha_encrypt_bytes(&ctx->main_ctx, poly_key, poly_key, sizeof(poly_key));
+
+ /* Set Chacha's block counter to 1 */
+ chacha_ivsetup_96(&ctx->main_ctx, seqbuf, one);
+
+ /* Check tag before anything else */
+ inlen -= POLY1305_TAGLEN;
+ const uint8_t *tag = (const uint8_t *)indata + inlen;
+
+ poly1305_auth(expected_tag, indata, inlen, poly_key);
+ if (memcmp(expected_tag, tag, POLY1305_TAGLEN))
+ return false;
+
+ chacha_encrypt_bytes(&ctx->main_ctx, indata, outdata, inlen);
+
+ if (outlen)
+ *outlen = inlen;
+
+ return true;
+}
extern bool chacha_poly1305_encrypt(chacha_poly1305_ctx_t *ctx, uint64_t seqnr, const void *indata, size_t inlen, void *outdata, size_t *outlen);
extern bool chacha_poly1305_decrypt(chacha_poly1305_ctx_t *ctx, uint64_t seqnr, const void *indata, size_t inlen, void *outdata, size_t *outlen);
+extern bool chacha_poly1305_encrypt_iv96(chacha_poly1305_ctx_t *ctx, const uint8_t *seqbuf, const void *indata, size_t inlen, void *outdata, size_t *outlen);
+extern bool chacha_poly1305_decrypt_iv96(chacha_poly1305_ctx_t *ctx, const uint8_t *seqbuf, const void *indata, size_t inlen, void *outdata, size_t *outlen);
+
#endif //CHACHA_POLY1305_H
x->input[15] = U8TO32_LITTLE(iv + 4);
}
+void chacha_ivsetup_96(chacha_ctx *x, const uint8_t *iv, const uint8_t *counter)
+{
+ x->input[12] = counter == NULL ? 0 : U8TO32_LITTLE(counter + 0);
+ x->input[13] = U8TO32_LITTLE(iv + 0);
+ x->input[14] = U8TO32_LITTLE(iv + 4);
+ x->input[15] = U8TO32_LITTLE(iv + 8);
+}
+
void
chacha_encrypt_bytes(chacha_ctx *x, const uint8_t *m, uint8_t *c, uint32_t bytes)
{
/*
- conf.c -- configuration code
- Copyright (C) 2014 Guus Sliepen <guus@meshlink.io>
+ econf.c -- configuration code
+ Copyright (C) 2018 Guus Sliepen <guus@meshlink.io>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include "system.h"
#include <assert.h>
-#include "splay_tree.h"
-#include "connection.h"
#include "conf.h"
-#include "list.h"
+#include "crypto.h"
#include "logger.h"
#include "meshlink_internal.h"
-#include "netutl.h" /* for str2address */
-#include "protocol.h"
-#include "utils.h" /* for cp */
#include "xalloc.h"
+#include "packmsg.h"
-static int config_compare(const config_t *a, const config_t *b) {
- int result;
-
- result = strcasecmp(a->variable, b->variable);
-
- if(result) {
- return result;
- }
-
- return result = a->line - b->line;
-}
-
-void init_configuration(splay_tree_t **config_tree) {
- *config_tree = splay_alloc_tree((splay_compare_t) config_compare, (splay_action_t) free_config);
-}
-
-void exit_configuration(splay_tree_t **config_tree) {
- if(*config_tree) {
- splay_delete_tree(*config_tree);
- }
-
- *config_tree = NULL;
+/// Generate a path to the main configuration file.
+static void make_main_path(meshlink_handle_t *mesh, char *path, size_t len) {
+ snprintf(path, len, "%s" SLASH "meshlink.conf", mesh->confbase);
}
-config_t *new_config(void) {
- return xzalloc(sizeof(config_t));
+/// Generate a path to a host configuration file.
+static void make_host_path(meshlink_handle_t *mesh, const char *name, char *path, size_t len) {
+ snprintf(path, len, "%s" SLASH "hosts" SLASH "%s", mesh->confbase, name);
}
-void free_config(config_t *cfg) {
- free(cfg->variable);
- free(cfg->value);
- free(cfg);
+/// Generate a path to an unused invitation file.
+static void make_invitation_path(meshlink_handle_t *mesh, const char *name, char *path, size_t len) {
+ snprintf(path, len, "%s" SLASH "invitations" SLASH "%s", mesh->confbase, name);
}
-void config_add(splay_tree_t *config_tree, config_t *cfg) {
- splay_insert(config_tree, cfg);
+/// Generate a path to a used invitation file.
+static void make_used_invitation_path(meshlink_handle_t *mesh, const char *name, char *path, size_t len) {
+ snprintf(path, len, "%s" SLASH "invitations" SLASH "%s.used", mesh->confbase, name);
}
-config_t *lookup_config(splay_tree_t *config_tree, char *variable) {
- config_t cfg, *found;
+/// Remove a directory recursively
+static void deltree(const char *dirname) {
+ DIR *d = opendir(dirname);
- cfg.variable = variable;
- cfg.line = 0;
+ if(d) {
+ struct dirent *ent;
- found = splay_search_closest_greater(config_tree, &cfg);
-
- if(!found) {
- return NULL;
- }
-
- if(strcasecmp(found->variable, variable)) {
- return NULL;
- }
-
- return found;
-}
-
-config_t *lookup_config_next(splay_tree_t *config_tree, const config_t *cfg) {
- splay_node_t *node;
- config_t *found;
-
- node = splay_search_node(config_tree, cfg);
+ while((ent = readdir(d))) {
+ if(ent->d_name[0] == '.') {
+ continue;
+ }
- if(node) {
- if(node->next) {
- found = node->next->data;
+ char filename[PATH_MAX];
+ snprintf(filename, sizeof(filename), "%s" SLASH "%s", dirname, ent->d_name);
- if(!strcasecmp(found->variable, cfg->variable)) {
- return found;
+ if(unlink(filename)) {
+ deltree(filename);
}
}
- }
-
- return NULL;
-}
-
-bool get_config_bool(const config_t *cfg, bool *result) {
- if(!cfg) {
- return false;
- }
- if(!strcasecmp(cfg->value, "yes")) {
- *result = true;
- return true;
- } else if(!strcasecmp(cfg->value, "no")) {
- *result = false;
- return true;
+ closedir(d);
}
- logger(NULL, MESHLINK_ERROR, "\"yes\" or \"no\" expected for configuration variable %s in line %d",
- cfg->variable, cfg->line);
-
- return false;
+ rmdir(dirname);
}
-bool get_config_int(const config_t *cfg, int *result) {
- if(!cfg) {
+/// Create a fresh configuration directory
+bool config_init(meshlink_handle_t *mesh) {
+ if(mkdir(mesh->confbase, 0700) && errno != EEXIST) {
+ logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", mesh->confbase, strerror(errno));
return false;
}
- if(sscanf(cfg->value, "%d", result) == 1) {
- return true;
- }
+ char path[PATH_MAX];
- logger(NULL, MESHLINK_ERROR, "Integer expected for configuration variable %s in line %d",
- cfg->variable, cfg->line);
+ // Remove meshlink.conf
+ snprintf(path, sizeof(path), "%s" SLASH "meshlink.conf", mesh->confbase);
+ unlink(path);
- return false;
-}
+ // Remove any host config files
+ snprintf(path, sizeof(path), "%s" SLASH "hosts", mesh->confbase);
+ deltree(path);
-bool set_config_int(config_t *cfg, int val) {
- if(!cfg) {
+ if(mkdir(path, 0700) && errno != EEXIST) {
+ logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", path, strerror(errno));
return false;
}
- char val_str[1024];
- snprintf(val_str, sizeof(val_str), "%d", val);
+ // Remove any invitation files
+ snprintf(path, sizeof(path), "%s" SLASH "invitations", mesh->confbase);
+ deltree(path);
- if(cfg->value) {
- free(cfg->value);
+ if(mkdir(path, 0700) && errno != EEXIST) {
+ logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", path, strerror(errno));
+ return false;
}
- cfg->value = xstrdup(val_str);
-
return true;
}
-bool get_config_string(const config_t *cfg, char **result) {
- if(!cfg) {
- return false;
+/// Wipe an existing configuration directory
+bool config_destroy(const char *confbase) {
+ char path[PATH_MAX];
+
+ // Remove meshlink.conf
+ snprintf(path, sizeof(path), "%s" SLASH "meshlink.conf", confbase);
+
+ if(unlink(path)) {
+ if(errno == ENOENT) {
+ meshlink_errno = MESHLINK_ENOENT;
+ return false;
+ } else {
+ logger(NULL, MESHLINK_ERROR, "Cannot delete %s: %s\n", path, strerror(errno));
+ meshlink_errno = MESHLINK_ESTORAGE;
+ return false;
+ }
}
- *result = xstrdup(cfg->value);
-
+ deltree(confbase);
return true;
}
-bool set_config_string(config_t *cfg, const char *val) {
- if(!cfg) {
- return false;
- }
-
- if(cfg->value) {
- free(cfg->value);
- }
-
- cfg->value = xstrdup(val);
+/// Check the presence of the main configuration file.
+bool main_config_exists(meshlink_handle_t *mesh) {
+ char path[PATH_MAX];
+ make_main_path(mesh, path, sizeof(path));
- return true;
+ return access(path, F_OK) == 0;
}
-bool get_config_address(const config_t *cfg, struct addrinfo **result) {
- struct addrinfo *ai;
+/// Lock the main configuration file.
+bool main_config_lock(meshlink_handle_t *mesh) {
+ char path[PATH_MAX];
+ make_main_path(mesh, path, sizeof(path));
+
+ mesh->conffile = fopen(path, "r");
- if(!cfg) {
+ if(!mesh->conffile) {
+ logger(NULL, MESHLINK_ERROR, "Cannot not open %s: %s\n", path, strerror(errno));
+ meshlink_errno = MESHLINK_ESTORAGE;
return false;
}
- ai = str2addrinfo(cfg->value, NULL, 0);
+#ifdef FD_CLOEXEC
+ fcntl(fileno(mesh->conffile), F_SETFD, FD_CLOEXEC);
+#endif
+
+#ifdef HAVE_MINGW
+ // TODO: use _locking()?
+#else
- if(ai) {
- *result = ai;
- return true;
+ if(flock(fileno(mesh->conffile), LOCK_EX | LOCK_NB) != 0) {
+ logger(NULL, MESHLINK_ERROR, "Cannot lock %s: %s\n", path, strerror(errno));
+ fclose(mesh->conffile);
+ mesh->conffile = NULL;
+ meshlink_errno = MESHLINK_EBUSY;
+ return false;
}
- logger(NULL, MESHLINK_ERROR, "Hostname or IP address expected for configuration variable %s in line %d",
- cfg->variable, cfg->line);
+#endif
- return false;
+ return true;
}
-/*
- Read exactly one line and strip the trailing newline if any.
-*/
-static char *readline(FILE *fp, char *buf, size_t buflen) {
- char *newline = NULL;
- char *p;
-
- if(feof(fp)) {
- return NULL;
+/// Unlock the main configuration file.
+void main_config_unlock(meshlink_handle_t *mesh) {
+ if(mesh->conffile) {
+ fclose(mesh->conffile);
+ mesh->conffile = NULL;
}
+}
- p = fgets(buf, buflen, fp);
+/// Read a configuration file from a FILE handle.
+bool config_read_file(meshlink_handle_t *mesh, FILE *f, config_t *config) {
+ (void)mesh;
+ long len;
- if(!p) {
- return NULL;
+ if(fseek(f, 0, SEEK_END) || !(len = ftell(f)) || fseek(f, 0, SEEK_SET)) {
+ logger(mesh, MESHLINK_ERROR, "Cannot get config file size: %s\n", strerror(errno));
+ meshlink_errno = MESHLINK_ESTORAGE;
+ fclose(f);
+ return false;
}
- newline = strchr(p, '\n');
+ uint8_t *buf = xmalloc(len);
- if(!newline) {
- return buf;
+ if(fread(buf, len, 1, f) != 1) {
+ logger(mesh, MESHLINK_ERROR, "Cannot read config file: %s\n", strerror(errno));
+ meshlink_errno = MESHLINK_ESTORAGE;
+ fclose(f);
+ return false;
}
- /* kill newline and carriage return if necessary */
- *newline = '\0';
-
- if(newline > p && newline[-1] == '\r') {
- newline[-1] = '\0';
+ if(mesh->config_key) {
+ uint8_t *decrypted = xmalloc(len);
+ size_t decrypted_len = len;
+ chacha_poly1305_ctx_t *ctx = chacha_poly1305_init();
+ chacha_poly1305_set_key(ctx, mesh->config_key);
+
+ if(len > 12 && chacha_poly1305_decrypt_iv96(ctx, buf, buf + 12, len - 12, decrypted, &decrypted_len)) {
+ free(buf);
+ config->buf = decrypted;
+ config->len = decrypted_len;
+ return true;
+ } else {
+ logger(mesh, MESHLINK_ERROR, "Cannot decrypt config file\n");
+ meshlink_errno = MESHLINK_ESTORAGE;
+ free(decrypted);
+ free(buf);
+ return false;
+ }
}
- return buf;
-}
-
-config_t *parse_config_line(char *line, const char *fname, int lineno) {
- config_t *cfg;
- int len;
- char *variable, *value, *eol;
- variable = value = line;
+ config->buf = buf;
+ config->len = len;
- eol = line + strlen(line);
-
- while(strchr("\t ", *--eol)) {
- *eol = '\0';
- }
+ return true;
+}
- len = strcspn(value, "\t =");
- value += len;
- value += strspn(value, "\t ");
+/// Write a configuration file to a FILE handle.
+bool config_write_file(meshlink_handle_t *mesh, FILE *f, const config_t *config) {
+ if(mesh->config_key) {
+ uint8_t buf[config->len + 16];
+ size_t len = sizeof(buf);
+ uint8_t seqbuf[12];
+ randomize(&seqbuf, sizeof(seqbuf));
+ chacha_poly1305_ctx_t *ctx = chacha_poly1305_init();
+ chacha_poly1305_set_key(ctx, mesh->config_key);
+ bool success = false;
+
+ if(chacha_poly1305_encrypt_iv96(ctx, seqbuf, config->buf, config->len, buf, &len)) {
+ success = fwrite(seqbuf, sizeof(seqbuf), 1, f) == 1 && fwrite(buf, len, 1, f) == 1;
+ } else {
+ logger(mesh, MESHLINK_ERROR, "Cannot encrypt config file\n");
+ meshlink_errno = MESHLINK_ESTORAGE;
+ }
- if(*value == '=') {
- value++;
- value += strspn(value, "\t ");
+ chacha_poly1305_exit(ctx);
+ return success;
}
- variable[len] = '\0';
-
- if(!*value) {
- const char err[] = "No value for variable";
- logger(NULL, MESHLINK_ERROR, "%s `%s' on line %d while reading config file %s",
- err, variable, lineno, fname);
- return NULL;
+ if(fwrite(config->buf, config->len, 1, f) != 1) {
+ logger(mesh, MESHLINK_ERROR, "Cannot write config file: %s", strerror(errno));
+ meshlink_errno = MESHLINK_ESTORAGE;
+ return false;
}
- cfg = new_config();
- cfg->variable = xstrdup(variable);
- cfg->value = xstrdup(value);
- cfg->line = lineno;
-
- return cfg;
+ return true;
}
-/*
- Parse a configuration file and put the results in the configuration tree
- starting at *base.
-*/
-bool read_config_file(splay_tree_t *config_tree, const char *fname) {
- FILE *fp;
- char buffer[MAX_STRING_SIZE];
- char *line;
- int lineno = 0;
- bool ignore = false;
- config_t *cfg;
- bool result = false;
-
- fp = fopen(fname, "r");
-
- if(!fp) {
- logger(NULL, MESHLINK_ERROR, "Cannot open config file %s: %s", fname, strerror(errno));
- return false;
- }
+/// Free resources of a loaded configuration file.
+void config_free(config_t *config) {
+ free((uint8_t *)config->buf);
+ config->buf = NULL;
+ config->len = 0;
+}
- for(;;) {
- line = readline(fp, buffer, sizeof(buffer));
+/// Check the presence of a host configuration file.
+bool config_exists(meshlink_handle_t *mesh, const char *name) {
+ char path[PATH_MAX];
+ make_host_path(mesh, name, path, sizeof(path));
- if(!line) {
- if(feof(fp)) {
- result = true;
- }
-
- break;
- }
+ return access(path, F_OK) == 0;
+}
- lineno++;
+/// Read a host configuration file.
+bool config_read(meshlink_handle_t *mesh, const char *name, config_t *config) {
+ char path[PATH_MAX];
+ make_host_path(mesh, name, path, sizeof(path));
- if(!*line || *line == '#') {
- continue;
- }
+ FILE *f = fopen(path, "r");
- if(ignore) {
- if(!strncmp(line, "-----END", 8)) {
- ignore = false;
- }
+ if(!f) {
+ logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
+ return false;
+ }
- continue;
- }
+ if(!config_read_file(mesh, f, config)) {
+ logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
+ fclose(f);
+ return false;
+ }
- if(!strncmp(line, "-----BEGIN", 10)) {
- ignore = true;
- continue;
- }
+ fclose(f);
+ return true;
+}
- cfg = parse_config_line(line, fname, lineno);
+/// Write a host configuration file.
+bool config_write(meshlink_handle_t *mesh, const char *name, const config_t *config) {
+ char path[PATH_MAX];
+ make_host_path(mesh, name, path, sizeof(path));
- if(!cfg) {
- break;
- }
+ FILE *f = fopen(path, "w");
- config_add(config_tree, cfg);
+ if(!f) {
+ logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
+ return false;
}
- fclose(fp);
+ if(!config_write_file(mesh, f, config)) {
+ logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", path, strerror(errno));
+ fclose(f);
+ return false;
+ }
- return result;
+ fclose(f);
+ return true;
}
-bool write_config_file(const struct splay_tree_t *config_tree, const char *fname) {
- FILE *fp;
+/// Read the main configuration file.
+bool main_config_read(meshlink_handle_t *mesh, config_t *config) {
+ char path[PATH_MAX];
+ make_main_path(mesh, path, sizeof(path));
- fp = fopen(fname, "w+");
+ FILE *f = fopen(path, "r");
- if(!fp) {
- logger(NULL, MESHLINK_ERROR, "Cannot open config file %s: %s", fname, strerror(errno));
+ if(!f) {
+ logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
return false;
}
- for splay_each(config_t, cnf, config_tree) {
- if(fwrite(cnf->variable, sizeof(char), strlen(cnf->variable), fp) < strlen(cnf->variable)) {
- goto error;
- }
-
- if(fwrite(" = ", sizeof(char), 3, fp) < 3) {
- goto error;
- }
-
- if(fwrite(cnf->value, sizeof(char), strlen(cnf->value), fp) < strlen(cnf->value)) {
- goto error;
- }
-
- if(fwrite("\n", sizeof(char), 1, fp) < 1) {
- goto error;
- }
+ if(!config_read_file(mesh, f, config)) {
+ logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
+ fclose(f);
+ return false;
}
- fclose(fp);
+ fclose(f);
return true;
-
-error:
- logger(NULL, MESHLINK_ERROR, "Cannot write to config file %s: %s", fname, strerror(errno));
- fclose(fp);
- return false;
}
-bool read_server_config(meshlink_handle_t *mesh) {
- char filename[PATH_MAX];
- bool x;
+/// Write the main configuration file.
+bool main_config_write(meshlink_handle_t *mesh, const config_t *config) {
+ char path[PATH_MAX];
+ make_main_path(mesh, path, sizeof(path));
- snprintf(filename, PATH_MAX, "%s" SLASH "meshlink.conf", mesh->confbase);
- errno = 0;
- x = read_config_file(mesh->config, filename);
+ FILE *f = fopen(path, "w");
- if(!x && errno) {
- logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", filename, strerror(errno));
+ if(!f) {
+ logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
+ return false;
}
- return x;
-}
-
-bool read_host_config(meshlink_handle_t *mesh, splay_tree_t *config_tree, const char *name) {
- char filename[PATH_MAX];
- bool x;
-
- snprintf(filename, PATH_MAX, "%s" SLASH "hosts" SLASH "%s", mesh->confbase, name);
- x = read_config_file(config_tree, filename);
+ if(!config_write_file(mesh, f, config)) {
+ logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", path, strerror(errno));
+ fclose(f);
+ return false;
+ }
- return x;
+ fclose(f);
+ return true;
}
-bool write_host_config(struct meshlink_handle *mesh, const struct splay_tree_t *config_tree, const char *name) {
- char filename[PATH_MAX];
-
- snprintf(filename, PATH_MAX, "%s" SLASH "hosts" SLASH "%s", mesh->confbase, name);
- return write_config_file(config_tree, filename);
-}
+/// Read an invitation file, and immediately delete it.
+bool invitation_read(meshlink_handle_t *mesh, const char *name, config_t *config) {
+ char path[PATH_MAX];
+ char used_path[PATH_MAX];
+ make_invitation_path(mesh, name, path, sizeof(path));
+ make_used_invitation_path(mesh, name, used_path, sizeof(used_path));
+
+ // Atomically rename the invitation file
+ if(rename(path, used_path)) {
+ if(errno == ENOENT) {
+ logger(mesh, MESHLINK_ERROR, "Peer tried to use non-existing invitation %s\n", name);
+ } else {
+ logger(mesh, MESHLINK_ERROR, "Error trying to rename invitation %s\n", name);
+ }
-bool modify_config_file(struct meshlink_handle *mesh, const char *name, const char *key, const char *value, int trim) {
- assert(mesh && name && key);
+ return false;
+ }
- char filename[PATH_MAX];
- char tmpname[PATH_MAX];
- bool error = false;
+ FILE *f = fopen(used_path, "r");
- if(snprintf(filename, sizeof(filename), "%s" SLASH "hosts" SLASH "%s", mesh->confbase, name) >= PATH_MAX) {
- logger(mesh, MESHLINK_ERROR, "Filename too long: %s" SLASH "hosts" SLASH "%s", mesh->confbase, name);
+ if(!f) {
+ logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
return false;
}
- if(snprintf(tmpname, sizeof(tmpname), "%s.tmp", filename) >= PATH_MAX) {
- logger(mesh, MESHLINK_ERROR, "Filename too long: %s.tmp", filename);
+ // Check the timestamp
+ struct stat st;
+
+ if(fstat(fileno(f), &st)) {
+ logger(mesh, MESHLINK_ERROR, "Could not stat invitation file %s\n", name);
+ fclose(f);
+ unlink(used_path);
return false;
}
- FILE *fr = fopen(filename, "r");
+ if(time(NULL) > st.st_mtime + mesh->invitation_timeout) {
+ logger(mesh, MESHLINK_ERROR, "Peer tried to use an outdated invitation file %s\n", name);
+ fclose(f);
+ unlink(used_path);
+ return false;
+ }
- if(!fr) {
- logger(mesh, MESHLINK_ERROR, "Cannot open config file %s: %s", filename, strerror(errno));
+ if(!config_read_file(mesh, f, config)) {
+ logger(mesh, MESHLINK_ERROR, "Failed to read `%s': %s", path, strerror(errno));
+ fclose(f);
+ unlink(used_path);
return false;
}
- FILE *fw = fopen(tmpname, "w");
+ fclose(f);
+ unlink(used_path);
+ return true;
+}
+
+/// Write an invitation file.
+bool invitation_write(meshlink_handle_t *mesh, const char *name, const config_t *config) {
+ char path[PATH_MAX];
+ make_invitation_path(mesh, name, path, sizeof(path));
+
+ FILE *f = fopen(path, "w");
- if(!fw) {
- logger(mesh, MESHLINK_ERROR, "Cannot open temporary file %s: %s", tmpname, strerror(errno));
- fclose(fr);
+ if(!f) {
+ logger(mesh, MESHLINK_ERROR, "Failed to open `%s': %s", path, strerror(errno));
return false;
}
- char buf[4096];
- char *sep;
- int found = 0;
-
- if(value) {
- fprintf(fw, "%s = %s\n", key, value);
- found++;
+ if(!config_write_file(mesh, f, config)) {
+ logger(mesh, MESHLINK_ERROR, "Failed to write `%s': %s", path, strerror(errno));
+ fclose(f);
+ return false;
}
- while(readline(fr, buf, sizeof(buf))) {
- if(!*buf || *buf == '#') {
- goto copy;
- }
+ fclose(f);
+ return true;
+}
- sep = strchr(buf, ' ');
+/// Purge old invitation files
+size_t invitation_purge_old(meshlink_handle_t *mesh, time_t deadline) {
+ char path[PATH_MAX];
+ make_invitation_path(mesh, "", path, sizeof(path));
- if(!sep) {
- goto copy;
- }
+ DIR *dir = opendir(path);
- *sep = 0;
+ if(!dir) {
+ logger(mesh, MESHLINK_DEBUG, "Could not read directory %s: %s\n", path, strerror(errno));
+ meshlink_errno = MESHLINK_ESTORAGE;
+ return 0;
+ }
- if(strcmp(buf, key)) {
- *sep = ' ';
- goto copy;
- }
+ errno = 0;
+ size_t count = 0;
+ struct dirent *ent;
- // We found the key and the value. We already added it at the top, so ignore this one.
- if(value && sep[1] == '=' && sep[2] == ' ' && !strcmp(sep + 3, value)) {
+ while((ent = readdir(dir))) {
+ if(strlen(ent->d_name) != 24) {
continue;
}
- // We found the key but with a different value, delete it if wanted.
- found++;
+ char invname[PATH_MAX];
+ struct stat st;
- if((!value || trim) && found > trim) {
+ if(snprintf(invname, sizeof(invname), "%s" SLASH "%s", path, ent->d_name) >= PATH_MAX) {
+ logger(mesh, MESHLINK_DEBUG, "Filename too long: %s" SLASH "%s", path, ent->d_name);
continue;
}
- *sep = ' ';
-
-copy:
- fprintf(fw, "%s\n", buf);
- }
-
- if(ferror(fr)) {
- error = true;
- }
-
- fclose(fr);
-
- if(ferror(fw)) {
- error = true;
- }
-
- if(fclose(fw)) {
- error = true;
+ if(!stat(invname, &st)) {
+ if(mesh->invitation_key && deadline < st.st_mtime) {
+ count++;
+ } else {
+ unlink(invname);
+ }
+ } else {
+ logger(mesh, MESHLINK_DEBUG, "Could not stat %s: %s\n", invname, strerror(errno));
+ errno = 0;
+ }
}
- // If any error occured during reading or writing, exit.
- if(error) {
- unlink(tmpname);
- return false;
+ if(errno) {
+ logger(mesh, MESHLINK_DEBUG, "Error while reading directory %s: %s\n", path, strerror(errno));
+ closedir(dir);
+ meshlink_errno = MESHLINK_ESTORAGE;
+ return 0;
}
- // Try to atomically replace the old config file with the new one.
-#ifdef HAVE_MINGW
- char bakname[PATH_MAX];
- snprintf(bakname, sizeof(bakname), "%s.bak", filename);
-
- if(rename(filename, bakname) || rename(tmpname, filename)) {
- rename(bakname, filename);
-#else
-
- if(rename(tmpname, filename)) {
-#endif
- return false;
- } else {
-#ifdef HAVE_MINGW
- unlink(bakname);
-#endif
- return true;
- }
-}
+ closedir(dir);
-bool append_config_file(meshlink_handle_t *mesh, const char *name, const char *key, const char *value) {
- return modify_config_file(mesh, name, key, value, 0);
+ return count;
}
#define MESHLINK_CONF_H
/*
- conf.h -- header for conf.c
- Copyright (C) 2014, 2017 Guus Sliepen <guus@meshlink.io>
+ econf.h -- header for econf.c
+ Copyright (C) 2018 Guus Sliepen <guus@meshlink.io>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "list.h"
-#include "meshlink_internal.h"
-#include "splay_tree.h"
+struct meshlink_handle;
typedef struct config_t {
- char *variable;
- char *value;
- int line;
+ const uint8_t *buf;
+ size_t len;
} config_t;
-extern void init_configuration(struct splay_tree_t **);
-extern void exit_configuration(struct splay_tree_t **);
-extern config_t *new_config(void) __attribute__((__malloc__));
-extern void free_config(config_t *);
-extern void config_add(struct splay_tree_t *, config_t *);
-extern config_t *lookup_config(struct splay_tree_t *, char *);
-extern config_t *lookup_config_next(struct splay_tree_t *, const config_t *);
-extern bool get_config_bool(const config_t *, bool *);
-extern bool get_config_int(const config_t *, int *);
-extern bool set_config_int(config_t *, int);
-extern bool get_config_string(const config_t *, char **);
-extern bool set_config_string(config_t *, const char *);
-extern bool get_config_address(const config_t *, struct addrinfo **);
-
-extern bool read_server_config(struct meshlink_handle *mesh);
-extern bool read_host_config(struct meshlink_handle *mesh, struct splay_tree_t *, const char *);
-extern bool write_host_config(struct meshlink_handle *mesh, const struct splay_tree_t *, const char *);
-extern bool modify_config_file(struct meshlink_handle *mesh, const char *name, const char *key, const char *value, int trim);
-extern bool append_config_file(struct meshlink_handle *mesh, const char *name, const char *key, const char *value);
+//extern bool config_read_file(struct meshlink_handle *mesh, FILE *f, struct config_t *);
+//extern bool config_write_file(struct meshlink_handle *mesh, FILE *f, const struct config_t *);
+extern void config_free(struct config_t *config);
+
+extern bool config_init(struct meshlink_handle *mesh);
+extern bool config_destroy(const char *confbase);
+
+extern bool main_config_exists(struct meshlink_handle *mesh);
+extern bool main_config_lock(struct meshlink_handle *mesh);
+extern void main_config_unlock(struct meshlink_handle *mesh);
+extern bool main_config_read(struct meshlink_handle *mesh, struct config_t *);
+extern bool main_config_write(struct meshlink_handle *mesh, const struct config_t *);
+
+extern bool config_exists(struct meshlink_handle *mesh, const char *name);
+extern bool config_read(struct meshlink_handle *mesh, const char *name, struct config_t *);
+extern bool config_write(struct meshlink_handle *mesh, const char *name, const struct config_t *);
+
+extern bool invitation_read(struct meshlink_handle *mesh, const char *name, struct config_t *);
+extern bool invitation_write(struct meshlink_handle *mesh, const char *name, const struct config_t *);
+extern size_t invitation_purge_old(struct meshlink_handle *mesh, time_t deadline);
#endif
free(c->name);
- if(c->config_tree) {
- exit_configuration(&c->config_tree);
- }
-
free(c);
}
int allow_request; /* defined if there's only one request possible */
time_t last_ping_time; /* last time we saw some activity from the other end or pinged them */
-
- splay_tree_t *config_tree; /* Pointer to configuration tree belonging to him */
} connection_t;
extern void init_connections(struct meshlink_handle *mesh);
typedef struct ecdsa ecdsa_t;
#endif
+extern ecdsa_t *ecdsa_set_private_key(const void *p) __attribute__((__malloc__));
extern ecdsa_t *ecdsa_set_base64_public_key(const char *p) __attribute__((__malloc__));
+extern ecdsa_t *ecdsa_set_public_key(const void *p) __attribute__((__malloc__));
extern char *ecdsa_get_base64_public_key(ecdsa_t *ecdsa);
+extern const void *ecdsa_get_public_key(ecdsa_t *ecdsa) __attribute__((__malloc__));
+extern const void *ecdsa_get_private_key(ecdsa_t *ecdsa) __attribute__((__malloc__));
extern ecdsa_t *ecdsa_read_pem_public_key(FILE *fp) __attribute__((__malloc__));
extern ecdsa_t *ecdsa_read_pem_private_key(FILE *fp) __attribute__((__malloc__));
extern size_t ecdsa_size(ecdsa_t *ecdsa);
return ecdsa;
}
+ecdsa_t *ecdsa_set_public_key(const void *p) {
+ ecdsa_t *ecdsa = xzalloc(sizeof(*ecdsa));
+ memcpy(ecdsa->public, p, sizeof ecdsa->public);
+ return ecdsa;
+}
+
char *ecdsa_get_base64_public_key(ecdsa_t *ecdsa) {
char *base64 = xmalloc(44);
b64encode(ecdsa->public, base64, sizeof ecdsa->public);
return base64;
}
+const void *ecdsa_get_public_key(ecdsa_t *ecdsa) {
+ return ecdsa->public;
+}
+
+ecdsa_t *ecdsa_set_private_key(const void *p) {
+ ecdsa_t *ecdsa = xzalloc(sizeof(*ecdsa));
+ memcpy(ecdsa->private, p, sizeof(*ecdsa));
+ return ecdsa;
+}
+
+const void *ecdsa_get_private_key(ecdsa_t *ecdsa) {
+ return ecdsa->private;
+}
+
// Read PEM ECDSA keys
ecdsa_t *ecdsa_read_pem_public_key(FILE *fp) {
- ecdsa_t *ecdsa = xzalloc(sizeof * ecdsa);
+ ecdsa_t *ecdsa = xzalloc(sizeof(*ecdsa));
if(fread(ecdsa->public, sizeof ecdsa->public, 1, fp) == 1) {
return ecdsa;
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#define VAR_SERVER 1 /* Should be in meshlink.conf */
-#define VAR_HOST 2 /* Can be in host config file */
-#define VAR_MULTIPLE 4 /* Multiple statements allowed */
-#define VAR_OBSOLETE 8 /* Should not be used anymore */
-#define VAR_SAFE 16 /* Variable is safe when accepting invitations */
-#define MAX_ADDRESS_LENGTH 45 /* Max length of an (IPv6) address */
-#define MAX_PORT_LENGTH 5 /* 0-65535 */
-typedef struct {
- const char *name;
- int type;
-} var_t;
#include "system.h"
#include <pthread.h>
#include "meshlink_internal.h"
#include "netutl.h"
#include "node.h"
+#include "packmsg.h"
+#include "prf.h"
#include "protocol.h"
#include "route.h"
#include "sockaddr.h"
meshlink_log_cb_t global_log_cb;
meshlink_log_level_t global_log_level;
-//TODO: this can go away completely
-const var_t variables[] = {
- /* Server configuration */
- {"ConnectTo", VAR_SERVER | VAR_MULTIPLE | VAR_SAFE},
- {"Name", VAR_SERVER},
- /* Host configuration */
- {"CanonicalAddress", VAR_HOST},
- {"Address", VAR_HOST | VAR_MULTIPLE},
- {"ECDSAPublicKey", VAR_HOST},
- {"Port", VAR_HOST},
- {NULL, 0}
-};
-
-static bool fcopy(FILE *out, const char *filename) {
- FILE *in = fopen(filename, "r");
-
- if(!in) {
- logger(NULL, MESHLINK_ERROR, "Could not open %s: %s\n", filename, strerror(errno));
- return false;
- }
-
- char buf[1024];
- size_t len;
-
- while((len = fread(buf, 1, sizeof(buf), in))) {
- fwrite(buf, len, 1, out);
- }
-
- fclose(in);
- return true;
-}
-
static int rstrip(char *value) {
int len = strlen(value);
return len;
}
-static void scan_for_canonical_address(const char *filename, char **hostname, char **port) {
- char line[4096];
-
- if(!filename || (*hostname && *port)) {
+static void get_canonical_address(node_t *n, char **hostname, char **port) {
+ if(!n->canonical_address) {
return;
}
- FILE *f = fopen(filename, "r");
-
- if(!f) {
- return;
- }
-
- while(fgets(line, sizeof(line), f)) {
- if(!rstrip(line)) {
- continue;
- }
-
- char *p = line, *q;
- p += strcspn(p, "\t =");
-
- if(!*p) {
- continue;
- }
-
- q = p + strspn(p, "\t ");
-
- if(*q == '=') {
- q += 1 + strspn(q + 1, "\t ");
- }
-
- // q is now pointing to the hostname
- *p = 0;
- p = q + strcspn(q, "\t ");
-
- if(*p) {
- *p++ = 0;
- }
-
- p += strspn(p, "\t ");
- p[strcspn(p, "\t ")] = 0;
- // p is now pointing to the port, if present
-
- if(!*port && !strcasecmp(line, "Port")) {
- *port = xstrdup(q);
- } else if(!strcasecmp(line, "CanonicalAddress")) {
- *hostname = xstrdup(q);
-
- if(*p) {
- free(*port);
- *port = xstrdup(p);
- }
- }
+ *hostname = xstrdup(n->canonical_address);
+ char *space = strchr(*hostname, ' ');
- if(*hostname && *port) {
- break;
- }
+ if(space) {
+ *space++ = 0;
+ *port = xstrdup(space);
}
-
- fclose(f);
}
static bool is_valid_hostname(const char *hostname) {
// Find out what local address a socket would use if we connect to the given address.
// We do this using connect() on a UDP socket, so the kernel has to resolve the address
// of both endpoints, but this will actually not send any UDP packet.
-static bool getlocaladdrname(char *destaddr, char *host, socklen_t hostlen) {
+static bool getlocaladdr(char *destaddr, struct sockaddr *sn, socklen_t *sl) {
struct addrinfo *rai = NULL;
const struct addrinfo hint = {
.ai_family = AF_UNSPEC,
freeaddrinfo(rai);
- struct sockaddr_storage sn;
- socklen_t sl = sizeof(sn);
-
- if(getsockname(sock, (struct sockaddr *)&sn, &sl)) {
+ if(getsockname(sock, sn, sl)) {
closesocket(sock);
return false;
}
closesocket(sock);
+ return true;
+}
+
+static bool getlocaladdrname(char *destaddr, char *host, socklen_t hostlen) {
+ struct sockaddr_storage sn;
+ socklen_t sl = sizeof(sn);
+
+ if(!getlocaladdr(destaddr, (struct sockaddr *)&sn, &sl)) {
+ return false;
+ }
if(getnameinfo((struct sockaddr *)&sn, sl, host, hostlen, NULL, 0, NI_NUMERICHOST | NI_NUMERICSERV)) {
return false;
// Add public/canonical addresses if requested
if(flags & MESHLINK_INVITE_PUBLIC) {
// Try the CanonicalAddress first
- char filename[PATH_MAX] = "";
- snprintf(filename, sizeof(filename), "%s" SLASH "hosts" SLASH "%s", mesh->confbase, mesh->self->name);
- scan_for_canonical_address(filename, &hostname[2], &port[2]);
+ get_canonical_address(mesh->self, &hostname[2], &port[2]);
if(!hostname[2]) {
if(flags & MESHLINK_INVITE_IPV4) {
// Ensure we have the same addresses in our own host config file.
char *tmphostport;
xasprintf(&tmphostport, "%s %s", hostname[i], port[i]);
- append_config_file(mesh, mesh->self->name, "Address", tmphostport);
+ /// TODO: FIX
+ //config_add_string(&mesh->config, "Address", tmphostport);
free(tmphostport);
// Append the address to the hostport string
return hostport;
}
-static char *get_line(const char **data) {
- if(!data || !*data) {
- return NULL;
- }
-
- if(! **data) {
- *data = NULL;
- return NULL;
- }
-
- static char line[1024];
- const char *end = strchr(*data, '\n');
- size_t len = end ? (size_t)(end - *data) : strlen(*data);
-
- if(len >= sizeof(line)) {
- logger(NULL, MESHLINK_ERROR, "Maximum line length exceeded!\n");
- return NULL;
- }
-
- if(len && !isprint(**data)) {
- abort();
- }
-
- memcpy(line, *data, len);
- line[len] = 0;
-
- if(end) {
- *data = end + 1;
- } else {
- *data = NULL;
- }
-
- return line;
-}
-
-static char *get_value(const char *data, const char *var) {
- char *line = get_line(&data);
-
- if(!line) {
- return NULL;
- }
-
- char *sep = line + strcspn(line, " \t=");
- char *val = sep + strspn(sep, " \t");
-
- if(*val == '=') {
- val += 1 + strspn(val + 1, " \t");
- }
-
- *sep = 0;
-
- if(strcasecmp(line, var)) {
- return NULL;
- }
-
- return val;
-}
-
static bool try_bind(int port) {
struct addrinfo *ai = NULL;
struct addrinfo hint = {
int port = 0x1000 + (rand() & 0x7fff);
if(try_bind(port)) {
- char filename[PATH_MAX];
- snprintf(filename, sizeof(filename), "%s" SLASH "hosts" SLASH "%s", mesh->confbase, mesh->name);
- FILE *f = fopen(filename, "a");
-
- if(!f) {
- meshlink_errno = MESHLINK_ESTORAGE;
- logger(mesh, MESHLINK_DEBUG, "Could not store Port.\n");
- return 0;
- }
-
- fprintf(f, "Port = %d\n", port);
- fclose(f);
+ free(mesh->myport);
+ xasprintf(&mesh->myport, "%d", port);
return port;
}
}
return 0;
}
-static void deltree(const char *dirname) {
- DIR *d = opendir(dirname);
+static bool finalize_join(meshlink_handle_t *mesh, const void *buf, uint16_t len) {
+ packmsg_input_t in = {buf, len};
+ uint32_t version = packmsg_get_uint32(&in);
- if(d) {
- struct dirent *ent;
-
- while((ent = readdir(d))) {
- if(ent->d_name[0] == '.') {
- continue;
- }
-
- char filename[PATH_MAX];
- snprintf(filename, sizeof(filename), "%s" SLASH "%s", dirname, ent->d_name);
-
- if(unlink(filename)) {
- deltree(filename);
- }
- }
-
- closedir(d);
+ if(version != MESHLINK_INVITATION_VERSION) {
+ logger(mesh, MESHLINK_ERROR, "Invalid invitation version!\n");
+ return false;
}
- rmdir(dirname);
-}
-
-static bool finalize_join(meshlink_handle_t *mesh) {
- char *name = xstrdup(get_value(mesh->data, "Name"));
+ char *name = packmsg_get_str_dup(&in);
+ int32_t devclass = packmsg_get_int32(&in);
+ uint32_t count = packmsg_get_array(&in);
if(!name) {
logger(mesh, MESHLINK_DEBUG, "No Name found in invitation!\n");
return false;
}
- char filename[PATH_MAX];
- snprintf(filename, sizeof(filename), "%s" SLASH "meshlink.conf", mesh->confbase);
-
- FILE *f = fopen(filename, "w");
-
- if(!f) {
- logger(mesh, MESHLINK_DEBUG, "Could not create file %s: %s\n", filename, strerror(errno));
+ if(!count) {
+ logger(mesh, MESHLINK_ERROR, "Incomplete invitation file!\n");
return false;
}
- fprintf(f, "Name = %s\n", name);
-
- // Wipe all old host config files and invitations
- snprintf(filename, sizeof(filename), "%s" SLASH "hosts", mesh->confbase);
- deltree(filename);
-
- if(mkdir(filename, 0777) && errno != EEXIST) {
- logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", filename, strerror(errno));
+ // Initialize configuration directory
+ if(!config_init(mesh)) {
return false;
}
- snprintf(filename, sizeof(filename), "%s" SLASH "invitations", mesh->confbase);
- deltree(filename);
+ // Write main config file
+ uint8_t outbuf[4096];
+ packmsg_output_t out = {outbuf, sizeof(outbuf)};
+ packmsg_add_uint32(&out, MESHLINK_CONFIG_VERSION);
+ packmsg_add_str(&out, name);
+ packmsg_add_bin(&out, ecdsa_get_private_key(mesh->private_key), 96);
+ packmsg_add_uint16(&out, atoi(mesh->myport));
- // Create a new host config file for ourself
- snprintf(filename, sizeof(filename), "%s" SLASH "hosts" SLASH "%s", mesh->confbase, name);
- FILE *fh = fopen(filename, "w");
+ config_t config = {outbuf, packmsg_output_size(&out, outbuf)};
- if(!fh) {
- logger(mesh, MESHLINK_DEBUG, "Could not create file %s: %s\n", filename, strerror(errno));
- fclose(f);
+ if(!main_config_write(mesh, &config)) {
return false;
}
- // Filter first chunk on approved keywords, split between meshlink.conf and hosts/Name
- // Other chunks go unfiltered to their respective host config files
- const char *p = mesh->data;
- char *l, *value;
-
- while((l = get_line(&p))) {
- // Ignore comments
- if(*l == '#') {
- continue;
- }
-
- // Split line into variable and value
- int len = strcspn(l, "\t =");
- value = l + len;
- value += strspn(value, "\t ");
-
- if(*value == '=') {
- value++;
- value += strspn(value, "\t ");
- }
-
- l[len] = 0;
-
- // Is it a Name?
- if(!strcasecmp(l, "Name"))
- if(strcmp(value, name)) {
- break;
- } else {
- continue;
- } else if(!strcasecmp(l, "NetName")) {
- continue;
- }
+ // Write our own host config file
+ out.ptr = outbuf;
+ out.len = sizeof(outbuf);
+ packmsg_add_uint32(&out, MESHLINK_CONFIG_VERSION);
+ packmsg_add_str(&out, name);
+ packmsg_add_int32(&out, devclass);
+ packmsg_add_bool(&out, false);
+ packmsg_add_bin(&out, ecdsa_get_public_key(mesh->private_key), 32);
+ packmsg_add_str(&out, ""); // TODO: copy existing canonical address, in case it was added before meshlink_join().
+ packmsg_add_array(&out, 0);
- // Check the list of known variables
- bool found = false;
- int i;
+ config.len = packmsg_output_size(&out, outbuf);
- for(i = 0; variables[i].name; i++) {
- if(strcasecmp(l, variables[i].name)) {
- continue;
- }
-
- found = true;
- break;
- }
-
- // Ignore unknown and unsafe variables
- if(!found) {
- logger(mesh, MESHLINK_DEBUG, "Ignoring unknown variable '%s' in invitation.\n", l);
- continue;
- } else if(!(variables[i].type & VAR_SAFE)) {
- logger(mesh, MESHLINK_DEBUG, "Ignoring unsafe variable '%s' in invitation.\n", l);
- continue;
- }
-
- // Copy the safe variable to the right config file
- fprintf(variables[i].type & VAR_HOST ? fh : f, "%s = %s\n", l, value);
+ if(!config_write(mesh, name, &config)) {
+ return false;
}
- fclose(f);
+ // Write host config files
+ while(count--) {
+ const void *data;
+ uint32_t len = packmsg_get_bin_raw(&in, &data);
- while(l && !strcasecmp(l, "Name")) {
- if(!check_id(value)) {
- logger(mesh, MESHLINK_DEBUG, "Invalid Name found in invitation.\n");
+ if(!len) {
+ logger(mesh, MESHLINK_ERROR, "Incomplete invitation file!\n");
return false;
}
- if(!strcmp(value, name)) {
- logger(mesh, MESHLINK_DEBUG, "Secondary chunk would overwrite our own host config file.\n");
+ packmsg_input_t in2 = {data, len};
+ uint32_t version = packmsg_get_uint32(&in2);
+
+ if(version != MESHLINK_CONFIG_VERSION) {
+ logger(mesh, MESHLINK_ERROR, "Invalid host config file in invitation file!\n");
return false;
}
- snprintf(filename, sizeof(filename), "%s" SLASH "hosts" SLASH "%s", mesh->confbase, value);
- f = fopen(filename, "w");
+ char *host = packmsg_get_str_dup(&in2);
- if(!f) {
- logger(mesh, MESHLINK_DEBUG, "Could not create file %s: %s\n", filename, strerror(errno));
+ if(!check_id(host)) {
+ logger(mesh, MESHLINK_ERROR, "Invalid node name in invitation file!\n");
+ free(host);
return false;
}
- while((l = get_line(&p))) {
- if(!strcmp(l, "#---------------------------------------------------------------#")) {
- continue;
- }
-
- int len = strcspn(l, "\t =");
-
- if(len == 4 && !strncasecmp(l, "Name", 4)) {
- value = l + len;
- value += strspn(value, "\t ");
-
- if(*value == '=') {
- value++;
- value += strspn(value, "\t ");
- }
-
- l[len] = 0;
- break;
- }
-
- fputs(l, f);
- fputc('\n', f);
+ if(!strcmp(host, name)) {
+ logger(mesh, MESHLINK_DEBUG, "Secondary chunk would overwrite our own host config file.\n");
+ free(host);
+ return false;
}
- fclose(f);
- }
-
- char *b64key = ecdsa_get_base64_public_key(mesh->self->connection->ecdsa);
+ config_t config = {data, len};
+ config_write(mesh, host, &config);
- if(!b64key) {
- fclose(fh);
- return false;
+ node_t *n = new_node();
+ n->name = host;
+ node_read_full(mesh, n);
+ n->devclass = mesh->devclass;
+ node_add(mesh, n);
}
- fprintf(fh, "ECDSAPublicKey = %s\n", b64key);
- fprintf(fh, "Port = %s\n", mesh->myport);
-
- fclose(fh);
-
- sptps_send_record(&(mesh->sptps), 1, b64key, strlen(b64key));
- free(b64key);
+ sptps_send_record(&(mesh->sptps), 1, ecdsa_get_public_key(mesh->private_key), 32);
free(mesh->name);
free(mesh->self->name);
- free(mesh->self->connection->name);
mesh->name = xstrdup(name);
mesh->self->name = xstrdup(name);
- mesh->self->connection->name = name;
logger(mesh, MESHLINK_DEBUG, "Configuration stored in: %s\n", mesh->confbase);
return sptps_send_record(&(mesh->sptps), 0, mesh->cookie, sizeof(mesh)->cookie);
case 0:
- mesh->data = xrealloc(mesh->data, mesh->thedatalen + len + 1);
- memcpy(mesh->data + mesh->thedatalen, msg, len);
- mesh->thedatalen += len;
- mesh->data[mesh->thedatalen] = 0;
- break;
+ return finalize_join(mesh, msg, len);
case 1:
- mesh->thedatalen = 0;
- return finalize_join(mesh);
-
- case 2:
logger(mesh, MESHLINK_DEBUG, "Invitation succesfully accepted.\n");
shutdown(mesh->sock, SHUT_RDWR);
mesh->success = true;
}
static bool ecdsa_keygen(meshlink_handle_t *mesh) {
- ecdsa_t *key;
- FILE *f;
- char pubname[PATH_MAX], privname[PATH_MAX];
+ logger(mesh, MESHLINK_DEBUG, "Generating ECDSA keypairs:\n");
- logger(mesh, MESHLINK_DEBUG, "Generating ECDSA keypair:\n");
+ mesh->private_key = ecdsa_generate();
+ mesh->invitation_key = ecdsa_generate();
- if(!(key = ecdsa_generate())) {
+ if(!mesh->private_key || !mesh->invitation_key) {
logger(mesh, MESHLINK_DEBUG, "Error during key generation!\n");
meshlink_errno = MESHLINK_EINTERNAL;
return false;
- } else {
- logger(mesh, MESHLINK_DEBUG, "Done.\n");
- }
-
- if(snprintf(privname, sizeof(privname), "%s" SLASH "ecdsa_key.priv", mesh->confbase) >= PATH_MAX) {
- logger(mesh, MESHLINK_DEBUG, "Filename too long: %s" SLASH "ecdsa_key.priv\n", mesh->confbase);
- meshlink_errno = MESHLINK_ESTORAGE;
- return false;
- }
-
- f = fopen(privname, "wb");
-
- if(!f) {
- meshlink_errno = MESHLINK_ESTORAGE;
- return false;
- }
-
-#ifdef HAVE_FCHMOD
- fchmod(fileno(f), 0600);
-#endif
-
- if(!ecdsa_write_pem_private_key(key, f)) {
- logger(mesh, MESHLINK_DEBUG, "Error writing private key!\n");
- ecdsa_free(key);
- fclose(f);
- meshlink_errno = MESHLINK_EINTERNAL;
- return false;
- }
-
- fclose(f);
-
- snprintf(pubname, sizeof(pubname), "%s" SLASH "hosts" SLASH "%s", mesh->confbase, mesh->name);
- f = fopen(pubname, "a");
-
- if(!f) {
- meshlink_errno = MESHLINK_ESTORAGE;
- return false;
}
- char *pubkey = ecdsa_get_base64_public_key(key);
- fprintf(f, "ECDSAPublicKey = %s\n", pubkey);
- free(pubkey);
-
- fclose(f);
- ecdsa_free(key);
+ logger(mesh, MESHLINK_DEBUG, "Done.\n");
return true;
}
// Get our local address(es) by simulating connecting to an Internet host.
static void add_local_addresses(meshlink_handle_t *mesh) {
- char host[NI_MAXHOST];
- char entry[MAX_STRING_SIZE];
+ struct sockaddr_storage sn;
+ socklen_t sl = sizeof(sn);
// IPv4 example.org
- if(getlocaladdrname("93.184.216.34", host, sizeof(host))) {
- snprintf(entry, sizeof(entry), "%s %s", host, mesh->myport);
- append_config_file(mesh, mesh->name, "Address", entry);
+ if(getlocaladdr("93.184.216.34", (struct sockaddr *)&sn, &sl)) {
+ ((struct sockaddr_in *)&sn)->sin_port = ntohs(atoi(mesh->myport));
+ meshlink_hint_address(mesh, (meshlink_node_t *)mesh->self, (struct sockaddr *)&sn);
}
// IPv6 example.org
- if(getlocaladdrname("2606:2800:220:1:248:1893:25c8:1946", host, sizeof(host))) {
- snprintf(entry, sizeof(entry), "%s %s", host, mesh->myport);
- append_config_file(mesh, mesh->name, "Address", entry);
+ sl = sizeof(sn);
+
+ if(getlocaladdr("2606:2800:220:1:248:1893:25c8:1946", (struct sockaddr *)&sn, &sl)) {
+ ((struct sockaddr_in6 *)&sn)->sin6_port = ntohs(atoi(mesh->myport));
+ meshlink_hint_address(mesh, (meshlink_node_t *)mesh->self, (struct sockaddr *)&sn);
}
}
+#if 0
+static bool meshlink_write_config(meshlink_handle_t *mesh) {
+ uint8_t buf[1024];
+ packmsg_output_t out = {buf, sizeof buf};
+ packmsg_add_str(&out, mesh->name);
+ packmsg_add_uint32(&out, mesh->devclass);
+ packmsg_add_uint16(&out, mesh->port);
+ packmsg_add_bin(&out, ecdsa, sizeof(ecdsa));
+ uint32_t len = packmsg_output_size(&out, buf);
+
+ if(!len) {
+ logger(mesh, MESHLINK_DEBUG, "Could not create configuration data\n",);
+ meshlink_errno = MESHLINK_EINTERNAL;
+ return false;
+ }
+}
+#endif
+
static bool meshlink_setup(meshlink_handle_t *mesh) {
- if(mkdir(mesh->confbase, 0777) && errno != EEXIST) {
- logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", mesh->confbase, strerror(errno));
+ if(!config_init(mesh)) {
+ logger(mesh, MESHLINK_ERROR, "Could not set up configuration in %s: %s\n", mesh->confbase, strerror(errno));
meshlink_errno = MESHLINK_ESTORAGE;
return false;
}
- char filename[PATH_MAX];
- snprintf(filename, sizeof(filename), "%s" SLASH "hosts", mesh->confbase);
+ if(!ecdsa_keygen(mesh)) {
+ meshlink_errno = MESHLINK_EINTERNAL;
+ return false;
+ }
- if(mkdir(filename, 0777) && errno != EEXIST) {
- logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", filename, strerror(errno));
- meshlink_errno = MESHLINK_ESTORAGE;
+ if(check_port(mesh) == 0) {
+ meshlink_errno = MESHLINK_ENETWORK;
return false;
}
- snprintf(filename, sizeof(filename), "%s" SLASH "meshlink.conf", mesh->confbase);
+ /* Create a node for ourself */
- if(!access(filename, F_OK)) {
- logger(mesh, MESHLINK_DEBUG, "Configuration file %s already exists!\n", filename);
- meshlink_errno = MESHLINK_EEXIST;
+ mesh->self = new_node();
+ mesh->self->name = xstrdup(mesh->name);
+ mesh->self->devclass = mesh->devclass;
+ mesh->self->ecdsa = ecdsa_set_public_key(ecdsa_get_public_key(mesh->private_key));
+
+ // Write the main config file
+ uint8_t buf[4096];
+ packmsg_output_t out = {buf, sizeof(buf)};
+
+ packmsg_add_uint32(&out, MESHLINK_CONFIG_VERSION);
+ packmsg_add_str(&out, mesh->name);
+ packmsg_add_bin(&out, ecdsa_get_private_key(mesh->private_key), 96);
+ packmsg_add_bin(&out, ecdsa_get_private_key(mesh->invitation_key), 96);
+ packmsg_add_uint16(&out, atoi(mesh->myport));
+
+ config_t config = {buf, packmsg_output_size(&out, buf)};
+
+ if(!main_config_write(mesh, &config)) {
return false;
}
- FILE *f = fopen(filename, "w");
+ // Write our own host config file
+ out.ptr = buf;
+ out.len = sizeof(buf);
+ packmsg_add_uint32(&out, MESHLINK_CONFIG_VERSION);
+ packmsg_add_str(&out, mesh->name);
+ packmsg_add_int32(&out, mesh->devclass);
+ packmsg_add_bool(&out, false);
+ packmsg_add_bin(&out, ecdsa_get_public_key(mesh->private_key), 32);
+ packmsg_add_str(&out, ""); // TODO: copy existing canonical address, in case it was added before meshlink_join().
+ packmsg_add_array(&out, 0);
+
+ config.len = packmsg_output_size(&out, buf);
+
+ if(!config_write(mesh, mesh->name, &config)) {
+ return false;
+ }
+
+ return true;
+}
- if(!f) {
- logger(mesh, MESHLINK_DEBUG, "Could not create file %s: %s\n", filename, strerror(errno));
+static bool meshlink_read_config(meshlink_handle_t *mesh) {
+ // Open the configuration file and lock it
+ if(!main_config_lock(mesh)) {
+ logger(NULL, MESHLINK_ERROR, "Cannot lock main config file\n");
meshlink_errno = MESHLINK_ESTORAGE;
return false;
}
- fprintf(f, "Name = %s\n", mesh->name);
- fclose(f);
+ config_t config;
- if(!ecdsa_keygen(mesh)) {
- meshlink_errno = MESHLINK_EINTERNAL;
- unlink(filename);
+ if(!main_config_read(mesh, &config)) {
+ logger(NULL, MESHLINK_ERROR, "Could not read main configuration file!");
return false;
}
- if(check_port(mesh) == 0) {
- meshlink_errno = MESHLINK_ENETWORK;
- unlink(filename);
+ packmsg_input_t in = {config.buf, config.len};
+ const void *private_key;
+ const void *invitation_key;
+
+ uint32_t version = packmsg_get_uint32(&in);
+ char *name = packmsg_get_str_dup(&in);
+ uint32_t private_key_len = packmsg_get_bin_raw(&in, &private_key);
+ uint32_t invitation_key_len = packmsg_get_bin_raw(&in, &invitation_key);
+ uint16_t myport = packmsg_get_uint16(&in);
+
+ if(!packmsg_done(&in) || version != MESHLINK_CONFIG_VERSION || private_key_len != 96 || invitation_key_len != 96) {
+ logger(NULL, MESHLINK_ERROR, "Error parsing main configuration file!");
+ free(name);
+ config_free(&config);
+ return false;
+ }
+
+#if 0
+
+ // TODO: check this?
+ if(mesh->name && strcmp(mesh->name, name)) {
+ logger(NULL, MESHLINK_ERROR, "Configuration is for a different name (%s)!", name);
+ meshlink_errno = MESHLINK_ESTORAGE;
+ free(name);
+ config_free(&config);
+ return false;
+ }
+
+#endif
+
+ free(mesh->name);
+ mesh->name = name;
+ xasprintf(&mesh->myport, "%u", myport);
+ mesh->private_key = ecdsa_set_private_key(private_key);
+ mesh->invitation_key = ecdsa_set_private_key(invitation_key);
+ config_free(&config);
+
+ /* Create a node for ourself and read our host configuration file */
+
+ mesh->self = new_node();
+ mesh->self->name = xstrdup(name);
+ mesh->self->devclass = mesh->devclass;
+
+ if(!node_read_public_key(mesh, mesh->self)) {
+ logger(NULL, MESHLINK_ERROR, "Could not read our host configuration file!");
+ free_node(mesh->self);
+ mesh->self = NULL;
return false;
}
return true;
}
-meshlink_handle_t *meshlink_open(const char *confbase, const char *name, const char *appname, dev_class_t devclass) {
+
+meshlink_handle_t *meshlink_open_encrypted(const char *confbase, const char *name, const char *appname, dev_class_t devclass, const void *key, size_t keylen) {
// Validate arguments provided by the application
bool usingname = false;
logger(NULL, MESHLINK_ERROR, "No name given!\n");
//return NULL;
} else { //check name only if there is a name != NULL
-
if(!check_id(name)) {
logger(NULL, MESHLINK_ERROR, "Invalid name given!\n");
meshlink_errno = MESHLINK_EINVAL;
return NULL;
}
+ if((key && !keylen) || (!key && keylen)) {
+ logger(NULL, MESHLINK_ERROR, "Invalid key length!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
meshlink_handle_t *mesh = xzalloc(sizeof(meshlink_handle_t));
mesh->confbase = xstrdup(confbase);
mesh->appname = xstrdup(appname);
mesh->name = xstrdup(name);
}
+ // Hash the key
+ if(key) {
+ mesh->config_key = xmalloc(CHACHA_POLY1305_KEYLEN);
+
+ if(!prf(key, keylen, "MeshLink configuration key", 26, mesh->config_key, CHACHA_POLY1305_KEYLEN)) {
+ logger(NULL, MESHLINK_ERROR, "Error creating configuration key!\n");
+ meshlink_errno = MESHLINK_EINTERNAL;
+ return NULL;
+ }
+ }
+
// initialize mutex
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
meshlink_queue_init(&mesh->outpacketqueue);
- // Check whether meshlink.conf already exists
-
- char filename[PATH_MAX];
- snprintf(filename, sizeof(filename), "%s" SLASH "meshlink.conf", confbase);
+ // If no configuration exists yet, create it.
- if(access(filename, R_OK)) {
- if(errno == ENOENT) {
- // If not, create it
- if(!meshlink_setup(mesh)) {
- // meshlink_errno is set by meshlink_setup()
- return NULL;
- }
- } else {
- logger(NULL, MESHLINK_ERROR, "Cannot not read from %s: %s\n", filename, strerror(errno));
+ if(!main_config_exists(mesh)) {
+ if(!meshlink_setup(mesh)) {
+ logger(NULL, MESHLINK_ERROR, "Cannot create initial configuration\n");
+ meshlink_close(mesh);
+ return NULL;
+ }
+ } else {
+ if(!meshlink_read_config(mesh)) {
+ logger(NULL, MESHLINK_ERROR, "Cannot read main configuration\n");
meshlink_close(mesh);
- meshlink_errno = MESHLINK_ESTORAGE;
return NULL;
}
}
- // Open the configuration file and lock it
-
- mesh->conffile = fopen(filename, "r");
-
- if(!mesh->conffile) {
- logger(NULL, MESHLINK_ERROR, "Cannot not open %s: %s\n", filename, strerror(errno));
- meshlink_close(mesh);
- meshlink_errno = MESHLINK_ESTORAGE;
- return NULL;
- }
-
-#ifdef FD_CLOEXEC
- fcntl(fileno(mesh->conffile), F_SETFD, FD_CLOEXEC);
-#endif
-
-#ifdef HAVE_MINGW
- // TODO: use _locking()?
-#else
-
- if(flock(fileno(mesh->conffile), LOCK_EX | LOCK_NB) != 0) {
- logger(NULL, MESHLINK_ERROR, "Cannot lock %s: %s\n", filename, strerror(errno));
- meshlink_close(mesh);
- meshlink_errno = MESHLINK_EBUSY;
- return NULL;
- }
-
-#endif
-
- // Read the configuration
-
- init_configuration(&mesh->config);
-
- if(!read_server_config(mesh)) {
- meshlink_close(mesh);
- meshlink_errno = MESHLINK_ESTORAGE;
- return NULL;
- };
-
#ifdef HAVE_MINGW
struct WSAData wsa_state;
-
WSAStartup(MAKEWORD(2, 2), &wsa_state);
-
#endif
// Setup up everything
}
add_local_addresses(mesh);
+ node_write_config(mesh, mesh->self);
idle_set(&mesh->loop, idle, mesh);
return mesh;
}
+meshlink_handle_t *meshlink_open(const char *confbase, const char *name, const char *appname, dev_class_t devclass) {
+ return meshlink_open_encrypted(confbase, name, appname, devclass, NULL, 0);
+}
+
static void *meshlink_main_loop(void *arg) {
meshlink_handle_t *mesh = arg;
pthread_mutex_lock(&(mesh->mesh_mutex));
- try_outgoing_connections(mesh);
-
logger(mesh, MESHLINK_DEBUG, "Starting main_loop...\n");
main_loop(mesh);
logger(mesh, MESHLINK_DEBUG, "main_loop returned.\n");
}
bool meshlink_start(meshlink_handle_t *mesh) {
+ assert(mesh->self);
+ assert(mesh->private_key);
+
if(!mesh) {
meshlink_errno = MESHLINK_EINVAL;
return false;
return false;
}
+ init_outgoings(mesh);
+
// Start the main thread
event_loop_start(&mesh->loop);
#endif
+ assert(mesh->self->ecdsa);
+ assert(!memcmp((uint8_t *)mesh->self->ecdsa + 64, (uint8_t *)mesh->private_key + 64, 32));
+
+
pthread_mutex_unlock(&(mesh->mesh_mutex));
return true;
}
}
}
- if(mesh->outgoings) {
- list_delete_list(mesh->outgoings);
- mesh->outgoings = NULL;
- }
+ exit_outgoings(mesh);
pthread_mutex_unlock(&(mesh->mesh_mutex));
}
logger(mesh, MESHLINK_INFO, "Terminating");
- exit_configuration(&mesh->config);
event_loop_exit(&mesh->loop);
#ifdef HAVE_MINGW
free(mesh->confbase);
pthread_mutex_destroy(&(mesh->mesh_mutex));
- if(mesh->conffile) {
- fclose(mesh->conffile);
- }
+ main_config_unlock(mesh);
memset(mesh, 0, sizeof(*mesh));
return false;
}
- char filename[PATH_MAX];
- snprintf(filename, sizeof(filename), "%s" SLASH "meshlink.conf", confbase);
-
- if(unlink(filename)) {
- if(errno == ENOENT) {
- meshlink_errno = MESHLINK_ENOENT;
- return false;
- } else {
- logger(NULL, MESHLINK_ERROR, "Cannot delete %s: %s\n", filename, strerror(errno));
- meshlink_errno = MESHLINK_ESTORAGE;
- return false;
- }
- }
-
- deltree(confbase);
-
- return true;
+ return config_destroy(confbase);
}
void meshlink_set_receive_cb(meshlink_handle_t *mesh, meshlink_receive_cb_t cb) {
node_t *n = (node_t *)node;
- if(!node_read_ecdsa_public_key(mesh, n) || !n->ecdsa) {
+ if(!node_read_public_key(mesh, n) || !n->ecdsa) {
meshlink_errno = MESHLINK_EINTERNAL;
pthread_mutex_unlock(&(mesh->mesh_mutex));
return false;
pthread_mutex_lock(&(mesh->mesh_mutex));
- if(!ecdsa_sign(mesh->self->connection->ecdsa, data, len, signature)) {
+ if(!ecdsa_sign(mesh->private_key, data, len, signature)) {
meshlink_errno = MESHLINK_EINTERNAL;
pthread_mutex_unlock(&(mesh->mesh_mutex));
return false;
bool rval = false;
struct node_t *n = (struct node_t *)source;
- node_read_ecdsa_public_key(mesh, n);
- if(!n->ecdsa) {
+ if(!node_read_public_key(mesh, n)) {
meshlink_errno = MESHLINK_EINTERNAL;
rval = false;
} else {
}
static bool refresh_invitation_key(meshlink_handle_t *mesh) {
- char filename[PATH_MAX];
-
pthread_mutex_lock(&(mesh->mesh_mutex));
- snprintf(filename, sizeof(filename), "%s" SLASH "invitations", mesh->confbase);
-
- if(mkdir(filename, 0700) && errno != EEXIST) {
- logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", filename, strerror(errno));
- meshlink_errno = MESHLINK_ESTORAGE;
- pthread_mutex_unlock(&(mesh->mesh_mutex));
- return false;
- }
-
- // Count the number of valid invitations, clean up old ones
- DIR *dir = opendir(filename);
-
- if(!dir) {
- logger(mesh, MESHLINK_DEBUG, "Could not read directory %s: %s\n", filename, strerror(errno));
- meshlink_errno = MESHLINK_ESTORAGE;
- pthread_mutex_unlock(&(mesh->mesh_mutex));
- return false;
- }
-
- errno = 0;
- int count = 0;
- struct dirent *ent;
- time_t deadline = time(NULL) - 604800; // 1 week in the past
-
- while((ent = readdir(dir))) {
- if(strlen(ent->d_name) != 24) {
- continue;
- }
-
- char invname[PATH_MAX];
- struct stat st;
-
- if(snprintf(invname, sizeof(invname), "%s" SLASH "%s", filename, ent->d_name) >= PATH_MAX) {
- logger(mesh, MESHLINK_DEBUG, "Filename too long: %s" SLASH "%s", filename, ent->d_name);
- continue;
- }
-
- if(!stat(invname, &st)) {
- if(mesh->invitation_key && deadline < st.st_mtime) {
- count++;
- } else {
- unlink(invname);
- }
- } else {
- logger(mesh, MESHLINK_DEBUG, "Could not stat %s: %s\n", invname, strerror(errno));
- errno = 0;
- }
- }
+ size_t count = invitation_purge_old(mesh, time(NULL) - mesh->invitation_timeout);
- if(errno) {
- logger(mesh, MESHLINK_DEBUG, "Error while reading directory %s: %s\n", filename, strerror(errno));
- closedir(dir);
- meshlink_errno = MESHLINK_ESTORAGE;
- pthread_mutex_unlock(&(mesh->mesh_mutex));
- return false;
- }
-
- closedir(dir);
-
- snprintf(filename, sizeof(filename), "%s" SLASH "invitations" SLASH "ecdsa_key.priv", mesh->confbase);
-
- // Remove the key if there are no outstanding invitations.
if(!count) {
- unlink(filename);
-
- if(mesh->invitation_key) {
- ecdsa_free(mesh->invitation_key);
- mesh->invitation_key = NULL;
- }
- }
-
- if(mesh->invitation_key) {
- pthread_mutex_unlock(&(mesh->mesh_mutex));
- return true;
- }
-
- // Create a new key if necessary.
- FILE *f = fopen(filename, "rb");
-
- if(!f) {
- if(errno != ENOENT) {
- logger(mesh, MESHLINK_DEBUG, "Could not read %s: %s\n", filename, strerror(errno));
- meshlink_errno = MESHLINK_ESTORAGE;
- pthread_mutex_unlock(&(mesh->mesh_mutex));
- return false;
- }
-
- mesh->invitation_key = ecdsa_generate();
-
- if(!mesh->invitation_key) {
- logger(mesh, MESHLINK_DEBUG, "Could not generate a new key!\n");
- meshlink_errno = MESHLINK_EINTERNAL;
- pthread_mutex_unlock(&(mesh->mesh_mutex));
- return false;
- }
-
- f = fopen(filename, "wb");
-
- if(!f) {
- logger(mesh, MESHLINK_DEBUG, "Could not write %s: %s\n", filename, strerror(errno));
- meshlink_errno = MESHLINK_ESTORAGE;
- pthread_mutex_unlock(&(mesh->mesh_mutex));
- return false;
- }
-
- chmod(filename, 0600);
- ecdsa_write_pem_private_key(mesh->invitation_key, f);
- fclose(f);
- } else {
- mesh->invitation_key = ecdsa_read_pem_private_key(f);
- fclose(f);
-
- if(!mesh->invitation_key) {
- logger(mesh, MESHLINK_DEBUG, "Could not read private key from %s\n", filename);
- meshlink_errno = MESHLINK_ESTORAGE;
- }
+ // TODO: Update invitation key if necessary?
}
pthread_mutex_unlock(&(mesh->mesh_mutex));
+
return mesh->invitation_key;
}
}
pthread_mutex_lock(&(mesh->mesh_mutex));
- bool rval = modify_config_file(mesh, node->name, "CanonicalAddress", canonical_address, 1);
+
+ node_t *n = (node_t *)node;
+ free(n->canonical_address);
+ n->canonical_address = canonical_address;
+ n->status.dirty = true;
+
pthread_mutex_unlock(&(mesh->mesh_mutex));
- free(canonical_address);
- return rval;
+ return true;
}
bool meshlink_add_address(meshlink_handle_t *mesh, const char *address) {
return false;
}
- bool rval = false;
-
- pthread_mutex_lock(&(mesh->mesh_mutex));
- rval = append_config_file(mesh, mesh->self->name, "Address", address);
- pthread_mutex_unlock(&(mesh->mesh_mutex));
-
+ bool rval = meshlink_add_address(mesh, address);
free(address);
+
return rval;
}
}
close_network_connections(mesh);
- exit_configuration(&mesh->config);
- char portstr[10];
- snprintf(portstr, sizeof(portstr), "%d", port);
- portstr[sizeof(portstr) - 1] = 0;
+ // TODO: write meshlink.conf again
- modify_config_file(mesh, mesh->name, "Port", portstr, true);
-
- init_configuration(&mesh->config);
-
- if(!read_server_config(mesh)) {
- meshlink_errno = MESHLINK_ESTORAGE;
- } else if(!setup_network(mesh)) {
+ if(!setup_network(mesh)) {
meshlink_errno = MESHLINK_ENETWORK;
} else {
rval = true;
}
// Ensure no host configuration file with that name exists
- char filename[PATH_MAX];
- snprintf(filename, sizeof(filename), "%s" SLASH "hosts" SLASH "%s", mesh->confbase, name);
-
- if(!access(filename, F_OK)) {
+ if(config_exists(mesh, name)) {
logger(mesh, MESHLINK_DEBUG, "A host config file for %s already exists!\n", name);
meshlink_errno = MESHLINK_EEXIST;
pthread_mutex_unlock(&(mesh->mesh_mutex));
free(fingerprint);
- // Create a file containing the details of the invitation.
- snprintf(filename, sizeof(filename), "%s" SLASH "invitations" SLASH "%s", mesh->confbase, cookiehash);
- int ifd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0600);
+ /* Construct the invitation file */
+ uint8_t outbuf[4096];
+ packmsg_output_t inv = {outbuf, sizeof(outbuf)};
- if(!ifd) {
- logger(mesh, MESHLINK_DEBUG, "Could not create invitation file %s: %s\n", filename, strerror(errno));
- meshlink_errno = MESHLINK_ESTORAGE;
- pthread_mutex_unlock(&(mesh->mesh_mutex));
- return NULL;
- }
+ packmsg_add_uint32(&inv, MESHLINK_INVITATION_VERSION);
+ packmsg_add_str(&inv, name);
+ packmsg_add_int32(&inv, DEV_CLASS_UNKNOWN); /* TODO: allow this to be set by inviter? */
- FILE *f = fdopen(ifd, "w");
+ /* TODO: Add several host config files to bootstrap connections */
+ config_t configs[5] = {NULL};
+ int count = 0;
- if(!f) {
- abort();
+ if(config_read(mesh, mesh->self->name, &configs[count])) {
+ count++;
}
- // Fill in the details.
- fprintf(f, "Name = %s\n", name);
- fprintf(f, "ConnectTo = %s\n", mesh->self->name);
-
- // Copy Broadcast and Mode
- snprintf(filename, sizeof(filename), "%s" SLASH "meshlink.conf", mesh->confbase);
- FILE *tc = fopen(filename, "r");
+ /* Append host config files to the invitation file */
+ packmsg_add_array(&inv, count);
- if(tc) {
- char buf[1024];
-
- while(fgets(buf, sizeof(buf), tc)) {
- if((!strncasecmp(buf, "Mode", 4) && strchr(" \t=", buf[4]))
- || (!strncasecmp(buf, "Broadcast", 9) && strchr(" \t=", buf[9]))) {
- fputs(buf, f);
+ for(int i = 0; i < count; i++) {
+ packmsg_add_bin(&inv, configs[i].buf, configs[i].len);
+ config_free(&configs[i]);
+ }
- // Make sure there is a newline character.
- if(!strchr(buf, '\n')) {
- fputc('\n', f);
- }
- }
- }
+ config_t config = {outbuf, packmsg_output_size(&inv, outbuf)};
- fclose(tc);
- } else {
- logger(mesh, MESHLINK_DEBUG, "Could not create %s: %s\n", filename, strerror(errno));
+ if(!invitation_write(mesh, cookiehash, &config)) {
+ logger(mesh, MESHLINK_DEBUG, "Could not create invitation file %s: %s\n", cookiehash, strerror(errno));
meshlink_errno = MESHLINK_ESTORAGE;
pthread_mutex_unlock(&(mesh->mesh_mutex));
return NULL;
}
- fprintf(f, "#---------------------------------------------------------------#\n");
- fprintf(f, "Name = %s\n", mesh->self->name);
-
- snprintf(filename, sizeof(filename), "%s" SLASH "hosts" SLASH "%s", mesh->confbase, mesh->self->name);
- fcopy(f, filename);
- fclose(f);
-
// Create an URL from the local address, key hash and cookie
char *url;
xasprintf(&url, "%s/%s%s", address, hash, cookie);
return NULL;
}
- pthread_mutex_lock(&(mesh->mesh_mutex));
+ config_t config;
- char filename[PATH_MAX];
- snprintf(filename, sizeof(filename), "%s" SLASH "hosts" SLASH "%s", mesh->confbase, mesh->self->name);
- FILE *f = fopen(filename, "r");
+ // Get our config file
- if(!f) {
- logger(mesh, MESHLINK_DEBUG, "Could not open %s: %s\n", filename, strerror(errno));
+ pthread_mutex_lock(&(mesh->mesh_mutex));
+
+ if(!config_read(mesh, mesh->self->name, &config)) {
meshlink_errno = MESHLINK_ESTORAGE;
- pthread_mutex_unlock(&(mesh->mesh_mutex));
+ pthread_mutex_unlock(&mesh->mesh_mutex);
return NULL;
}
- fseek(f, 0, SEEK_END);
- int fsize = ftell(f);
- rewind(f);
+ pthread_mutex_unlock(&(mesh->mesh_mutex));
+
+ // Prepare a base64-encoded packmsg array containing our config file
- size_t len = fsize + 9 + strlen(mesh->self->name);
- char *buf = xmalloc(len);
- snprintf(buf, len, "Name = %s\n", mesh->self->name);
+ uint8_t *buf = xmalloc(((config.len + 4) * 4) / 3 + 4);
+ packmsg_output_t out = {buf, config.len + 4};
+ packmsg_add_array(&out, 1);
+ packmsg_add_bin(&out, config.buf, config.len);
+ config_free(&config);
- if(fread(buf + len - fsize - 1, fsize, 1, f) != 1) {
- logger(mesh, MESHLINK_DEBUG, "Error reading from %s: %s\n", filename, strerror(errno));
- fclose(f);
+ if(!packmsg_output_ok(&out)) {
+ logger(mesh, MESHLINK_DEBUG, "Error creating export data\n");
+ meshlink_errno = MESHLINK_EINTERNAL;
free(buf);
- meshlink_errno = MESHLINK_ESTORAGE;
- pthread_mutex_unlock(&(mesh->mesh_mutex));
return NULL;
}
- fclose(f);
- buf[len - 1] = 0;
+ b64encode_urlsafe(buf, (char *)buf, packmsg_output_size(&out, buf));
- pthread_mutex_unlock(&(mesh->mesh_mutex));
- return buf;
+ return (char *)buf;
}
bool meshlink_import(meshlink_handle_t *mesh, const char *data) {
if(!mesh || !data) {
+ abort();
meshlink_errno = MESHLINK_EINVAL;
return false;
}
- pthread_mutex_lock(&(mesh->mesh_mutex));
+ size_t datalen = strlen(data);
+ uint8_t *buf = xmalloc(datalen);
+ int buflen = b64decode(data, buf, datalen);
- if(strncmp(data, "Name = ", 7)) {
+ if(!buflen) {
+ abort();
logger(mesh, MESHLINK_DEBUG, "Invalid data\n");
meshlink_errno = MESHLINK_EPEER;
- pthread_mutex_unlock(&(mesh->mesh_mutex));
return false;
}
- char *end = strchr(data + 7, '\n');
+ packmsg_input_t in = {buf, buflen};
+ uint32_t count = packmsg_get_array(&in);
- if(!end) {
+ if(!count) {
+ abort();
logger(mesh, MESHLINK_DEBUG, "Invalid data\n");
meshlink_errno = MESHLINK_EPEER;
- pthread_mutex_unlock(&(mesh->mesh_mutex));
return false;
}
- int len = end - (data + 7);
- char name[len + 1];
- memcpy(name, data + 7, len);
- name[len] = 0;
+ pthread_mutex_lock(&(mesh->mesh_mutex));
- if(!check_id(name)) {
- logger(mesh, MESHLINK_DEBUG, "Invalid Name\n");
- meshlink_errno = MESHLINK_EPEER;
- pthread_mutex_unlock(&(mesh->mesh_mutex));
- return false;
- }
+ while(count--) {
+ const void *data;
+ uint32_t len = packmsg_get_bin_raw(&in, &data);
- char filename[PATH_MAX];
- snprintf(filename, sizeof(filename), "%s" SLASH "hosts" SLASH "%s", mesh->confbase, name);
+ if(!len) {
+ break;
+ }
- if(!access(filename, F_OK)) {
- logger(mesh, MESHLINK_DEBUG, "File %s already exists, not importing\n", filename);
- meshlink_errno = MESHLINK_EEXIST;
- pthread_mutex_unlock(&(mesh->mesh_mutex));
- return false;
- }
+ packmsg_input_t in2 = {data, len};
+ uint32_t version = packmsg_get_uint32(&in2);
+ char *name = packmsg_get_str_dup(&in2);
- if(errno != ENOENT) {
- logger(mesh, MESHLINK_DEBUG, "Error accessing %s: %s\n", filename, strerror(errno));
- meshlink_errno = MESHLINK_ESTORAGE;
- pthread_mutex_unlock(&(mesh->mesh_mutex));
- return false;
- }
+ if(!packmsg_input_ok(&in2) || version != MESHLINK_CONFIG_VERSION || !check_id(name)) {
+ free(name);
+ packmsg_input_invalidate(&in2);
+ break;
+ }
- FILE *f = fopen(filename, "w");
+ if(!check_id(name)) {
+ free(name);
+ break;
+ }
- if(!f) {
- logger(mesh, MESHLINK_DEBUG, "Could not create %s: %s\n", filename, strerror(errno));
- meshlink_errno = MESHLINK_ESTORAGE;
- pthread_mutex_unlock(&(mesh->mesh_mutex));
- return false;
- }
+ node_t *n = lookup_node(mesh, name);
- fwrite(end + 1, strlen(end + 1), 1, f);
- fclose(f);
+ if(n) {
+ free(name);
+ logger(mesh, MESHLINK_DEBUG, "Node %s already exists, not importing\n", name);
+ continue;
+ }
- load_all_nodes(mesh);
+ n = new_node();
+ n->name = name;
+ n->devclass = packmsg_get_int32(&in2);
+ n->status.blacklisted = packmsg_get_bool(&in2);
+ const void *key;
+ uint32_t keylen = packmsg_get_bin_raw(&in2, &key);
+
+ if(keylen == 32) {
+ n->ecdsa = ecdsa_set_public_key(key);
+ }
+
+ n->canonical_address = packmsg_get_str_dup(&in2);
+ uint32_t count = packmsg_get_array(&in2);
+
+ if(count > 5) {
+ count = 5;
+ }
+
+ for(uint32_t i = 0; i < count; i++) {
+ n->recent[i] = packmsg_get_sockaddr(&in2);
+ }
+
+ if(!packmsg_done(&in2) || keylen != 32) {
+ abort();
+ packmsg_input_invalidate(&in2);
+ free_node(n);
+ } else {
+ config_t config = {data, len};
+ config_write(mesh, n->name, &config);
+ node_add(mesh, n);
+ }
+ }
pthread_mutex_unlock(&(mesh->mesh_mutex));
+
+ if(!packmsg_done(&in)) {
+ abort();
+ logger(mesh, MESHLINK_ERROR, "Invalid data\n");
+ meshlink_errno = MESHLINK_EPEER;
+ return false;
+ }
+
return true;
}
node_t *n;
n = (node_t *)node;
n->status.blacklisted = true;
+ n->status.dirty = true;
logger(mesh, MESHLINK_DEBUG, "Blacklisted %s.\n", node->name);
- //Make blacklisting persistent in the config file
- append_config_file(mesh, n->name, "blacklisted", "yes");
-
//Immediately terminate any connections we have with the blacklisted node
for list_each(connection_t, c, mesh->connections) {
if(c->node == n) {
node_t *n = (node_t *)node;
n->status.blacklisted = false;
-
- //TODO: remove blacklisted = yes from the config file
+ n->status.dirty = true;
pthread_mutex_unlock(&(mesh->mesh_mutex));
return;
*/
void meshlink_hint_address(meshlink_handle_t *mesh, meshlink_node_t *node, const struct sockaddr *addr) {
if(!mesh || !node || !addr) {
- return;
- }
-
- // Ignore hints about ourself.
- if((node_t *)node == mesh->self) {
+ meshlink_errno = EINVAL;
return;
}
pthread_mutex_lock(&(mesh->mesh_mutex));
- char *host = NULL, *port = NULL, *str = NULL;
- sockaddr2str((const sockaddr_t *)addr, &host, &port);
-
- if(host && port) {
- xasprintf(&str, "%s %s", host, port);
-
- if((strncmp("fe80", host, 4) != 0) && (strncmp("127.", host, 4) != 0) && (strcmp("localhost", host) != 0)) {
- modify_config_file(mesh, node->name, "Address", str, 5);
- } else {
- logger(mesh, MESHLINK_DEBUG, "Not adding Link Local IPv6 Address to config\n");
- }
- }
-
- free(str);
- free(host);
- free(port);
+ node_t *n = (node_t *)node;
+ memmove(n->recent + 1, n->recent, 4 * sizeof(*n->recent));
+ memcpy(n->recent, addr, SALEN(*addr));
+ n->status.dirty = true;
pthread_mutex_unlock(&(mesh->mesh_mutex));
// @TODO do we want to fire off a connection attempt right away?
*/
extern meshlink_handle_t *meshlink_open(const char *confbase, const char *name, const char *appname, dev_class_t devclass);
+/// Open or create a MeshLink instance that uses encrypted storage.
+/** This function opens or creates a MeshLink instance.
+ * The state is stored in the configuration directory passed in the variable @a confbase @a.
+ * If the configuration directory does not exist yet, for example when it is the first time
+ * this instance is opened, the configuration directory will be automatically created and initialized.
+ * However, the parent directory should already exist, otherwise an error will be returned.
+ *
+ * The name given should be a unique identifier for this instance.
+ *
+ * This function returns a pointer to a struct meshlink_handle that will be allocated by MeshLink.
+ * When the application does no longer need to use this handle, it must call meshlink_close() to
+ * free its resources.
+ *
+ * This function does not start any network I/O yet. The application should
+ * first set callbacks, and then call meshlink_start().
+ *
+ * @param confbase The directory in which MeshLink will store its configuration files.
+ * After the function returns, the application is free to overwrite or free @a confbase @a.
+ * @param name The name which this instance of the application will use in the mesh.
+ * After the function returns, the application is free to overwrite or free @a name @a.
+ * @param appname The application name which will be used in the mesh.
+ * After the function returns, the application is free to overwrite or free @a name @a.
+ * @param devclass The device class which will be used in the mesh.
+ * @param key A pointer to a key used to encrypt storage.
+ * @param keylen The length of the key in bytes.
+ *
+ * @return A pointer to a meshlink_handle_t which represents this instance of MeshLink, or NULL in case of an error.
+ * The pointer is valid until meshlink_close() is called.
+ */
+extern meshlink_handle_t *meshlink_open_encrypted(const char *confbase, const char *name, const char *appname, dev_class_t devclass, const void *key, size_t keylen);
+
/// Start MeshLink.
/** This function causes MeshLink to open network sockets, make outgoing connections, and
* create a new thread, which will handle all network I/O.
static const char meshlink_tcp_label[] = "MeshLink TCP";
static const char meshlink_udp_label[] = "MeshLink UDP";
+#define MESHLINK_CONFIG_VERSION 1
+#define MESHLINK_INVITATION_VERSION 1
+
struct CattaServer;
struct CattaSServiceBrowser;
struct CattaSimplePoll;
void *priv;
char *appname;
- dev_class_t devclass;
+ int32_t devclass;
char *confbase;
FILE *conffile;
struct node_t *self;
- struct splay_tree_t *config;
struct splay_tree_t *edges;
struct splay_tree_t *nodes;
hash_t *node_udp_cache;
struct connection_t *everyone;
+ struct ecdsa *private_key;
struct ecdsa *invitation_key;
int invitation_timeout;
struct CattaSimplePoll *catta_poll;
struct CattaSEntryGroup *catta_group;
char *catta_servicetype;
+
+ void *config_key;
};
/// A handle for a MeshLink node.
splay_tree_t *nodes = splay_alloc_tree(node_compare_devclass_asc_lsc_desc, NULL);
for splay_each(node_t, n, mesh->nodes) {
- logger(mesh, MESHLINK_DEBUG, "* n->devclass = %d", n->devclass);
+ logger(mesh, MESHLINK_DEBUG, "* %s->devclass = %d", n->name, n->devclass);
if(n != mesh->self && n->devclass <= mesh->devclass && !n->connection && (n->last_connect_try == 0 || (time(NULL) - n->last_connect_try) > retry_timeout)) {
splay_insert(nodes, n);
}
if(nodes->head) {
- logger(mesh, MESHLINK_DEBUG, "* found best one for initial connect");
-
//timeout = 0;
connect_to = (node_t *)nodes->head->data;
+
+ logger(mesh, MESHLINK_DEBUG, "* found best one for initial connect: %s", connect_to->name);
} else {
logger(mesh, MESHLINK_DEBUG, "* could not find node for initial connect");
}
if(!connect_to && min_connects <= cur_connects && cur_connects < max_connects) {
unsigned int connects = 0;
- for(unsigned int devclass = 0; devclass <= mesh->devclass; ++devclass) {
+ for(int32_t devclass = 0; devclass <= mesh->devclass; ++devclass) {
for list_each(connection_t, c, mesh->connections) {
if(c->status.active && c->node && c->node->devclass == devclass) {
connects += 1;
bool found = false;
for list_each(outgoing_t, outgoing, mesh->outgoings) {
- if(!strcmp(outgoing->name, connect_to->name)) {
+ if(outgoing->node == connect_to) {
found = true;
break;
}
if(!found) {
logger(mesh, MESHLINK_DEBUG, "Autoconnecting to %s", connect_to->name);
outgoing_t *outgoing = xzalloc(sizeof(outgoing_t));
- outgoing->mesh = mesh;
- outgoing->name = xstrdup(connect_to->name);
+ outgoing->node = connect_to;
list_insert_tail(mesh->outgoings, outgoing);
setup_outgoing_connection(mesh, outgoing);
} else {
if(min_connects < cur_connects /*&& cur_connects <= max_connects*/) {
unsigned int connects = 0;
- for(unsigned int devclass = 0; devclass <= mesh->devclass; ++devclass) {
+ for(int32_t devclass = 0; devclass <= mesh->devclass; ++devclass) {
for list_each(connection_t, c, mesh->connections) {
if(c->status.active && c->node && c->node->devclass == devclass) {
connects += 1;
#include "list.h"
typedef struct outgoing_t {
- char *name;
- struct splay_tree_t *config_tree;
- int timeout;
+ struct node_t *node;
enum {
OUTGOING_START,
OUTGOING_CANONICAL,
OUTGOING_END,
OUTGOING_NO_KNOWN_ADDRESSES,
} state;
- struct config_t *cfg;
+ int timeout;
+ timeout_t ev;
struct addrinfo *ai;
struct addrinfo *aip;
- timeout_t ev;
- struct meshlink_handle *mesh;
} outgoing_t;
extern int maxoutbufsize;
#include "connection.h"
#include "node.h"
+extern void init_outgoings(struct meshlink_handle *mesh);
+extern void exit_outgoings(struct meshlink_handle *mesh);
+
extern void retry_outgoing(struct meshlink_handle *mesh, outgoing_t *);
extern void handle_incoming_vpn_data(struct event_loop_t *loop, void *, int);
extern void finish_connecting(struct meshlink_handle *mesh, struct connection_t *);
extern bool setup_myself_reloadable(struct meshlink_handle *mesh);
extern bool setup_network(struct meshlink_handle *mesh);
extern void setup_outgoing_connection(struct meshlink_handle *mesh, struct outgoing_t *);
-extern void try_outgoing_connections(struct meshlink_handle *mesh);
extern void close_network_connections(struct meshlink_handle *mesh);
extern int main_loop(struct meshlink_handle *mesh);
extern void terminate_connection(struct meshlink_handle *mesh, struct connection_t *, bool);
-extern bool node_read_ecdsa_public_key(struct meshlink_handle *mesh, struct node_t *);
+extern bool node_read_public_key(struct meshlink_handle *mesh, struct node_t *);
+extern bool node_read_full(struct meshlink_handle *mesh, struct node_t *);
extern bool read_ecdsa_public_key(struct meshlink_handle *mesh, struct connection_t *);
extern bool read_ecdsa_private_key(struct meshlink_handle *mesh);
+extern bool node_write_config(struct meshlink_handle *mesh, struct node_t *);
extern void send_mtu_probe(struct meshlink_handle *mesh, struct node_t *);
extern void handle_meta_connection_data(struct meshlink_handle *mesh, struct connection_t *);
extern void retry(struct meshlink_handle *mesh);
#include "meshlink_internal.h"
#include "net.h"
#include "netutl.h"
+#include "packmsg.h"
#include "protocol.h"
#include "route.h"
#include "utils.h"
#include "xalloc.h"
-bool node_read_ecdsa_public_key(meshlink_handle_t *mesh, node_t *n) {
- if(ecdsa_active(n->ecdsa)) {
- return true;
+/// Helper function to start parsing a host config file
+static bool node_get_config(meshlink_handle_t *mesh, node_t *n, config_t *config, packmsg_input_t *in) {
+ if(!config_read(mesh, n->name, config)) {
+ return false;
}
- splay_tree_t *config_tree;
- char *p;
+ in->ptr = config->buf;
+ in->len = config->len;
- init_configuration(&config_tree);
+ uint32_t version = packmsg_get_uint32(in);
- if(!read_host_config(mesh, config_tree, n->name)) {
- goto exit;
+ if(version != MESHLINK_CONFIG_VERSION) {
+ config_free(config);
+ return false;
}
- /* First, check for simple ECDSAPublicKey statement */
+ const char *name;
+ uint32_t len = packmsg_get_str_raw(in, &name);
- if(get_config_string(lookup_config(config_tree, "ECDSAPublicKey"), &p)) {
- n->ecdsa = ecdsa_set_base64_public_key(p);
- free(p);
+ if(len != strlen(n->name) || strncmp(name, n->name, len)) {
+ config_free(config);
+ return false;
}
-exit:
- exit_configuration(&config_tree);
- return n->ecdsa;
+ return true;
}
-bool read_ecdsa_public_key(meshlink_handle_t *mesh, connection_t *c) {
- if(ecdsa_active(c->ecdsa)) {
- return true;
- }
-
- char *p;
-
- if(!c->config_tree) {
- init_configuration(&c->config_tree);
+/// Read just the devclass from a host config file. Used at startup when reading all host config files.
+bool node_read_devclass(meshlink_handle_t *mesh, node_t *n) {
+ config_t config;
+ packmsg_input_t in;
- if(!read_host_config(mesh, c->config_tree, c->name)) {
- return false;
- }
+ if(!node_get_config(mesh, n, &config, &in)) {
+ return false;
}
- /* First, check for simple ECDSAPublicKey statement */
+ int32_t devclass = packmsg_get_int32(&in);
+ bool blacklisted = packmsg_get_bool(&in);
+ config_free(&config);
- if(get_config_string(lookup_config(c->config_tree, "ECDSAPublicKey"), &p)) {
- c->ecdsa = ecdsa_set_base64_public_key(p);
- free(p);
- return c->ecdsa;
+ if(!packmsg_input_ok(&in) || devclass < 0 || devclass > _DEV_CLASS_MAX) {
+ return false;
}
- return false;
+ n->devclass = devclass;
+ n->status.blacklisted = blacklisted;
+ return true;
}
-bool read_ecdsa_private_key(meshlink_handle_t *mesh) {
- FILE *fp;
- char filename[PATH_MAX];
+/// Read the public key from a host config file. Used whenever we need to start an SPTPS session.
+bool node_read_public_key(meshlink_handle_t *mesh, node_t *n) {
+ if(ecdsa_active(n->ecdsa)) {
+ return true;
+ }
- snprintf(filename, PATH_MAX, "%s" SLASH "ecdsa_key.priv", mesh->confbase);
- fp = fopen(filename, "rb");
+ config_t config;
+ packmsg_input_t in;
- if(!fp) {
- logger(mesh, MESHLINK_ERROR, "Error reading ECDSA private key file: %s", strerror(errno));
+ if(!node_get_config(mesh, n, &config, &in)) {
return false;
}
- mesh->self->connection->ecdsa = ecdsa_read_pem_private_key(fp);
- fclose(fp);
+ packmsg_get_int32(&in); /* devclass */
+ packmsg_get_bool(&in); /* blacklisted */
+
+ const void *key;
+ uint32_t len = packmsg_get_bin_raw(&in, &key);
- if(!mesh->self->connection->ecdsa) {
- logger(mesh, MESHLINK_ERROR, "Reading ECDSA private key file failed: %s", strerror(errno));
+ if(len != 32) {
+ config_free(&config);
+ return false;
}
- return mesh->self->connection->ecdsa;
+ n->ecdsa = ecdsa_set_public_key(key);
+ config_free(&config);
+ return true;
}
-static bool read_invitation_key(meshlink_handle_t *mesh) {
- FILE *fp;
- char filename[PATH_MAX];
-
- if(mesh->invitation_key) {
- ecdsa_free(mesh->invitation_key);
- mesh->invitation_key = NULL;
+/// Read the full host config file. Used whenever we need to start an SPTPS session.
+bool node_read_full(meshlink_handle_t *mesh, node_t *n) {
+ if(n->canonical_address) {
+ return true;
}
- snprintf(filename, PATH_MAX, "%s" SLASH "invitations" SLASH "ecdsa_key.priv", mesh->confbase);
+ config_t config;
+ packmsg_input_t in;
- fp = fopen(filename, "rb");
+ if(!node_get_config(mesh, n, &config, &in)) {
+ return false;
+ }
- if(fp) {
- mesh->invitation_key = ecdsa_read_pem_private_key(fp);
- fclose(fp);
+ packmsg_get_int32(&in); /* devclass */
+ packmsg_get_bool(&in); /* blacklisted */
+ const void *key;
+ uint32_t len = packmsg_get_bin_raw(&in, &key); /* public key */
- if(!mesh->invitation_key) {
- logger(mesh, MESHLINK_ERROR, "Reading ECDSA private key file `%s' failed: %s", filename, strerror(errno));
- }
+ if(len != 32) {
+ return false;
}
- return mesh->invitation_key;
-}
-
-bool node_read_devclass(meshlink_handle_t *mesh, node_t *n) {
+ if(!ecdsa_active(n->ecdsa)) {
+ n->ecdsa = ecdsa_set_public_key(key);
+ }
- splay_tree_t *config_tree;
- char *p;
+ n->canonical_address = packmsg_get_str_dup(&in);
- init_configuration(&config_tree);
+ uint32_t count = packmsg_get_array(&in);
- if(!read_host_config(mesh, config_tree, n->name)) {
- goto exit;
+ if(count > 5) {
+ count = 5;
}
- if(get_config_string(lookup_config(config_tree, "DeviceClass"), &p)) {
- n->devclass = atoi(p);
- free(p);
+ for(uint32_t i = 0; i < count; i++) {
+ n->recent[i] = packmsg_get_sockaddr(&in);
}
- if((int)n->devclass < 0 || n->devclass > _DEV_CLASS_MAX) {
- n->devclass = _DEV_CLASS_MAX;
- }
+ config_free(&config);
-exit:
- exit_configuration(&config_tree);
- return n->devclass != 0;
+ return packmsg_done(&in);
}
-bool node_write_devclass(meshlink_handle_t *mesh, node_t *n) {
-
- if((int)n->devclass < 0 || n->devclass > _DEV_CLASS_MAX) {
- return false;
- }
+bool node_write_config(meshlink_handle_t *mesh, node_t *n) {
+ uint8_t buf[4096];
+ packmsg_output_t out = {buf, sizeof(buf)};
- bool result = false;
+ packmsg_add_uint32(&out, MESHLINK_CONFIG_VERSION);
+ packmsg_add_str(&out, n->name);
+ packmsg_add_int32(&out, n->devclass);
+ assert(n->devclass != 3);
+ packmsg_add_bool(&out, n->status.blacklisted);
- splay_tree_t *config_tree;
- init_configuration(&config_tree);
+ if(ecdsa_active(n->ecdsa)) {
+ packmsg_add_bin(&out, ecdsa_get_public_key(n->ecdsa), 32);
+ } else {
+ packmsg_add_bin(&out, "", 0);
+ }
- // ignore read errors; in case the file does not exist we will create it
- read_host_config(mesh, config_tree, n->name);
+ packmsg_add_str(&out, n->canonical_address ? n->canonical_address : "");
- config_t *cnf = lookup_config(config_tree, "DeviceClass");
+ uint32_t count = 0;
- if(!cnf) {
- cnf = new_config();
- cnf->variable = xstrdup("DeviceClass");
- config_add(config_tree, cnf);
+ for(uint32_t i = 0; i < 5; i++) {
+ if(n->recent[i].sa.sa_family) {
+ count++;
+ } else {
+ break;
+ }
}
- set_config_int(cnf, n->devclass);
+ packmsg_add_array(&out, count);
- if(!write_host_config(mesh, config_tree, n->name)) {
- goto fail;
+ for(uint32_t i = 0; i < count; i++) {
+ packmsg_add_sockaddr(&out, &n->recent[i]);
}
- result = true;
+ if(!packmsg_output_ok(&out)) {
+ return false;
+ }
-fail:
- exit_configuration(&config_tree);
- return result;
+ config_t config = {buf, packmsg_output_size(&out, buf)};
+ return config_write(mesh, n->name, &config);
}
void load_all_nodes(meshlink_handle_t *mesh) {
closedir(dir);
}
-
-char *get_name(meshlink_handle_t *mesh) {
- char *name = NULL;
-
- get_config_string(lookup_config(mesh->config, "Name"), &name);
-
- if(!name) {
- return NULL;
- }
-
- if(!check_id(name)) {
- logger(mesh, MESHLINK_ERROR, "Invalid name for mesh->self!");
- free(name);
- return NULL;
- }
-
- return name;
-}
-
-bool setup_myself_reloadable(meshlink_handle_t *mesh) {
- mesh->localdiscovery = true;
- keylifetime = 3600; // TODO: check if this can be removed as well
- mesh->maxtimeout = 900;
- mesh->self->options |= OPTION_PMTU_DISCOVERY;
-
- read_invitation_key(mesh);
-
- return true;
-}
-
/*
Add listening sockets.
*/
Configure node_t mesh->self and set up the local sockets (listen only)
*/
bool setup_myself(meshlink_handle_t *mesh) {
- char *name;
- char *address = NULL;
-
- if(!(name = get_name(mesh))) {
- logger(mesh, MESHLINK_ERROR, "Name for MeshLink instance required!");
- return false;
- }
-
- mesh->self = new_node();
- mesh->self->connection = new_connection();
- mesh->self->name = name;
- mesh->self->devclass = mesh->devclass;
- mesh->self->connection->name = xstrdup(name);
- read_host_config(mesh, mesh->config, name);
-
- if(!get_config_string(lookup_config(mesh->config, "Port"), &mesh->myport)) {
- int port = check_port(mesh);
-
- if(port == 0) {
- return false;
- }
-
- xasprintf(&mesh->myport, "%d", port);
- }
-
- mesh->self->connection->options = 0;
- mesh->self->connection->protocol_major = PROT_MAJOR;
- mesh->self->connection->protocol_minor = PROT_MINOR;
-
- mesh->self->options |= PROT_MINOR << 24;
+ /* Set some defaults */
- if(!read_ecdsa_private_key(mesh)) {
- return false;
- }
-
- /* Ensure mesh->myport is numeric */
-
- if(!atoi(mesh->myport)) {
- struct addrinfo *ai = str2addrinfo("localhost", mesh->myport, SOCK_DGRAM);
- sockaddr_t sa;
-
- if(!ai || !ai->ai_addr) {
- return false;
- }
-
- free(mesh->myport);
- memcpy(&sa, ai->ai_addr, ai->ai_addrlen);
- sockaddr2str(&sa, NULL, &mesh->myport);
- }
-
- /* Check some options */
-
- if(!setup_myself_reloadable(mesh)) {
- return false;
- }
-
- /* Compression */
-
- // TODO: drop compression in the packet layer?
- mesh->self->incompression = 0;
- mesh->self->connection->outcompression = 0;
+ mesh->localdiscovery = true;
+ keylifetime = 3600; // TODO: check if this can be removed as well
+ mesh->maxtimeout = 900;
+ mesh->self->options |= OPTION_PMTU_DISCOVERY;
/* Done */
mesh->self->status.reachable = true;
mesh->self->last_state_change = mesh->loop.now.tv_sec;
- node_write_devclass(mesh, mesh->self);
node_add(mesh, mesh->self);
graph(mesh);
mesh->listen_sockets = 0;
- if(!add_listen_address(mesh, address, NULL)) {
+ if(!add_listen_address(mesh, NULL, NULL)) {
if(strcmp(mesh->myport, "0")) {
logger(mesh, MESHLINK_INFO, "Could not bind to port %s, asking OS to choose one for us", mesh->myport);
free(mesh->myport);
return false;
}
- if(!add_listen_address(mesh, address, NULL)) {
+ if(!add_listen_address(mesh, NULL, NULL)) {
return false;
}
} else {
}
}
- if(mesh->outgoings) {
- list_delete_list(mesh->outgoings);
- }
-
- if(mesh->self && mesh->self->connection) {
- terminate_connection(mesh, mesh->self->connection, false);
- free_connection(mesh->self->connection);
- }
-
for(int i = 0; i < mesh->listen_sockets; i++) {
io_del(&mesh->loop, &mesh->listen_socket[i].tcp);
io_del(&mesh->loop, &mesh->listen_socket[i].udp);
return ai;
}
+// Build a list of recently seen addresses.
+static struct addrinfo *get_recent_addresses(node_t *n) {
+ struct addrinfo *ai = NULL;
+ struct addrinfo *aip;
+
+ for(int i = 0; i < 5; i++) {
+ if(!n->recent[i].sa.sa_family) {
+ break;
+ }
+
+ // Create a new struct addrinfo, and put it at the end of the list.
+ struct addrinfo *nai = xzalloc(sizeof(*nai) + SALEN(n->recent[i].sa));
+
+ if(!ai) {
+ ai = nai;
+ } else {
+ aip->ai_next = nai;
+ }
+
+ aip = nai;
+
+ nai->ai_family = n->recent[i].sa.sa_family;
+ nai->ai_socktype = SOCK_STREAM;
+ nai->ai_protocol = IPPROTO_TCP;
+ nai->ai_addrlen = SALEN(n->recent[i].sa);
+ nai->ai_addr = (struct sockaddr *)(nai + 1);
+ memcpy(nai->ai_addr, &n->recent[i], nai->ai_addrlen);
+ }
+
+ return ai;
+}
+
// Free struct addrinfo list from get_known_addresses().
static void free_known_addresses(struct addrinfo *ai) {
for(struct addrinfo *aip = ai, *next; aip; aip = next) {
}
}
-static bool get_recent(meshlink_handle_t *mesh, outgoing_t *outgoing) {
- node_t *n = lookup_node(mesh, outgoing->name);
-
- if(!n) {
+static struct addrinfo *get_canonical_address(node_t *n) {
+ if(!n->canonical_address) {
return false;
}
- outgoing->ai = get_known_addresses(n);
- outgoing->aip = outgoing->ai;
- return outgoing->aip;
-}
-
-static bool get_next_ai(meshlink_handle_t *mesh, outgoing_t *outgoing) {
- if(!outgoing->ai) {
- char *address = NULL;
-
- if(get_config_string(outgoing->cfg, &address)) {
- char *port;
- char *space = strchr(address, ' ');
-
- if(space) {
- port = xstrdup(space + 1);
- *space = 0;
- } else {
- if(!get_config_string(lookup_config(outgoing->config_tree, "Port"), &port)) {
- logger(mesh, MESHLINK_ERROR, "No Port known for %s", outgoing->name);
- return false;
- }
- }
-
- outgoing->ai = str2addrinfo(address, port, SOCK_STREAM);
- free(port);
- free(address);
- }
+ char *address = xstrdup(n->canonical_address);
+ char *port = strchr(address, ' ');
- outgoing->aip = outgoing->ai;
- } else {
- outgoing->aip = outgoing->aip->ai_next;
+ if(!port) {
+ free(address);
+ return false;
}
- return outgoing->aip;
-}
-
-static bool get_next_cfg(meshlink_handle_t *mesh, outgoing_t *outgoing, char *variable) {
- (void)mesh;
+ *port++ = 0;
- if(!outgoing->cfg) {
- outgoing->cfg = lookup_config(outgoing->config_tree, variable);
- } else {
- outgoing->cfg = lookup_config_next(outgoing->config_tree, outgoing->cfg);
- }
+ struct addrinfo *ai = str2addrinfo(address, port, SOCK_STREAM);
+ free(address);
- return outgoing->cfg;
+ return ai;
}
static bool get_next_outgoing_address(meshlink_handle_t *mesh, outgoing_t *outgoing) {
+ (void)mesh;
+
bool start = false;
if(outgoing->state == OUTGOING_START) {
}
if(outgoing->state == OUTGOING_CANONICAL) {
- while(outgoing->aip || get_next_cfg(mesh, outgoing, "CanonicalAddress")) {
- if(get_next_ai(mesh, outgoing)) {
- return true;
- } else {
- freeaddrinfo(outgoing->ai);
- outgoing->ai = NULL;
- outgoing->aip = NULL;
- }
+ if(!outgoing->aip) {
+ outgoing->ai = get_canonical_address(outgoing->node);
+ outgoing->aip = outgoing->ai;
+ } else {
+ outgoing->aip = outgoing->aip->ai_next;
+ }
+
+ if(outgoing->aip) {
+ return true;
}
+ freeaddrinfo(outgoing->ai);
+ outgoing->ai = NULL;
+ outgoing->aip = NULL;
outgoing->state = OUTGOING_RECENT;
}
if(outgoing->state == OUTGOING_RECENT) {
- while(outgoing->aip || get_next_cfg(mesh, outgoing, "Address")) {
- if(get_next_ai(mesh, outgoing)) {
- return true;
- } else {
- freeaddrinfo(outgoing->ai);
- outgoing->ai = NULL;
- outgoing->aip = NULL;
- }
+ if(!outgoing->aip) {
+ outgoing->ai = get_recent_addresses(outgoing->node);
+ outgoing->aip = outgoing->ai;
+ } else {
+ outgoing->aip = outgoing->aip->ai_next;
+ }
+
+ if(outgoing->aip) {
+ return true;
}
+ free_known_addresses(outgoing->ai);
+ outgoing->ai = NULL;
+ outgoing->aip = NULL;
outgoing->state = OUTGOING_KNOWN;
}
if(outgoing->state == OUTGOING_KNOWN) {
if(!outgoing->aip) {
- get_recent(mesh, outgoing);
+ outgoing->ai = get_known_addresses(outgoing->node);
+ outgoing->aip = outgoing->ai;
} else {
outgoing->aip = outgoing->aip->ai_next;
}
if(outgoing->aip) {
return true;
- } else {
- free_known_addresses(outgoing->ai);
- outgoing->ai = NULL;
- outgoing->aip = NULL;
}
+ free_known_addresses(outgoing->ai);
+ outgoing->ai = NULL;
+ outgoing->aip = NULL;
outgoing->state = OUTGOING_END;
}
if(!get_next_outgoing_address(mesh, outgoing)) {
if(outgoing->state == OUTGOING_NO_KNOWN_ADDRESSES) {
- logger(mesh, MESHLINK_ERROR, "No known addresses for %s", outgoing->name);
+ logger(mesh, MESHLINK_ERROR, "No known addresses for %s", outgoing->node->name);
} else {
- logger(mesh, MESHLINK_ERROR, "Could not set up a meta connection to %s", outgoing->name);
+ logger(mesh, MESHLINK_ERROR, "Could not set up a meta connection to %s", outgoing->node->name);
retry_outgoing(mesh, outgoing);
}
char *hostname = sockaddr2hostname(&c->address);
- logger(mesh, MESHLINK_INFO, "Trying to connect to %s at %s", outgoing->name, hostname);
+ logger(mesh, MESHLINK_INFO, "Trying to connect to %s at %s", outgoing->node->name, hostname);
if(!mesh->proxytype) {
c->socket = socket(c->address.sa.sa_family, SOCK_STREAM, IPPROTO_TCP);
}
if(result == -1 && !sockinprogress(sockerrno)) {
- logger(mesh, MESHLINK_ERROR, "Could not connect to %s: %s", outgoing->name, sockstrerror(sockerrno));
+ logger(mesh, MESHLINK_ERROR, "Could not connect to %s: %s", outgoing->node->name, sockstrerror(sockerrno));
free_connection(c);
goto begin;
/* Now that there is a working socket, fill in the rest and register this connection. */
c->status.connecting = true;
- c->name = xstrdup(outgoing->name);
- c->outcompression = mesh->self->connection->outcompression;
+ c->name = xstrdup(outgoing->node->name);
c->last_ping_time = mesh->loop.now.tv_sec;
connection_add(mesh, c);
}
void setup_outgoing_connection(meshlink_handle_t *mesh, outgoing_t *outgoing) {
- bool blacklisted = false;
timeout_del(&mesh->loop, &outgoing->ev);
- node_t *n = lookup_node(mesh, outgoing->name);
+ if(outgoing->node->connection) {
+ logger(mesh, MESHLINK_INFO, "Already connected to %s", outgoing->node->name);
- if(n && n->connection) {
- logger(mesh, MESHLINK_INFO, "Already connected to %s", outgoing->name);
-
- n->connection->outgoing = outgoing;
+ outgoing->node->connection->outgoing = outgoing;
return;
}
if(outgoing->ai) {
- if(outgoing->state == OUTGOING_KNOWN) {
+ if(outgoing->state == OUTGOING_RECENT || outgoing->state == OUTGOING_KNOWN) {
free_known_addresses(outgoing->ai);
} else {
freeaddrinfo(outgoing->ai);
}
}
- outgoing->cfg = NULL;
-
- exit_configuration(&outgoing->config_tree); // discard old configuration if present
- init_configuration(&outgoing->config_tree);
- read_host_config(mesh, outgoing->config_tree, outgoing->name);
- get_config_bool(lookup_config(outgoing->config_tree, "blacklisted"), &blacklisted);
-
outgoing->state = OUTGOING_START;
- if(blacklisted) {
+ if(outgoing->node->status.blacklisted) {
return;
}
c = new_connection();
c->name = xstrdup("<unknown>");
- c->outcompression = mesh->self->connection->outcompression;
c->address = sa;
c->socket = fd;
}
static void free_outgoing(outgoing_t *outgoing) {
- meshlink_handle_t *mesh = outgoing->mesh;
+ meshlink_handle_t *mesh = outgoing->node->mesh;
timeout_del(&mesh->loop, &outgoing->ev);
if(outgoing->ai) {
- if(outgoing->state == OUTGOING_KNOWN) {
+ if(outgoing->state == OUTGOING_RECENT || outgoing->state == OUTGOING_KNOWN) {
free_known_addresses(outgoing->ai);
} else {
freeaddrinfo(outgoing->ai);
}
}
- if(outgoing->config_tree) {
- exit_configuration(&outgoing->config_tree);
- }
-
- if(outgoing->name) {
- free(outgoing->name);
- }
-
free(outgoing);
}
-void try_outgoing_connections(meshlink_handle_t *mesh) {
- /* If there is no outgoing list yet, create one. Otherwise, mark all outgoings as deleted. */
-
- if(!mesh->outgoings) {
- mesh->outgoings = list_alloc((list_action_t)free_outgoing);
- } else {
- for list_each(outgoing_t, outgoing, mesh->outgoings) {
- outgoing->timeout = -1;
- }
- }
-
- /* Make sure there is one outgoing_t in the list for each ConnectTo. */
-
- // TODO: Drop support for ConnectTo since AutoConnect is now always on?
- for(config_t *cfg = lookup_config(mesh->config, "ConnectTo"); cfg; cfg = lookup_config_next(mesh->config, cfg)) {
- char *name;
- get_config_string(cfg, &name);
-
- if(!check_id(name)) {
- logger(mesh, MESHLINK_ERROR,
- "Invalid name for outgoing connection in line %d",
- cfg->line);
- free(name);
- continue;
- }
-
- bool found = false;
-
- for list_each(outgoing_t, outgoing, mesh->outgoings) {
- if(!strcmp(outgoing->name, name)) {
- found = true;
- outgoing->timeout = 0;
- break;
- }
- }
-
- if(!found) {
- outgoing_t *outgoing = xzalloc(sizeof(*outgoing));
- outgoing->mesh = mesh;
- outgoing->name = name;
- list_insert_tail(mesh->outgoings, outgoing);
- setup_outgoing_connection(mesh, outgoing);
- }
- }
-
- /* Terminate any connections whose outgoing_t is to be deleted. */
+void init_outgoings(meshlink_handle_t *mesh) {
+ mesh->outgoings = list_alloc((list_action_t)free_outgoing);
+}
- for list_each(connection_t, c, mesh->connections) {
- if(c->outgoing && c->outgoing->timeout == -1) {
- c->outgoing = NULL;
- logger(mesh, MESHLINK_INFO, "No more outgoing connection to %s", c->name);
- terminate_connection(mesh, c, c->status.active);
- }
+void exit_outgoings(meshlink_handle_t *mesh) {
+ if(mesh->outgoings) {
+ list_delete_list(mesh->outgoings);
+ mesh->outgoings = NULL;
}
-
- /* Delete outgoing_ts for which there is no ConnectTo. */
-
- for list_each(outgoing_t, outgoing, mesh->outgoings)
- if(outgoing->timeout == -1) {
- list_delete_node(mesh->outgoings, node);
- }
}
if(err) {
logger(NULL, MESHLINK_ERROR, "Error while looking up hostname: %s", err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
+ abort();
}
xasprintf(&str, "%s port %s", address, port);
}
}
+void sockaddrcpy_setport(sockaddr_t *a, const sockaddr_t *b, uint16_t port) {
+ sockaddrcpy(a, b);
+
+ switch(b->sa.sa_family) {
+ case AF_INET:
+ a->in.sin_port = port;
+ break;
+
+ case AF_INET6:
+ a->in6.sin6_port = port;
+ break;
+
+ default:
+ break;
+ }
+}
+
void sockaddrfree(sockaddr_t *a) {
if(a->sa.sa_family == AF_UNKNOWN) {
free(a->unknown.address);
sa->in.sin_family = AF_INET;
}
}
+
+void packmsg_add_sockaddr(packmsg_output_t *out, const sockaddr_t *sa) {
+ switch(sa->sa.sa_family) {
+ case AF_INET: {
+ uint8_t buf[6];
+ memcpy(buf + 0, &sa->in.sin_port, 2);
+ memcpy(buf + 2, &sa->in.sin_addr, 4);
+ packmsg_add_ext(out, 4, buf, sizeof(buf));
+ break;
+ }
+
+ case AF_INET6: {
+ uint8_t buf[18];
+ memcpy(buf + 0, &sa->in6.sin6_port, 2);
+ memcpy(buf + 2, &sa->in6.sin6_addr, 16);
+ packmsg_add_ext(out, 6, buf, sizeof(buf));
+ break;
+ }
+
+ default:
+ packmsg_output_invalidate(out);
+ break;
+ }
+}
+
+sockaddr_t packmsg_get_sockaddr(packmsg_input_t *in) {
+ sockaddr_t sa = {0};
+
+ int8_t type;
+ const void *data;
+ uint32_t len = packmsg_get_ext_raw(in, &type, &data);
+
+ switch(type) {
+ case 4:
+ if(len != 6) {
+ packmsg_input_invalidate(in);
+ return sa;
+ }
+
+ sa.sa.sa_family = AF_INET;
+ memcpy(&sa.in.sin_port, (uint8_t *)data + 0, 2);
+ memcpy(&sa.in.sin_addr, (uint8_t *)data + 2, 4);
+ break;
+
+ case 6:
+ if(len != 18) {
+ packmsg_input_invalidate(in);
+ return sa;
+ }
+
+ sa.sa.sa_family = AF_INET6;
+ memcpy(&sa.in6.sin6_port, (uint8_t *)data + 0, 2);
+ memcpy(&sa.in6.sin6_addr, (uint8_t *)data + 2, 16);
+ break;
+
+ default:
+ packmsg_input_invalidate(in);
+ return sa;
+ }
+
+ return sa;
+}
*/
#include "net.h"
+#include "packmsg.h"
extern bool hostnames;
extern void sockaddrunmap(sockaddr_t *);
extern void sockaddrfree(sockaddr_t *);
extern void sockaddrcpy(sockaddr_t *, const sockaddr_t *);
+extern void sockaddrcpy_setport(sockaddr_t *, const sockaddr_t *, uint16_t port);
+
+extern void packmsg_add_sockaddr(struct packmsg_output *out, const sockaddr_t *);
+extern sockaddr_t packmsg_get_sockaddr(struct packmsg_input *in);
#endif
unsigned int blacklisted: 1; /* 1 if the node is blacklist so we never want to speak with him anymore */
unsigned int destroyed: 1; /* 1 if the node is being destroyed, deallocate channels when any callback is triggered */
unsigned int duplicate: 1; /* 1 if the node is duplicate, ie. multiple nodes using the same Name are online */
- unsigned int unused: 20;
+ unsigned int dirty: 1; /* 1 if the configuration of the node is dirty and needs to be written out */
+ unsigned int unused: 19;
} node_status_t;
typedef struct node_t {
void *priv;
uint32_t options; /* options turned on for this node */
- dev_class_t devclass;
+ int32_t devclass;
struct meshlink_handle *mesh; /* The mesh this node belongs to */
struct edge_t *prevedge; /* nearest node from him to us */
struct node_t *via; /* next hop for UDP packets */
- struct splay_tree_t *edge_tree; /* Edges with this node as one of the endpoints */
+ struct splay_tree_t *edge_tree; /* Edges with this node as one of the endpoints */
struct connection_t *connection; /* Connection associated with this node (if a direct connection exists) */
time_t last_connect_try;
uint64_t in_bytes;
uint64_t out_packets;
uint64_t out_bytes;
+
+ char *canonical_address; /* The canonical address of this node, if known */
+ sockaddr_t recent[5]; /* Recently seen addresses */
} node_t;
extern void init_nodes(struct meshlink_handle *mesh);
extern void exit_nodes(struct meshlink_handle *mesh);
extern node_t *new_node(void) __attribute__((__malloc__));
-extern void free_node(node_t *);
-extern void node_add(struct meshlink_handle *mesh, node_t *);
-extern void node_del(struct meshlink_handle *mesh, node_t *);
-extern node_t *lookup_node(struct meshlink_handle *mesh, const char *);
-extern node_t *lookup_node_udp(struct meshlink_handle *mesh, const sockaddr_t *);
-extern void update_node_udp(struct meshlink_handle *mesh, node_t *, const sockaddr_t *);
+extern void free_node(node_t *n);
+extern void node_add(struct meshlink_handle *mesh, node_t *n);
+extern void node_del(struct meshlink_handle *mesh, node_t *n);
+extern node_t *lookup_node(struct meshlink_handle *mesh, const char *name);
+extern node_t *lookup_node_udp(struct meshlink_handle *mesh, const sockaddr_t *sa);
+extern void update_node_udp(struct meshlink_handle *mesh, node_t *n, const sockaddr_t *sa);
#endif
#include "net.h"
#include "netutl.h"
#include "node.h"
+#include "packmsg.h"
#include "prf.h"
#include "protocol.h"
#include "sptps.h"
}
bool send_id(meshlink_handle_t *mesh, connection_t *c) {
-
- int minor = mesh->self->connection->protocol_minor;
-
if(mesh->proxytype && c->outgoing)
if(!send_proxyrequest(mesh, c)) {
return false;
}
- return send_request(mesh, c, "%d %s %d.%d %s", ID, mesh->self->connection->name, mesh->self->connection->protocol_major, minor, mesh->appname);
+ return send_request(mesh, c, "%d %s %d.%d %s", ID, mesh->self->name, PROT_MAJOR, PROT_MINOR, mesh->appname);
}
static bool finalize_invitation(meshlink_handle_t *mesh, connection_t *c, const void *data, uint16_t len) {
- (void)len;
-
- if(strchr(data, '\n')) {
+ if(len != 32) {
logger(mesh, MESHLINK_ERROR, "Received invalid key from invited node %s!\n", c->name);
return false;
}
- // Create a new host config file
- char filename[PATH_MAX];
- snprintf(filename, sizeof(filename), "%s" SLASH "hosts" SLASH "%s", mesh->confbase, c->name);
-
- if(!access(filename, F_OK)) {
- logger(mesh, MESHLINK_ERROR, "Host config file for %s already exists!\n", c->name);
- return false;
- }
-
- FILE *f = fopen(filename, "w");
-
- if(!f) {
- logger(mesh, MESHLINK_ERROR, "Error trying to create %s: %s\n", filename, strerror(errno));
- return false;
- }
-
- fprintf(f, "ECDSAPublicKey = %s\n", (const char *)data);
- fclose(f);
+ // Create a new node
+ node_t *n = new_node();
+ n->name = xstrdup(c->name);
+ n->devclass = DEV_CLASS_UNKNOWN;
+ n->ecdsa = ecdsa_set_public_key(data);
+ n->status.dirty = true;
+ node_add(mesh, n);
+ // TODO: immediately write the config file?
logger(mesh, MESHLINK_INFO, "Key succesfully received from %s", c->name);
//TODO: callback to application to inform of an accepted invitation
- sptps_send_record(&c->sptps, 2, data, 0);
-
- load_all_nodes(mesh);
+ sptps_send_record(&c->sptps, 1, "", 0);
return true;
}
b64encode_urlsafe(hash, cookie, 18);
free(fingerprint);
- char filename[PATH_MAX], usedname[PATH_MAX];
- snprintf(filename, sizeof(filename), "%s" SLASH "invitations" SLASH "%s", mesh->confbase, cookie);
- snprintf(usedname, sizeof(usedname), "%s" SLASH "invitations" SLASH "%s.used", mesh->confbase, cookie);
-
- // Atomically rename the invitation file
- if(rename(filename, usedname)) {
- if(errno == ENOENT) {
- logger(mesh, MESHLINK_ERROR, "Peer %s tried to use non-existing invitation %s\n", c->name, cookie);
- } else {
- logger(mesh, MESHLINK_ERROR, "Error trying to rename invitation %s\n", cookie);
- }
-
- return false;
- }
-
- // Open the renamed file
- FILE *f = fopen(usedname, "r");
-
- if(!f) {
- logger(mesh, MESHLINK_ERROR, "Error trying to open invitation %s\n", cookie);
- unlink(usedname);
- return false;
- }
-
- // Check the timestamp
- struct stat st;
+ config_t config;
- if(fstat(fileno(f), &st)) {
- logger(mesh, MESHLINK_ERROR, "Could not stat invitation file %s\n", usedname);
- fclose(f);
- unlink(usedname);
- return false;
- }
-
- if(time(NULL) > st.st_mtime + mesh->invitation_timeout) {
- logger(mesh, MESHLINK_ERROR, "Peer %s tried to use an outdated invitation file %s\n", c->name, usedname);
- fclose(f);
- unlink(usedname);
+ if(!invitation_read(mesh, cookie, &config)) {
+ logger(mesh, MESHLINK_ERROR, "Error while trying to read invitation file\n");
return false;
}
// Read the new node's Name from the file
- char buf[1024];
- fgets(buf, sizeof(buf), f);
-
- if(*buf) {
- buf[strlen(buf) - 1] = 0;
- }
-
- len = strcspn(buf, " \t=");
- char *name = buf + len;
- name += strspn(name, " \t");
-
- if(*name == '=') {
- name++;
- name += strspn(name, " \t");
- }
-
- buf[len] = 0;
-
- if(!*buf || !*name || strcasecmp(buf, "Name") || !check_id(name)) {
- logger(mesh, MESHLINK_ERROR, "Invalid invitation file %s\n", cookie);
- fclose(f);
- return false;
- }
-
+ packmsg_input_t in = {config.buf, config.len};
+ packmsg_get_uint32(&in); // skip version
free(c->name);
- c->name = xstrdup(name);
+ c->name = packmsg_get_str_dup(&in);
// Send the node the contents of the invitation file
- rewind(f);
- size_t result;
-
- while((result = fread(buf, 1, sizeof(buf), f))) {
- sptps_send_record(&c->sptps, 0, buf, result);
- }
-
- sptps_send_record(&c->sptps, 1, buf, 0);
- fclose(f);
- unlink(usedname);
+ sptps_send_record(&c->sptps, 0, config.buf, config.len);
c->status.invitation_used = true;
/* Check if version matches */
- if(c->protocol_major != mesh->self->connection->protocol_major) {
+ if(c->protocol_major != PROT_MAJOR) {
logger(mesh, MESHLINK_ERROR, "Peer %s uses incompatible version %d.%d",
c->name, c->protocol_major, c->protocol_minor);
return false;
}
- if(!c->config_tree) {
- init_configuration(&c->config_tree);
+ /* Check if we know this node */
- if(!read_host_config(mesh, c->config_tree, c->name)) {
- logger(mesh, MESHLINK_ERROR, "Peer %s has unknown identity", c->name);
- return false;
- }
- }
+ node_t *n = lookup_node(mesh, c->name);
- bool blacklisted = false;
- get_config_bool(lookup_config(c->config_tree, "blacklisted"), &blacklisted);
+ if(!n) {
+ logger(mesh, MESHLINK_ERROR, "Peer %s has unknown identity", c->name);
+ return false;
+ }
- if(blacklisted) {
+ if(n->status.blacklisted) {
logger(mesh, MESHLINK_EPEER, "Peer %s is blacklisted", c->name);
return false;
}
- read_ecdsa_public_key(mesh, c);
+ node_read_public_key(mesh, n);
- if(!ecdsa_active(c->ecdsa)) {
+ if(!ecdsa_active(n->ecdsa)) {
logger(mesh, MESHLINK_ERROR, "No key known for peer %s", c->name);
- node_t *n = lookup_node(mesh, c->name);
-
- if(n && n->status.reachable && !n->status.waitingforkey) {
+ if(n->status.reachable && !n->status.waitingforkey) {
logger(mesh, MESHLINK_INFO, "Requesting key from peer %s", c->name);
send_req_key(mesh, n);
}
snprintf(label, sizeof(label), "%s %s %s", meshlink_tcp_label, c->name, mesh->self->name);
}
- return sptps_start(&c->sptps, c, c->outgoing, false, mesh->self->connection->ecdsa, c->ecdsa, label, sizeof(label) - 1, send_meta_sptps, receive_meta_sptps);
+ char buf1[1024], buf2[1024];
+ bin2hex((uint8_t *)mesh->private_key + 64, buf1, 32);
+ bin2hex((uint8_t *)n->ecdsa + 64, buf2, 32);
+ logger(mesh, MESHLINK_DEBUG, "Connection to %s mykey %s hiskey %s", c->name, buf1, buf2);
+ return sptps_start(&c->sptps, c, c->outgoing, false, mesh->private_key, n->ecdsa, label, sizeof(label) - 1, send_meta_sptps, receive_meta_sptps);
}
bool send_ack(meshlink_handle_t *mesh, connection_t *c) {
bool ack_h(meshlink_handle_t *mesh, connection_t *c, const char *request) {
char hisport[MAX_STRING_SIZE];
- char *hisaddress;
int devclass;
uint32_t options;
node_t *n;
}
n->devclass = devclass;
- node_write_devclass(mesh, n);
+ n->status.dirty = true;
n->last_successfull_connection = time(NULL);
c->edge = new_edge();
c->edge->from = mesh->self;
c->edge->to = n;
- sockaddr2str(&c->address, &hisaddress, NULL);
- c->edge->address = str2sockaddr(hisaddress, hisport);
- free(hisaddress);
+ sockaddrcpy_setport(&c->edge->address, &c->address, atoi(hisport));
c->edge->weight = dev_class_traits[devclass].edge_weight;
c->edge->connection = c;
c->edge->options = c->options;
#include "utils.h"
#include "xalloc.h"
-extern bool node_write_devclass(meshlink_handle_t *mesh, node_t *n);
-
bool send_add_edge(meshlink_handle_t *mesh, connection_t *c, const edge_t *e, int contradictions) {
bool x;
char *address, *port;
if(!from) {
from = new_node();
+ from->status.dirty = true;
from->status.blacklisted = mesh->default_blacklist;
from->name = xstrdup(from_name);
+ from->devclass = from_devclass;
node_add(mesh, from);
}
}
from->devclass = from_devclass;
- node_write_devclass(mesh, from);
if(!to) {
to = new_node();
+ to->status.dirty = true;
to->status.blacklisted = mesh->default_blacklist;
to->name = xstrdup(to_name);
+ to->devclass = to_devclass;
node_add(mesh, to);
}
- to->devclass = to_devclass;
- node_write_devclass(mesh, to);
-
/* Convert addresses */
address = str2sockaddr(to_address, to_port);
}
bool send_req_key(meshlink_handle_t *mesh, node_t *to) {
- if(!node_read_ecdsa_public_key(mesh, to)) {
+ if(!node_read_public_key(mesh, to)) {
logger(mesh, MESHLINK_DEBUG, "No ECDSA key known for %s", to->name);
send_request(mesh, to->nexthop->connection, "%d %s %s %d", REQ_KEY, mesh->self->name, to->name, REQ_PUBKEY);
return true;
to->status.waitingforkey = true;
to->last_req_key = mesh->loop.now.tv_sec;
to->incompression = mesh->self->incompression;
- return sptps_start(&to->sptps, to, true, true, mesh->self->connection->ecdsa, to->ecdsa, label, sizeof(label) - 1, send_initial_sptps_data, receive_sptps_record);
+ return sptps_start(&to->sptps, to, true, true, mesh->private_key, to->ecdsa, label, sizeof(label) - 1, send_initial_sptps_data, receive_sptps_record);
}
/* REQ_KEY is overloaded to allow arbitrary requests to be routed between two nodes. */
switch(reqno) {
case REQ_PUBKEY: {
- char *pubkey = ecdsa_get_base64_public_key(mesh->self->connection->ecdsa);
+ char *pubkey = ecdsa_get_base64_public_key(mesh->private_key);
send_request(mesh, from->nexthop->connection, "%d %s %s %d %s", REQ_KEY, mesh->self->name, from->name, ANS_PUBKEY, pubkey);
free(pubkey);
return true;
}
case ANS_PUBKEY: {
- if(node_read_ecdsa_public_key(mesh, from)) {
+ if(node_read_public_key(mesh, from)) {
logger(mesh, MESHLINK_WARNING, "Got ANS_PUBKEY from %s even though we already have his pubkey", from->name);
return true;
}
}
logger(mesh, MESHLINK_INFO, "Learned ECDSA public key from %s", from->name);
- append_config_file(mesh, from->name, "ECDSAPublicKey", pubkey);
+ from->status.dirty = true;
return true;
}
case REQ_KEY: {
- if(!node_read_ecdsa_public_key(mesh, from)) {
+ if(!node_read_public_key(mesh, from)) {
logger(mesh, MESHLINK_DEBUG, "No ECDSA key known for %s", from->name);
send_request(mesh, from->nexthop->connection, "%d %s %s %d", REQ_KEY, mesh->self->name, from->name, REQ_PUBKEY);
return true;
from->status.validkey = false;
from->status.waitingforkey = true;
from->last_req_key = mesh->loop.now.tv_sec;
- sptps_start(&from->sptps, from, false, true, mesh->self->connection->ecdsa, from->ecdsa, label, sizeof(label) - 1, send_sptps_data, receive_sptps_record);
+ sptps_start(&from->sptps, from, false, true, mesh->private_key, from->ecdsa, label, sizeof(label) - 1, send_sptps_data, receive_sptps_record);
sptps_receive_data(&from->sptps, buf, len);
return true;
}
/* Succesful connection, reset timeout if this is an outgoing connection. */
+ // TODO: completely remove this outgoing, let the autoconnect algorithm handle it
if(c->outgoing) {
c->outgoing->timeout = 0;
- c->outgoing->cfg = NULL;
+ c->outgoing->state = OUTGOING_START;
if(c->outgoing->ai) {
freeaddrinfo(c->outgoing->ai);
#define AF_UNKNOWN 255
#ifdef SA_LEN
-#define SALEN(s) SA_LEN(&s)
+#define SALEN(s) SA_LEN(&(s))
#else
-#define SALEN(s) (s.sa_family==AF_INET?sizeof(struct sockaddr_in):sizeof(struct sockaddr_in6))
+#define SALEN(s) ((s).sa_family==AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6))
#endif
struct sockaddr_unknown {
channels-fork.test \
channels-cornercases.test \
duplicate.test \
+ encrypted.test \
import-export.test \
invite-join.test \
sign-verify.test \
channels-cornercases \
duplicate \
echo-fork \
+ encrypted \
import-export \
invite-join \
sign-verify \
echo_fork_SOURCES = echo-fork.c
echo_fork_LDADD = ../src/libmeshlink.la
+encrypted_SOURCES = encrypted.c
+encrypted_LDADD = ../src/libmeshlink.la
+
import_export_SOURCES = import-export.c
import_export_LDADD = ../src/libmeshlink.la
#include "meshlink.h"
+void log_cb(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) {
+ static struct timeval tv0;
+ struct timeval tv;
+
+ if(tv0.tv_sec == 0) {
+ gettimeofday(&tv0, NULL);
+ }
+
+ gettimeofday(&tv, NULL);
+ fprintf(stderr, "%u.%.03u ", (unsigned int)(tv.tv_sec - tv0.tv_sec), (unsigned int)tv.tv_usec / 1000);
+
+ if(mesh) {
+ fprintf(stderr, "(%s) ", mesh->name);
+ }
+
+ fprintf(stderr, "[%d] %s\n", level, text);
+}
+
int main() {
+ meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
+
// Open a new meshlink instance.
meshlink_handle_t *mesh = meshlink_open("basic_conf", "foo", "basic", DEV_CLASS_BACKBONE);
return 1;
}
+ meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb);
+
// Check that our own node exists.
meshlink_node_t *self = meshlink_get_self(mesh);
return 1;
}
+ meshlink_set_log_cb(mesh, MESHLINK_DEBUG, log_cb);
+
if(meshlink_get_node(mesh, "bar")) {
fprintf(stderr, "Foo knows about bar, it shouldn't\n");
return 1;
/* Create meshlink instance */
mesh_handle = meshlink_open("testconf", node_name, "node_sim", atoi(dev_class));
fprintf(stderr, "meshlink_open status: %s\n", meshlink_strerror(meshlink_errno));
+ meshlink_enable_discovery(mesh_handle, false);
PRINT_TEST_CASE_MSG("meshlink_open status: %s\n", meshlink_strerror(meshlink_errno));
assert(mesh_handle);
}
char *execute_invite(char *invitee) {
- char *invite_url = meshlink_invite(mesh_handle, invitee);
+ char *invite_url = meshlink_invite_ex(mesh_handle, invitee, MESHLINK_INVITE_LOCAL | MESHLINK_INVITE_NUMERIC);
PRINT_TEST_CASE_MSG("meshlink_invite status: %s\n", meshlink_strerror(meshlink_errno));
assert(invite_url);
void execute_join(char *invite_url) {
bool join_status;
- /* The inviting node may take a moment to open its listening port
- This sleep() prevents meshlink_join() from failing when the listening port is not open */
- /* TO DO: Replace this with code that actually checks for the port being open, if possible */
- PRINT_TEST_CASE_MSG("Sleeping 1 sec to allow inviting node to start listening...\n");
- sleep(1);
-
- PRINT_TEST_CASE_MSG("About to join with mesh_handle = %p, invite_url = %s\n", mesh_handle, invite_url);
join_status = meshlink_join(mesh_handle, invite_url);
- PRINT_TEST_CASE_MSG("meshlink_join status: %s\n", meshlink_strerror(meshlink_errno));
assert(join_status);
}
--- /dev/null
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/time.h>
+
+#include "meshlink.h"
+
+void log_cb(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) {
+ static struct timeval tv0;
+ struct timeval tv;
+
+ if(tv0.tv_sec == 0) {
+ gettimeofday(&tv0, NULL);
+ }
+
+ gettimeofday(&tv, NULL);
+ fprintf(stderr, "%u.%.03u ", (unsigned int)(tv.tv_sec - tv0.tv_sec), (unsigned int)tv.tv_usec / 1000);
+
+ if(mesh) {
+ fprintf(stderr, "(%s) ", mesh->name);
+ }
+
+ fprintf(stderr, "[%d] %s\n", level, text);
+}
+
+int main() {
+ meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
+
+ // Open a new meshlink instance.
+
+ meshlink_handle_t *mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "right", 5);
+
+ if(!mesh) {
+ fprintf(stderr, "Could not initialize configuration for foo\n");
+ return 1;
+ }
+
+ // Close the mesh and open it again, now with a different key.
+
+ meshlink_close(mesh);
+
+ mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "wrong", 5);
+
+ if(mesh) {
+ fprintf(stderr, "Could open mesh with the wrong key\n");
+ return 1;
+ }
+
+ // Open it again, now with the right key.
+
+ mesh = meshlink_open_encrypted("encrypted_conf", "foo", "encrypted", DEV_CLASS_BACKBONE, "right", 5);
+
+ if(!mesh) {
+ fprintf(stderr, "Could not open mesh with the right key\n");
+ return 1;
+ }
+
+ // That's it.
+
+ meshlink_close(mesh);
+
+ // Destroy the mesh.
+
+ if(!meshlink_destroy("encrypted_conf")) {
+ fprintf(stderr, "Could not destroy configuration\n");
+ return 1;
+ }
+
+ if(!access("encrypted_conf", F_OK) || errno != ENOENT) {
+ fprintf(stderr, "Configuration not fully destroyed\n");
+ return 1;
+ }
+
+ return 0;
+}
--- /dev/null
+#!/bin/sh
+
+rm -Rf encrypted_conf
+./encrypted
volatile bool bar_reachable = false;
+void log_cb(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) {
+ static struct timeval tv0;
+ struct timeval tv;
+
+ if(tv0.tv_sec == 0) {
+ gettimeofday(&tv0, NULL);
+ }
+
+ gettimeofday(&tv, NULL);
+ fprintf(stderr, "%u.%.03u ", (unsigned int)(tv.tv_sec - tv0.tv_sec), (unsigned int)tv.tv_usec / 1000);
+
+ if(mesh) {
+ fprintf(stderr, "(%s) ", mesh->name);
+ }
+
+ fprintf(stderr, "[%d] %s\n", level, text);
+}
+
void status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) {
(void)mesh;
}
int main() {
+ meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
+
// Open two new meshlink instance.
meshlink_handle_t *mesh1 = meshlink_open("import_export_conf.1", "foo", "import-export", DEV_CLASS_BACKBONE);
if(!mesh1) {
- fprintf(stderr, "Could not initialize configuration for foo\n");
+ fprintf(stderr, "Could not initialize configuration for foo: %s\n", meshlink_strerror(meshlink_errno));
return 1;
}
return 1;
}
+ meshlink_set_log_cb(mesh1, MESHLINK_DEBUG, log_cb);
+ meshlink_set_log_cb(mesh2, MESHLINK_DEBUG, log_cb);
+
// Disable local discovery
meshlink_enable_discovery(mesh1, false);
return 1;
}
+ fprintf(stderr, "Foo export data:\n%s\n", data);
+
if(!meshlink_import(mesh2, data)) {
fprintf(stderr, "Bar could not import foo's configuration\n");
return 1;
volatile bool baz_reachable = false;
+void log_cb(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) {
+ static struct timeval tv0;
+ struct timeval tv;
+
+ if(tv0.tv_sec == 0) {
+ gettimeofday(&tv0, NULL);
+ }
+
+ gettimeofday(&tv, NULL);
+ fprintf(stderr, "%u.%.03u ", (unsigned int)(tv.tv_sec - tv0.tv_sec), (unsigned int)tv.tv_usec / 1000);
+
+ if(mesh) {
+ fprintf(stderr, "(%s) ", mesh->name);
+ }
+
+ fprintf(stderr, "[%d] %s\n", level, text);
+}
+
void status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) {
(void)mesh;
}
int main() {
- // Open two new meshlink instance.
+ meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
+
+ // Open thee new meshlink instance.
meshlink_handle_t *mesh1 = meshlink_open("invite_join_conf.1", "foo", "invite-join", DEV_CLASS_BACKBONE);
return 1;
}
+ meshlink_set_log_cb(mesh1, MESHLINK_DEBUG, log_cb);
+
meshlink_handle_t *mesh2 = meshlink_open("invite_join_conf.2", "bar", "invite-join", DEV_CLASS_BACKBONE);
if(!mesh2) {
return 1;
}
+ meshlink_set_log_cb(mesh2, MESHLINK_DEBUG, log_cb);
+
meshlink_handle_t *mesh3 = meshlink_open("invite_join_conf.3", "quux", "invite-join", DEV_CLASS_BACKBONE);
if(!mesh3) {
return 1;
}
+ meshlink_set_log_cb(mesh3, MESHLINK_DEBUG, log_cb);
+
// Disable local discovery.
meshlink_enable_discovery(mesh1, false);
return 1;
}
+ fprintf(stderr, "Invitation URL for baz: %s\n", baz_url);
+ fprintf(stderr, "Invitation URL for quux: %s\n", quux_url);
+
// Have the second instance join the first.
if(!meshlink_join(mesh2, baz_url)) {
// Clean up.
- meshlink_stop(mesh2);
- meshlink_stop(mesh1);
+ meshlink_close(mesh3);
meshlink_close(mesh2);
meshlink_close(mesh1);