+ return true;
+}
+
+static bool invitation_send(void *handle, uint8_t type, const void *data, size_t len) {
+ (void)type;
+ join_state_t *state = handle;
+ const char *ptr = data;
+
+ while(len) {
+ int result = send(state->sock, ptr, len, 0);
+
+ if(result == -1 && errno == EINTR) {
+ continue;
+ } else if(result <= 0) {
+ return false;
+ }
+
+ ptr += result;
+ len -= result;
+ }
+
+ return true;
+}
+
+static bool invitation_receive(void *handle, uint8_t type, const void *msg, uint16_t len) {
+ join_state_t *state = handle;
+ meshlink_handle_t *mesh = state->mesh;
+
+ if(mesh->inviter_commits_first) {
+ switch(type) {
+ case SPTPS_HANDSHAKE:
+ return sptps_send_record(&state->sptps, 2, state->cookie, 18 + 32);
+
+ case 1:
+ break;
+
+ case 0:
+ if(!finalize_join(state, msg, len)) {
+ return false;
+ }
+
+ logger(mesh, MESHLINK_DEBUG, "Invitation successfully accepted.\n");
+ shutdown(state->sock, SHUT_RDWR);
+ state->success = true;
+ break;
+
+ default:
+ return false;
+ }
+ } else {
+ switch(type) {
+ case SPTPS_HANDSHAKE:
+ return sptps_send_record(&state->sptps, 0, state->cookie, 18);
+
+ case 0:
+ return finalize_join(state, msg, len);
+
+ case 1:
+ logger(mesh, MESHLINK_DEBUG, "Invitation successfully accepted.\n");
+ shutdown(state->sock, SHUT_RDWR);
+ state->success = true;
+ break;
+
+ default:
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool recvline(join_state_t *state) {
+ char *newline = NULL;
+
+ while(!(newline = memchr(state->buffer, '\n', state->blen))) {
+ int result = recv(state->sock, state->buffer + state->blen, sizeof(state)->buffer - state->blen, 0);
+
+ if(result == -1 && errno == EINTR) {
+ continue;
+ } else if(result <= 0) {
+ return false;
+ }
+
+ state->blen += result;
+ }
+
+ if((size_t)(newline - state->buffer) >= sizeof(state->line)) {
+ return false;
+ }
+
+ size_t len = newline - state->buffer;
+
+ memcpy(state->line, state->buffer, len);
+ state->line[len] = 0;
+ memmove(state->buffer, newline + 1, state->blen - len - 1);
+ state->blen -= len + 1;
+
+ return true;
+}
+
+static bool sendline(int fd, const char *format, ...) {
+ char buffer[4096];
+ char *p = buffer;
+ int blen = 0;
+ va_list ap;
+
+ va_start(ap, format);
+ blen = vsnprintf(buffer, sizeof(buffer), format, ap);
+ va_end(ap);
+
+ if(blen < 1 || (size_t)blen >= sizeof(buffer)) {
+ return false;
+ }
+
+ buffer[blen] = '\n';
+ blen++;
+
+ while(blen) {
+ int result = send(fd, p, blen, MSG_NOSIGNAL);
+
+ if(result == -1 && errno == EINTR) {
+ continue;
+ } else if(result <= 0) {
+ return false;
+ }
+
+ p += result;
+ blen -= result;
+ }
+
+ return true;
+}
+
+static const char *errstr[] = {
+ [MESHLINK_OK] = "No error",
+ [MESHLINK_EINVAL] = "Invalid argument",
+ [MESHLINK_ENOMEM] = "Out of memory",
+ [MESHLINK_ENOENT] = "No such node",
+ [MESHLINK_EEXIST] = "Node already exists",
+ [MESHLINK_EINTERNAL] = "Internal error",
+ [MESHLINK_ERESOLV] = "Could not resolve hostname",
+ [MESHLINK_ESTORAGE] = "Storage error",
+ [MESHLINK_ENETWORK] = "Network error",
+ [MESHLINK_EPEER] = "Error communicating with peer",
+ [MESHLINK_ENOTSUP] = "Operation not supported",
+ [MESHLINK_EBUSY] = "MeshLink instance already in use",
+ [MESHLINK_EBLACKLISTED] = "Node is blacklisted",
+};
+
+const char *meshlink_strerror(meshlink_errno_t err) {
+ if((int)err < 0 || err >= sizeof(errstr) / sizeof(*errstr)) {
+ return "Invalid error code";
+ }
+
+ return errstr[err];
+}
+
+static bool ecdsa_keygen(meshlink_handle_t *mesh) {
+ logger(mesh, MESHLINK_DEBUG, "Generating ECDSA keypairs:\n");
+
+ mesh->private_key = ecdsa_generate();
+ mesh->invitation_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;
+ }
+
+ logger(mesh, MESHLINK_DEBUG, "Done.\n");
+
+ return true;
+}
+
+static bool timespec_lt(const struct timespec *a, const struct timespec *b) {
+ if(a->tv_sec == b->tv_sec) {
+ return a->tv_nsec < b->tv_nsec;
+ } else {
+ return a->tv_sec < b->tv_sec;
+ }
+}
+
+static struct timespec idle(event_loop_t *loop, void *data) {
+ (void)loop;
+ meshlink_handle_t *mesh = data;
+ struct timespec t, tmin = {3600, 0};
+
+ for splay_each(node_t, n, mesh->nodes) {
+ if(!n->utcp) {
+ continue;
+ }
+
+ t = utcp_timeout(n->utcp);
+
+ if(timespec_lt(&t, &tmin)) {
+ tmin = t;
+ }
+ }
+
+ return tmin;
+}
+
+// Get our local address(es) by simulating connecting to an Internet host.
+static void add_local_addresses(meshlink_handle_t *mesh) {
+ sockaddr_t sa;
+ sa.storage.ss_family = AF_UNKNOWN;
+ socklen_t salen = sizeof(sa);
+
+ // IPv4 example.org
+
+ if(getlocaladdr("93.184.216.34", &sa, &salen, mesh->netns)) {
+ sa.in.sin_port = ntohs(atoi(mesh->myport));
+ node_add_recent_address(mesh, mesh->self, &sa);
+ }
+
+ // IPv6 example.org
+
+ salen = sizeof(sa);
+
+ if(getlocaladdr("2606:2800:220:1:248:1893:25c8:1946", &sa, &salen, mesh->netns)) {
+ sa.in6.sin6_port = ntohs(atoi(mesh->myport));
+ node_add_recent_address(mesh, mesh->self, &sa);
+ }
+}
+
+static bool meshlink_setup(meshlink_handle_t *mesh) {
+ if(!config_destroy(mesh->confbase, "new")) {
+ logger(mesh, MESHLINK_ERROR, "Could not delete configuration in %s/new: %s\n", mesh->confbase, strerror(errno));
+ meshlink_errno = MESHLINK_ESTORAGE;
+ return false;
+ }
+
+ if(!config_destroy(mesh->confbase, "old")) {
+ logger(mesh, MESHLINK_ERROR, "Could not delete configuration in %s/old: %s\n", mesh->confbase, strerror(errno));
+ meshlink_errno = MESHLINK_ESTORAGE;
+ return false;
+ }
+
+ if(!config_init(mesh, "current")) {
+ logger(mesh, MESHLINK_ERROR, "Could not set up configuration in %s/current: %s\n", mesh->confbase, strerror(errno));
+ meshlink_errno = MESHLINK_ESTORAGE;
+ return false;
+ }
+
+ if(!ecdsa_keygen(mesh)) {
+ meshlink_errno = MESHLINK_EINTERNAL;
+ return false;
+ }
+
+ if(check_port(mesh) == 0) {
+ meshlink_errno = MESHLINK_ENETWORK;
+ return false;
+ }
+
+ /* Create a node for ourself */
+
+ 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));
+ mesh->self->session_id = mesh->session_id;
+
+ if(!write_main_config_files(mesh)) {
+ logger(mesh, MESHLINK_ERROR, "Could not write main config files into %s/current: %s\n", mesh->confbase, strerror(errno));
+ meshlink_errno = MESHLINK_ESTORAGE;
+ return false;
+ }
+
+ /* Ensure the configuration directory metadata is on disk */
+ if(!config_sync(mesh, "current")) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool meshlink_read_config(meshlink_handle_t *mesh) {
+ config_t config;
+
+ if(!main_config_read(mesh, "current", &config, mesh->config_key)) {
+ logger(NULL, MESHLINK_ERROR, "Could not read main configuration file!");
+ return false;
+ }
+
+ 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(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;
+ }
+
+ 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;
+ mesh->self->session_id = mesh->session_id;
+
+ if(!node_read_public_key(mesh, mesh->self)) {
+ logger(NULL, MESHLINK_ERROR, "Could not read our host configuration file!");
+ meshlink_errno = MESHLINK_ESTORAGE;
+ free_node(mesh->self);
+ mesh->self = NULL;
+ return false;
+ }
+
+ return true;
+}
+
+#ifdef HAVE_SETNS
+static void *setup_network_in_netns_thread(void *arg) {
+ meshlink_handle_t *mesh = arg;
+
+ if(setns(mesh->netns, CLONE_NEWNET) != 0) {
+ return NULL;
+ }
+
+ bool success = setup_network(mesh);
+ return success ? arg : NULL;
+}
+#endif // HAVE_SETNS
+
+meshlink_open_params_t *meshlink_open_params_init(const char *confbase, const char *name, const char *appname, dev_class_t devclass) {
+ if(!confbase || !*confbase) {
+ logger(NULL, MESHLINK_ERROR, "No confbase given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ if(!appname || !*appname) {
+ logger(NULL, MESHLINK_ERROR, "No appname given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ if(strchr(appname, ' ')) {
+ logger(NULL, MESHLINK_ERROR, "Invalid appname given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ if(name && !check_id(name)) {
+ logger(NULL, MESHLINK_ERROR, "Invalid name given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ if(devclass < 0 || devclass >= DEV_CLASS_COUNT) {
+ logger(NULL, MESHLINK_ERROR, "Invalid devclass given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ meshlink_open_params_t *params = xzalloc(sizeof * params);
+
+ params->confbase = xstrdup(confbase);
+ params->name = name ? xstrdup(name) : NULL;
+ params->appname = xstrdup(appname);
+ params->devclass = devclass;
+ params->netns = -1;
+
+ return params;
+}
+
+bool meshlink_open_params_set_netns(meshlink_open_params_t *params, int netns) {
+ if(!params) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ params->netns = netns;
+
+ return true;
+}
+
+bool meshlink_open_params_set_storage_key(meshlink_open_params_t *params, const void *key, size_t keylen) {
+ if(!params) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ if((!key && keylen) || (key && !keylen)) {
+ logger(NULL, MESHLINK_ERROR, "Invalid key length!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ params->key = key;
+ params->keylen = keylen;
+
+ return true;
+}
+
+bool meshlink_encrypted_key_rotate(meshlink_handle_t *mesh, const void *new_key, size_t new_keylen) {
+ if(!mesh || !new_key || !new_keylen) {
+ logger(mesh, MESHLINK_ERROR, "Invalid arguments given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ pthread_mutex_lock(&mesh->mutex);
+
+ // Create hash for the new key
+ void *new_config_key;
+ new_config_key = xmalloc(CHACHA_POLY1305_KEYLEN);
+
+ if(!prf(new_key, new_keylen, "MeshLink configuration key", 26, new_config_key, CHACHA_POLY1305_KEYLEN)) {
+ logger(mesh, MESHLINK_ERROR, "Error creating new configuration key!\n");
+ meshlink_errno = MESHLINK_EINTERNAL;
+ pthread_mutex_unlock(&mesh->mutex);
+ return false;
+ }
+
+ // Copy contents of the "current" confbase sub-directory to "new" confbase sub-directory with the new key
+
+ if(!config_copy(mesh, "current", mesh->config_key, "new", new_config_key)) {
+ logger(mesh, MESHLINK_ERROR, "Could not set up configuration in %s/old: %s\n", mesh->confbase, strerror(errno));
+ meshlink_errno = MESHLINK_ESTORAGE;
+ pthread_mutex_unlock(&mesh->mutex);
+ return false;
+ }
+
+ devtool_keyrotate_probe(1);
+
+ // Rename confbase/current/ to confbase/old
+
+ if(!config_rename(mesh, "current", "old")) {
+ logger(mesh, MESHLINK_ERROR, "Cannot rename %s/current to %s/old\n", mesh->confbase, mesh->confbase);
+ meshlink_errno = MESHLINK_ESTORAGE;
+ pthread_mutex_unlock(&mesh->mutex);
+ return false;
+ }
+
+ devtool_keyrotate_probe(2);
+
+ // Rename confbase/new/ to confbase/current
+
+ if(!config_rename(mesh, "new", "current")) {
+ logger(mesh, MESHLINK_ERROR, "Cannot rename %s/new to %s/current\n", mesh->confbase, mesh->confbase);
+ meshlink_errno = MESHLINK_ESTORAGE;
+ pthread_mutex_unlock(&mesh->mutex);
+ return false;
+ }
+
+ devtool_keyrotate_probe(3);
+
+ // Cleanup the "old" confbase sub-directory
+
+ if(!config_destroy(mesh->confbase, "old")) {
+ pthread_mutex_unlock(&mesh->mutex);
+ return false;
+ }
+
+ // Change the mesh handle key with new key
+
+ free(mesh->config_key);
+ mesh->config_key = new_config_key;
+
+ pthread_mutex_unlock(&mesh->mutex);
+
+ return true;
+}
+
+void meshlink_open_params_free(meshlink_open_params_t *params) {
+ if(!params) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return;
+ }
+
+ free(params->confbase);
+ free(params->name);
+ free(params->appname);
+
+ free(params);
+}
+
+/// Device class traits
+static const dev_class_traits_t default_class_traits[DEV_CLASS_COUNT] = {
+ { .pingtimeout = 5, .pinginterval = 60, .min_connects = 3, .max_connects = 10000, .edge_weight = 1 }, // DEV_CLASS_BACKBONE
+ { .pingtimeout = 5, .pinginterval = 60, .min_connects = 3, .max_connects = 100, .edge_weight = 3 }, // DEV_CLASS_STATIONARY
+ { .pingtimeout = 5, .pinginterval = 60, .min_connects = 3, .max_connects = 3, .edge_weight = 6 }, // DEV_CLASS_PORTABLE
+ { .pingtimeout = 5, .pinginterval = 60, .min_connects = 1, .max_connects = 1, .edge_weight = 9 }, // DEV_CLASS_UNKNOWN
+};
+
+meshlink_handle_t *meshlink_open(const char *confbase, const char *name, const char *appname, dev_class_t devclass) {
+ if(!confbase || !*confbase) {
+ logger(NULL, MESHLINK_ERROR, "No confbase given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ /* Create a temporary struct on the stack, to avoid allocating and freeing one. */
+ meshlink_open_params_t params;
+ memset(¶ms, 0, sizeof(params));
+
+ params.confbase = (char *)confbase;
+ params.name = (char *)name;
+ params.appname = (char *)appname;
+ params.devclass = devclass;
+ params.netns = -1;
+
+ return meshlink_open_ex(¶ms);
+}
+
+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) {
+ if(!confbase || !*confbase) {
+ logger(NULL, MESHLINK_ERROR, "No confbase given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ /* Create a temporary struct on the stack, to avoid allocating and freeing one. */
+ meshlink_open_params_t params;
+ memset(¶ms, 0, sizeof(params));
+
+ params.confbase = (char *)confbase;
+ params.name = (char *)name;
+ params.appname = (char *)appname;
+ params.devclass = devclass;
+ params.netns = -1;
+
+ if(!meshlink_open_params_set_storage_key(¶ms, key, keylen)) {
+ return false;
+ }
+
+ return meshlink_open_ex(¶ms);
+}
+
+meshlink_handle_t *meshlink_open_ephemeral(const char *name, const char *appname, dev_class_t devclass) {
+ if(!name) {
+ logger(NULL, MESHLINK_ERROR, "No name given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ if(!check_id(name)) {
+ logger(NULL, MESHLINK_ERROR, "Invalid name given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ if(!appname || !*appname) {
+ logger(NULL, MESHLINK_ERROR, "No appname given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ if(strchr(appname, ' ')) {
+ logger(NULL, MESHLINK_ERROR, "Invalid appname given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ if(devclass < 0 || devclass >= DEV_CLASS_COUNT) {
+ logger(NULL, MESHLINK_ERROR, "Invalid devclass given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ /* Create a temporary struct on the stack, to avoid allocating and freeing one. */
+ meshlink_open_params_t params;
+ memset(¶ms, 0, sizeof(params));
+
+ params.name = (char *)name;
+ params.appname = (char *)appname;
+ params.devclass = devclass;
+ params.netns = -1;
+
+ return meshlink_open_ex(¶ms);
+}
+
+meshlink_handle_t *meshlink_open_ex(const meshlink_open_params_t *params) {
+ logger(NULL, MESHLINK_DEBUG, "meshlink_open called\n");
+
+ // Validate arguments provided by the application
+ if(!params->appname || !*params->appname) {
+ logger(NULL, MESHLINK_ERROR, "No appname given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ if(strchr(params->appname, ' ')) {
+ logger(NULL, MESHLINK_ERROR, "Invalid appname given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ if(params->name && !check_id(params->name)) {
+ logger(NULL, MESHLINK_ERROR, "Invalid name given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ if(params->devclass < 0 || params->devclass >= DEV_CLASS_COUNT) {
+ logger(NULL, MESHLINK_ERROR, "Invalid devclass given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ if((params->key && !params->keylen) || (!params->key && params->keylen)) {
+ logger(NULL, MESHLINK_ERROR, "Invalid key length!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ meshlink_handle_t *mesh = xzalloc(sizeof(meshlink_handle_t));
+
+ if(params->confbase) {
+ mesh->confbase = xstrdup(params->confbase);
+ }
+
+ mesh->appname = xstrdup(params->appname);
+ mesh->devclass = params->devclass;
+ mesh->discovery = true;
+ mesh->invitation_timeout = 604800; // 1 week
+ mesh->netns = params->netns;
+ mesh->submeshes = NULL;
+ mesh->log_cb = global_log_cb;
+ mesh->log_level = global_log_level;
+ mesh->packet = xmalloc(sizeof(vpn_packet_t));
+
+ randomize(&mesh->prng_state, sizeof(mesh->prng_state));
+
+ do {
+ randomize(&mesh->session_id, sizeof(mesh->session_id));
+ } while(mesh->session_id == 0);
+
+ memcpy(mesh->dev_class_traits, default_class_traits, sizeof(default_class_traits));
+
+ mesh->name = params->name ? xstrdup(params->name) : NULL;
+
+ // Hash the key
+ if(params->key) {
+ mesh->config_key = xmalloc(CHACHA_POLY1305_KEYLEN);
+
+ if(!prf(params->key, params->keylen, "MeshLink configuration key", 26, mesh->config_key, CHACHA_POLY1305_KEYLEN)) {
+ logger(NULL, MESHLINK_ERROR, "Error creating configuration key!\n");
+ meshlink_close(mesh);
+ meshlink_errno = MESHLINK_EINTERNAL;
+ return NULL;
+ }
+ }
+
+ // initialize mutex
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+ pthread_mutex_init(&mesh->mutex, &attr);
+
+ mesh->threadstarted = false;
+ event_loop_init(&mesh->loop);
+ mesh->loop.data = mesh;
+
+ meshlink_queue_init(&mesh->outpacketqueue);
+
+ // Atomically lock the configuration directory.
+ if(!main_config_lock(mesh)) {
+ meshlink_close(mesh);
+ return NULL;
+ }
+
+ // If no configuration exists yet, create it.
+
+ if(!meshlink_confbase_exists(mesh)) {
+ if(!mesh->name) {
+ logger(NULL, MESHLINK_ERROR, "No configuration files found!\n");
+ meshlink_close(mesh);
+ meshlink_errno = MESHLINK_ESTORAGE;
+ return NULL;
+ }
+
+ 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);
+ return NULL;
+ }
+ }
+
+#ifdef HAVE_MINGW
+ struct WSAData wsa_state;
+ WSAStartup(MAKEWORD(2, 2), &wsa_state);
+#endif
+
+ // Setup up everything
+ // TODO: we should not open listening sockets yet
+
+ bool success = false;
+
+ if(mesh->netns != -1) {
+#ifdef HAVE_SETNS
+ pthread_t thr;
+
+ if(pthread_create(&thr, NULL, setup_network_in_netns_thread, mesh) == 0) {
+ void *retval = NULL;
+ success = pthread_join(thr, &retval) == 0 && retval;
+ }
+
+#else
+ meshlink_errno = MESHLINK_EINTERNAL;
+ return NULL;
+
+#endif // HAVE_SETNS
+ } else {
+ success = setup_network(mesh);
+ }
+
+ if(!success) {
+ meshlink_close(mesh);
+ meshlink_errno = MESHLINK_ENETWORK;
+ return NULL;
+ }
+
+ add_local_addresses(mesh);
+
+ if(!node_write_config(mesh, mesh->self)) {
+ logger(NULL, MESHLINK_ERROR, "Cannot update configuration\n");
+ return NULL;
+ }
+
+ idle_set(&mesh->loop, idle, mesh);
+
+ logger(NULL, MESHLINK_DEBUG, "meshlink_open returning\n");
+ return mesh;
+}
+
+meshlink_submesh_t *meshlink_submesh_open(meshlink_handle_t *mesh, const char *submesh) {
+ meshlink_submesh_t *s = NULL;
+
+ if(!mesh) {
+ logger(NULL, MESHLINK_ERROR, "No mesh handle given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ if(!submesh || !*submesh) {
+ logger(NULL, MESHLINK_ERROR, "No submesh name given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ //lock mesh->nodes
+ pthread_mutex_lock(&mesh->mutex);
+
+ s = (meshlink_submesh_t *)create_submesh(mesh, submesh);
+
+ pthread_mutex_unlock(&mesh->mutex);
+
+ return s;
+}
+
+static void *meshlink_main_loop(void *arg) {
+ meshlink_handle_t *mesh = arg;
+
+ if(mesh->netns != -1) {
+#ifdef HAVE_SETNS
+
+ if(setns(mesh->netns, CLONE_NEWNET) != 0) {
+ pthread_cond_signal(&mesh->cond);
+ return NULL;
+ }
+
+#else
+ pthread_cond_signal(&mesh->cond);
+ return NULL;
+#endif // HAVE_SETNS
+ }
+
+#if HAVE_CATTA
+
+ if(mesh->discovery) {
+ discovery_start(mesh);
+ }
+
+#endif
+
+ pthread_mutex_lock(&mesh->mutex);
+
+ logger(mesh, MESHLINK_DEBUG, "Starting main_loop...\n");
+ pthread_cond_broadcast(&mesh->cond);
+ main_loop(mesh);
+ logger(mesh, MESHLINK_DEBUG, "main_loop returned.\n");
+
+ pthread_mutex_unlock(&mesh->mutex);
+
+#if HAVE_CATTA
+
+ // Stop discovery
+ if(mesh->discovery) {
+ discovery_stop(mesh);
+ }
+
+#endif
+
+ return NULL;
+}
+
+bool meshlink_start(meshlink_handle_t *mesh) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ logger(mesh, MESHLINK_DEBUG, "meshlink_start called\n");
+
+ pthread_mutex_lock(&mesh->mutex);
+
+ assert(mesh->self);
+ assert(mesh->private_key);
+ assert(mesh->self->ecdsa);
+ assert(!memcmp((uint8_t *)mesh->self->ecdsa + 64, (uint8_t *)mesh->private_key + 64, 32));
+
+ if(mesh->threadstarted) {
+ logger(mesh, MESHLINK_DEBUG, "thread was already running\n");
+ pthread_mutex_unlock(&mesh->mutex);
+ return true;
+ }
+
+ if(mesh->listen_socket[0].tcp.fd < 0) {
+ logger(mesh, MESHLINK_ERROR, "Listening socket not open\n");
+ meshlink_errno = MESHLINK_ENETWORK;
+ return false;
+ }
+
+ // TODO: open listening sockets first
+
+ //Check that a valid name is set
+ if(!mesh->name) {
+ logger(mesh, MESHLINK_DEBUG, "No name given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ pthread_mutex_unlock(&mesh->mutex);
+ return false;
+ }
+
+ init_outgoings(mesh);
+ init_adns(mesh);
+
+ // Start the main thread
+
+ event_loop_start(&mesh->loop);
+
+ if(pthread_create(&mesh->thread, NULL, meshlink_main_loop, mesh) != 0) {
+ logger(mesh, MESHLINK_DEBUG, "Could not start thread: %s\n", strerror(errno));
+ memset(&mesh->thread, 0, sizeof(mesh)->thread);
+ meshlink_errno = MESHLINK_EINTERNAL;
+ event_loop_stop(&mesh->loop);
+ pthread_mutex_unlock(&mesh->mutex);
+ return false;
+ }
+
+ pthread_cond_wait(&mesh->cond, &mesh->mutex);
+ mesh->threadstarted = true;
+
+ // Ensure we are considered reachable
+ graph(mesh);
+
+ pthread_mutex_unlock(&mesh->mutex);
+ return true;
+}
+
+void meshlink_stop(meshlink_handle_t *mesh) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return;
+ }
+
+ pthread_mutex_lock(&mesh->mutex);
+ logger(mesh, MESHLINK_DEBUG, "meshlink_stop called\n");
+
+ // Shut down the main thread
+ event_loop_stop(&mesh->loop);
+
+ // Send ourselves a UDP packet to kick the event loop
+ for(int i = 0; i < mesh->listen_sockets; i++) {
+ sockaddr_t sa;
+ socklen_t salen = sizeof(sa);
+
+ if(getsockname(mesh->listen_socket[i].udp.fd, &sa.sa, &salen) == -1) {
+ logger(mesh, MESHLINK_ERROR, "System call `%s' failed: %s", "getsockname", sockstrerror(sockerrno));
+ continue;
+ }
+
+ if(sendto(mesh->listen_socket[i].udp.fd, "", 1, MSG_NOSIGNAL, &sa.sa, salen) == -1) {
+ logger(mesh, MESHLINK_ERROR, "Could not send a UDP packet to ourself: %s", sockstrerror(sockerrno));
+ }
+ }
+
+ if(mesh->threadstarted) {
+ // Wait for the main thread to finish
+ pthread_mutex_unlock(&mesh->mutex);
+ pthread_join(mesh->thread, NULL);
+ pthread_mutex_lock(&mesh->mutex);
+
+ mesh->threadstarted = false;
+ }
+
+ // Close all metaconnections
+ if(mesh->connections) {
+ for(list_node_t *node = mesh->connections->head, *next; node; node = next) {
+ next = node->next;
+ connection_t *c = node->data;
+ c->outgoing = NULL;
+ terminate_connection(mesh, c, false);
+ }
+ }
+
+ exit_adns(mesh);
+ exit_outgoings(mesh);
+
+ // Ensure we are considered unreachable
+ if(mesh->nodes) {
+ graph(mesh);
+ }
+
+ // Try to write out any changed node config files, ignore errors at this point.
+ if(mesh->nodes) {
+ for splay_each(node_t, n, mesh->nodes) {
+ if(n->status.dirty) {
+ n->status.dirty = !node_write_config(mesh, n);
+ }
+ }
+ }
+
+ pthread_mutex_unlock(&mesh->mutex);
+}
+
+void meshlink_close(meshlink_handle_t *mesh) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return;
+ }
+
+ // stop can be called even if mesh has not been started
+ meshlink_stop(mesh);
+
+ // lock is not released after this
+ pthread_mutex_lock(&mesh->mutex);
+
+ // Close and free all resources used.
+
+ close_network_connections(mesh);
+
+ logger(mesh, MESHLINK_INFO, "Terminating");
+
+ event_loop_exit(&mesh->loop);
+
+#ifdef HAVE_MINGW
+
+ if(mesh->confbase) {
+ WSACleanup();
+ }
+
+#endif
+
+ ecdsa_free(mesh->invitation_key);
+
+ if(mesh->netns != -1) {
+ close(mesh->netns);
+ }
+
+ for(vpn_packet_t *packet; (packet = meshlink_queue_pop(&mesh->outpacketqueue));) {
+ free(packet);
+ }
+
+ meshlink_queue_exit(&mesh->outpacketqueue);
+
+ free(mesh->name);
+ free(mesh->appname);
+ free(mesh->confbase);
+ free(mesh->config_key);
+ free(mesh->external_address_url);
+ free(mesh->packet);
+ ecdsa_free(mesh->private_key);
+
+ if(mesh->invitation_addresses) {
+ list_delete_list(mesh->invitation_addresses);
+ }
+
+ main_config_unlock(mesh);
+
+ pthread_mutex_unlock(&mesh->mutex);
+ pthread_mutex_destroy(&mesh->mutex);
+
+ memset(mesh, 0, sizeof(*mesh));
+
+ free(mesh);
+}
+
+bool meshlink_destroy(const char *confbase) {
+ if(!confbase) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ /* Exit early if the confbase directory itself doesn't exist */
+ if(access(confbase, F_OK) && errno == ENOENT) {
+ return true;
+ }
+
+ /* Take the lock the same way meshlink_open() would. */
+ char lockfilename[PATH_MAX];
+ snprintf(lockfilename, sizeof(lockfilename), "%s" SLASH "meshlink.lock", confbase);
+
+ FILE *lockfile = fopen(lockfilename, "w+");
+
+ if(!lockfile) {
+ logger(NULL, MESHLINK_ERROR, "Could not open lock file %s: %s", lockfilename, strerror(errno));
+ meshlink_errno = MESHLINK_ESTORAGE;
+ return false;
+ }
+
+#ifdef FD_CLOEXEC
+ fcntl(fileno(lockfile), F_SETFD, FD_CLOEXEC);
+#endif
+
+#ifdef HAVE_MINGW
+ // TODO: use _locking()?
+#else
+
+ if(flock(fileno(lockfile), LOCK_EX | LOCK_NB) != 0) {
+ logger(NULL, MESHLINK_ERROR, "Configuration directory %s still in use\n", lockfilename);
+ fclose(lockfile);
+ meshlink_errno = MESHLINK_EBUSY;
+ return false;
+ }
+
+#endif
+
+ if(!config_destroy(confbase, "current") || !config_destroy(confbase, "new") || !config_destroy(confbase, "old")) {
+ logger(NULL, MESHLINK_ERROR, "Cannot remove sub-directories in %s: %s\n", confbase, strerror(errno));
+ return false;
+ }
+
+ if(unlink(lockfilename)) {
+ logger(NULL, MESHLINK_ERROR, "Cannot remove lock file %s: %s\n", lockfilename, strerror(errno));
+ fclose(lockfile);
+ meshlink_errno = MESHLINK_ESTORAGE;
+ return false;
+ }
+
+ fclose(lockfile);
+
+ if(!sync_path(confbase)) {
+ logger(NULL, MESHLINK_ERROR, "Cannot sync directory %s: %s\n", confbase, strerror(errno));
+ meshlink_errno = MESHLINK_ESTORAGE;
+ return false;
+ }
+
+ return true;
+}
+
+void meshlink_set_receive_cb(meshlink_handle_t *mesh, meshlink_receive_cb_t cb) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return;
+ }
+
+ pthread_mutex_lock(&mesh->mutex);
+ mesh->receive_cb = cb;
+ pthread_mutex_unlock(&mesh->mutex);
+}
+
+void meshlink_set_connection_try_cb(meshlink_handle_t *mesh, meshlink_connection_try_cb_t cb) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return;
+ }
+
+ pthread_mutex_lock(&mesh->mutex);
+ mesh->connection_try_cb = cb;
+ pthread_mutex_unlock(&mesh->mutex);
+}
+
+void meshlink_set_node_status_cb(meshlink_handle_t *mesh, meshlink_node_status_cb_t cb) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return;
+ }
+
+ pthread_mutex_lock(&mesh->mutex);
+ mesh->node_status_cb = cb;
+ pthread_mutex_unlock(&mesh->mutex);
+}
+
+void meshlink_set_node_pmtu_cb(meshlink_handle_t *mesh, meshlink_node_pmtu_cb_t cb) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return;
+ }
+
+ pthread_mutex_lock(&mesh->mutex);
+ mesh->node_pmtu_cb = cb;
+ pthread_mutex_unlock(&mesh->mutex);
+}
+
+void meshlink_set_node_duplicate_cb(meshlink_handle_t *mesh, meshlink_node_duplicate_cb_t cb) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return;
+ }
+
+ pthread_mutex_lock(&mesh->mutex);
+ mesh->node_duplicate_cb = cb;
+ pthread_mutex_unlock(&mesh->mutex);
+}
+
+void meshlink_set_log_cb(meshlink_handle_t *mesh, meshlink_log_level_t level, meshlink_log_cb_t cb) {
+ if(mesh) {
+ pthread_mutex_lock(&mesh->mutex);
+ mesh->log_cb = cb;
+ mesh->log_level = cb ? level : 0;
+ pthread_mutex_unlock(&mesh->mutex);
+ } else {
+ global_log_cb = cb;
+ global_log_level = cb ? level : 0;
+ }
+}
+
+void meshlink_set_error_cb(struct meshlink_handle *mesh, meshlink_error_cb_t cb) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return;
+ }
+
+ pthread_mutex_lock(&mesh->mutex);
+ mesh->error_cb = cb;
+ pthread_mutex_unlock(&mesh->mutex);
+}
+
+static bool prepare_packet(meshlink_handle_t *mesh, meshlink_node_t *destination, const void *data, size_t len, vpn_packet_t *packet) {
+ meshlink_packethdr_t *hdr;
+
+ if(len > MAXSIZE - sizeof(*hdr)) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ node_t *n = (node_t *)destination;
+
+ if(n->status.blacklisted) {
+ logger(mesh, MESHLINK_ERROR, "Node %s blacklisted, dropping packet\n", n->name);
+ meshlink_errno = MESHLINK_EBLACKLISTED;
+ return false;
+ }
+
+ // Prepare the packet
+ packet->probe = false;
+ packet->tcp = false;
+ packet->len = len + sizeof(*hdr);
+
+ hdr = (meshlink_packethdr_t *)packet->data;
+ memset(hdr, 0, sizeof(*hdr));
+ // leave the last byte as 0 to make sure strings are always
+ // null-terminated if they are longer than the buffer
+ strncpy((char *)hdr->destination, destination->name, sizeof(hdr->destination) - 1);
+ strncpy((char *)hdr->source, mesh->self->name, sizeof(hdr->source) - 1);
+
+ memcpy(packet->data + sizeof(*hdr), data, len);
+
+ return true;
+}
+
+static bool meshlink_send_immediate(meshlink_handle_t *mesh, meshlink_node_t *destination, const void *data, size_t len) {
+ assert(mesh);
+ assert(destination);
+ assert(data);
+ assert(len);
+
+ // Prepare the packet
+ if(!prepare_packet(mesh, destination, data, len, mesh->packet)) {
+ return false;
+ }
+
+ // Send it immediately
+ route(mesh, mesh->self, mesh->packet);
+
+ return true;
+}
+
+bool meshlink_send(meshlink_handle_t *mesh, meshlink_node_t *destination, const void *data, size_t len) {
+ // Validate arguments
+ if(!mesh || !destination) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ if(!len) {
+ return true;
+ }
+
+ if(!data) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ // Prepare the packet
+ vpn_packet_t *packet = malloc(sizeof(*packet));
+
+ if(!packet) {
+ meshlink_errno = MESHLINK_ENOMEM;
+ return false;
+ }
+
+ if(!prepare_packet(mesh, destination, data, len, packet)) {
+ free(packet);
+ return false;
+ }
+
+ // Queue it
+ if(!meshlink_queue_push(&mesh->outpacketqueue, packet)) {
+ free(packet);
+ meshlink_errno = MESHLINK_ENOMEM;
+ return false;
+ }
+
+ logger(mesh, MESHLINK_DEBUG, "Adding packet of %zu bytes to packet queue", len);
+
+ // Notify event loop
+ signal_trigger(&mesh->loop, &mesh->datafromapp);
+
+ return true;
+}
+
+void meshlink_send_from_queue(event_loop_t *loop, void *data) {
+ (void)loop;
+ meshlink_handle_t *mesh = data;
+
+ logger(mesh, MESHLINK_DEBUG, "Flushing the packet queue");
+
+ for(vpn_packet_t *packet; (packet = meshlink_queue_pop(&mesh->outpacketqueue));) {
+ logger(mesh, MESHLINK_DEBUG, "Removing packet of %d bytes from packet queue", packet->len);
+ mesh->self->in_packets++;
+ mesh->self->in_bytes += packet->len;
+ route(mesh, mesh->self, packet);
+ free(packet);
+ }
+}
+
+ssize_t meshlink_get_pmtu(meshlink_handle_t *mesh, meshlink_node_t *destination) {
+ if(!mesh || !destination) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return -1;
+ }
+
+ pthread_mutex_lock(&mesh->mutex);
+
+ node_t *n = (node_t *)destination;
+
+ if(!n->status.reachable) {
+ pthread_mutex_unlock(&mesh->mutex);
+ return 0;
+
+ } else if(n->mtuprobes > 30 && n->minmtu) {
+ pthread_mutex_unlock(&mesh->mutex);
+ return n->minmtu;
+ } else {
+ pthread_mutex_unlock(&mesh->mutex);
+ return MTU;
+ }
+}
+
+char *meshlink_get_fingerprint(meshlink_handle_t *mesh, meshlink_node_t *node) {
+ if(!mesh || !node) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ pthread_mutex_lock(&mesh->mutex);
+
+ node_t *n = (node_t *)node;
+
+ if(!node_read_public_key(mesh, n) || !n->ecdsa) {
+ meshlink_errno = MESHLINK_EINTERNAL;
+ pthread_mutex_unlock(&mesh->mutex);
+ return false;
+ }
+
+ char *fingerprint = ecdsa_get_base64_public_key(n->ecdsa);
+
+ if(!fingerprint) {
+ meshlink_errno = MESHLINK_EINTERNAL;
+ }
+
+ pthread_mutex_unlock(&mesh->mutex);
+ return fingerprint;
+}
+
+meshlink_node_t *meshlink_get_self(meshlink_handle_t *mesh) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ return (meshlink_node_t *)mesh->self;
+}
+
+meshlink_node_t *meshlink_get_node(meshlink_handle_t *mesh, const char *name) {
+ if(!mesh || !name) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ node_t *n = NULL;
+
+ pthread_mutex_lock(&mesh->mutex);
+ n = lookup_node(mesh, (char *)name); // TODO: make lookup_node() use const
+ pthread_mutex_unlock(&mesh->mutex);
+
+ if(!n) {
+ meshlink_errno = MESHLINK_ENOENT;
+ }
+
+ return (meshlink_node_t *)n;
+}
+
+meshlink_submesh_t *meshlink_get_submesh(meshlink_handle_t *mesh, const char *name) {
+ if(!mesh || !name) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ meshlink_submesh_t *submesh = NULL;
+
+ pthread_mutex_lock(&mesh->mutex);
+ submesh = (meshlink_submesh_t *)lookup_submesh(mesh, name);
+ pthread_mutex_unlock(&mesh->mutex);
+
+ if(!submesh) {
+ meshlink_errno = MESHLINK_ENOENT;
+ }
+
+ return submesh;
+}
+
+meshlink_node_t **meshlink_get_all_nodes(meshlink_handle_t *mesh, meshlink_node_t **nodes, size_t *nmemb) {
+ if(!mesh || !nmemb || (*nmemb && !nodes)) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ meshlink_node_t **result;
+
+ //lock mesh->nodes
+ pthread_mutex_lock(&mesh->mutex);
+
+ *nmemb = mesh->nodes->count;
+ result = realloc(nodes, *nmemb * sizeof(*nodes));
+
+ if(result) {
+ meshlink_node_t **p = result;
+
+ for splay_each(node_t, n, mesh->nodes) {
+ *p++ = (meshlink_node_t *)n;
+ }
+ } else {
+ *nmemb = 0;
+ free(nodes);
+ meshlink_errno = MESHLINK_ENOMEM;
+ }
+
+ pthread_mutex_unlock(&mesh->mutex);
+
+ return result;
+}
+
+static meshlink_node_t **meshlink_get_all_nodes_by_condition(meshlink_handle_t *mesh, const void *condition, meshlink_node_t **nodes, size_t *nmemb, search_node_by_condition_t search_node) {
+ meshlink_node_t **result;
+
+ pthread_mutex_lock(&mesh->mutex);
+
+ *nmemb = 0;
+
+ for splay_each(node_t, n, mesh->nodes) {
+ if(search_node(n, condition)) {
+ ++*nmemb;
+ }
+ }
+
+ if(*nmemb == 0) {
+ free(nodes);
+ pthread_mutex_unlock(&mesh->mutex);
+ return NULL;
+ }
+
+ result = realloc(nodes, *nmemb * sizeof(*nodes));
+
+ if(result) {
+ meshlink_node_t **p = result;
+
+ for splay_each(node_t, n, mesh->nodes) {
+ if(search_node(n, condition)) {
+ *p++ = (meshlink_node_t *)n;
+ }
+ }
+ } else {
+ *nmemb = 0;
+ free(nodes);
+ meshlink_errno = MESHLINK_ENOMEM;
+ }
+
+ pthread_mutex_unlock(&mesh->mutex);
+
+ return result;
+}
+
+static bool search_node_by_dev_class(const node_t *node, const void *condition) {
+ dev_class_t *devclass = (dev_class_t *)condition;
+
+ if(*devclass == (dev_class_t)node->devclass) {
+ return true;
+ }
+
+ return false;
+}
+
+static bool search_node_by_submesh(const node_t *node, const void *condition) {
+ if(condition == node->submesh) {
+ return true;
+ }
+
+ return false;
+}
+
+struct time_range {
+ time_t start;
+ time_t end;
+};
+
+static bool search_node_by_last_reachable(const node_t *node, const void *condition) {
+ const struct time_range *range = condition;
+ time_t start = node->last_reachable;
+ time_t end = node->last_unreachable;
+
+ if(end < start) {
+ end = time(NULL);
+
+ if(end < start) {
+ start = end;
+ }
+ }
+
+ if(range->end >= range->start) {
+ return start <= range->end && end >= range->start;
+ } else {
+ return start > range->start || end < range->end;
+ }
+}
+
+meshlink_node_t **meshlink_get_all_nodes_by_dev_class(meshlink_handle_t *mesh, dev_class_t devclass, meshlink_node_t **nodes, size_t *nmemb) {
+ if(!mesh || devclass < 0 || devclass >= DEV_CLASS_COUNT || !nmemb) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ return meshlink_get_all_nodes_by_condition(mesh, &devclass, nodes, nmemb, search_node_by_dev_class);
+}
+
+meshlink_node_t **meshlink_get_all_nodes_by_submesh(meshlink_handle_t *mesh, meshlink_submesh_t *submesh, meshlink_node_t **nodes, size_t *nmemb) {
+ if(!mesh || !submesh || !nmemb) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ return meshlink_get_all_nodes_by_condition(mesh, submesh, nodes, nmemb, search_node_by_submesh);
+}
+
+meshlink_node_t **meshlink_get_all_nodes_by_last_reachable(meshlink_handle_t *mesh, time_t start, time_t end, meshlink_node_t **nodes, size_t *nmemb) {
+ if(!mesh || !nmemb) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ struct time_range range = {start, end};
+
+ return meshlink_get_all_nodes_by_condition(mesh, &range, nodes, nmemb, search_node_by_last_reachable);
+}
+
+dev_class_t meshlink_get_node_dev_class(meshlink_handle_t *mesh, meshlink_node_t *node) {
+ if(!mesh || !node) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return -1;
+ }
+
+ dev_class_t devclass;
+
+ pthread_mutex_lock(&mesh->mutex);
+
+ devclass = ((node_t *)node)->devclass;
+
+ pthread_mutex_unlock(&mesh->mutex);
+
+ return devclass;
+}
+
+meshlink_submesh_t *meshlink_get_node_submesh(meshlink_handle_t *mesh, meshlink_node_t *node) {
+ if(!mesh || !node) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ node_t *n = (node_t *)node;
+
+ meshlink_submesh_t *s;
+
+ s = (meshlink_submesh_t *)n->submesh;
+
+ return s;
+}
+
+bool meshlink_get_node_reachability(struct meshlink_handle *mesh, struct meshlink_node *node, time_t *last_reachable, time_t *last_unreachable) {
+ if(!mesh || !node) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ node_t *n = (node_t *)node;
+ bool reachable;
+
+ pthread_mutex_lock(&mesh->mutex);
+ reachable = n->status.reachable && !n->status.blacklisted;
+
+ if(last_reachable) {
+ *last_reachable = n->last_reachable;
+ }
+
+ if(last_unreachable) {
+ *last_unreachable = n->last_unreachable;
+ }
+
+ pthread_mutex_unlock(&mesh->mutex);
+
+ return reachable;
+}
+
+bool meshlink_sign(meshlink_handle_t *mesh, const void *data, size_t len, void *signature, size_t *siglen) {
+ if(!mesh || !data || !len || !signature || !siglen) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ if(*siglen < MESHLINK_SIGLEN) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ pthread_mutex_lock(&mesh->mutex);
+
+ if(!ecdsa_sign(mesh->private_key, data, len, signature)) {
+ meshlink_errno = MESHLINK_EINTERNAL;
+ pthread_mutex_unlock(&mesh->mutex);
+ return false;
+ }
+
+ *siglen = MESHLINK_SIGLEN;
+ pthread_mutex_unlock(&mesh->mutex);
+ return true;
+}
+
+bool meshlink_verify(meshlink_handle_t *mesh, meshlink_node_t *source, const void *data, size_t len, const void *signature, size_t siglen) {
+ if(!mesh || !source || !data || !len || !signature) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ if(siglen != MESHLINK_SIGLEN) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ pthread_mutex_lock(&mesh->mutex);
+
+ bool rval = false;
+
+ struct node_t *n = (struct node_t *)source;
+
+ if(!node_read_public_key(mesh, n)) {
+ meshlink_errno = MESHLINK_EINTERNAL;
+ rval = false;
+ } else {
+ rval = ecdsa_verify(((struct node_t *)source)->ecdsa, data, len, signature);
+ }
+
+ pthread_mutex_unlock(&mesh->mutex);
+ return rval;
+}
+
+static bool refresh_invitation_key(meshlink_handle_t *mesh) {
+ pthread_mutex_lock(&mesh->mutex);
+
+ size_t count = invitation_purge_old(mesh, time(NULL) - mesh->invitation_timeout);
+
+ if(!count) {
+ // TODO: Update invitation key if necessary?
+ }
+
+ pthread_mutex_unlock(&mesh->mutex);
+
+ return mesh->invitation_key;
+}
+
+bool meshlink_set_canonical_address(meshlink_handle_t *mesh, meshlink_node_t *node, const char *address, const char *port) {
+ if(!mesh || !node || !address) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ if(!is_valid_hostname(address)) {
+ logger(mesh, MESHLINK_DEBUG, "Invalid character in address: %s", address);
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ if((node_t *)node != mesh->self && !port) {
+ logger(mesh, MESHLINK_DEBUG, "Missing port number!");
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+
+ }
+
+ if(port && !is_valid_port(port)) {
+ logger(mesh, MESHLINK_DEBUG, "Invalid character in port: %s", address);
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ char *canonical_address;
+
+ if(port) {
+ xasprintf(&canonical_address, "%s %s", address, port);
+ } else {
+ canonical_address = xstrdup(address);
+ }
+
+ pthread_mutex_lock(&mesh->mutex);
+
+ node_t *n = (node_t *)node;
+ free(n->canonical_address);
+ n->canonical_address = canonical_address;
+
+ if(!node_write_config(mesh, n)) {
+ pthread_mutex_unlock(&mesh->mutex);
+ return false;
+ }
+
+ pthread_mutex_unlock(&mesh->mutex);
+
+ return config_sync(mesh, "current");
+}
+
+bool meshlink_add_invitation_address(struct meshlink_handle *mesh, const char *address, const char *port) {
+ if(!mesh || !address) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ if(!is_valid_hostname(address)) {
+ logger(mesh, MESHLINK_DEBUG, "Invalid character in address: %s\n", address);
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ if(port && !is_valid_port(port)) {
+ logger(mesh, MESHLINK_DEBUG, "Invalid character in port: %s\n", address);
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ char *combo;
+
+ if(port) {
+ xasprintf(&combo, "%s/%s", address, port);
+ } else {
+ combo = xstrdup(address);
+ }
+
+ pthread_mutex_lock(&mesh->mutex);
+
+ if(!mesh->invitation_addresses) {
+ mesh->invitation_addresses = list_alloc((list_action_t)free);
+ }
+
+ list_insert_tail(mesh->invitation_addresses, combo);
+ pthread_mutex_unlock(&mesh->mutex);
+
+ return true;
+}
+
+void meshlink_clear_invitation_addresses(struct meshlink_handle *mesh) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return;
+ }
+
+ pthread_mutex_lock(&mesh->mutex);
+
+ if(mesh->invitation_addresses) {
+ list_delete_list(mesh->invitation_addresses);
+ mesh->invitation_addresses = NULL;
+ }
+
+ pthread_mutex_unlock(&mesh->mutex);
+}
+
+bool meshlink_add_address(meshlink_handle_t *mesh, const char *address) {
+ return meshlink_set_canonical_address(mesh, (meshlink_node_t *)mesh->self, address, NULL);
+}
+
+bool meshlink_add_external_address(meshlink_handle_t *mesh) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ char *address = meshlink_get_external_address(mesh);
+
+ if(!address) {
+ return false;
+ }
+
+ bool rval = meshlink_set_canonical_address(mesh, (meshlink_node_t *)mesh->self, address, NULL);
+ free(address);
+
+ return rval;
+}
+
+int meshlink_get_port(meshlink_handle_t *mesh) {
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return -1;
+ }
+
+ if(!mesh->myport) {
+ meshlink_errno = MESHLINK_EINTERNAL;
+ return -1;
+ }
+
+ int port;
+
+ pthread_mutex_lock(&mesh->mutex);
+ port = atoi(mesh->myport);
+ pthread_mutex_unlock(&mesh->mutex);
+
+ return port;
+}
+
+bool meshlink_set_port(meshlink_handle_t *mesh, int port) {
+ if(!mesh || port < 0 || port >= 65536 || mesh->threadstarted) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ if(mesh->myport && port == atoi(mesh->myport)) {
+ return true;
+ }
+
+ if(!try_bind(mesh, port)) {
+ meshlink_errno = MESHLINK_ENETWORK;
+ return false;
+ }
+
+ devtool_trybind_probe();
+
+ bool rval = false;
+
+ pthread_mutex_lock(&mesh->mutex);
+
+ if(mesh->threadstarted) {
+ meshlink_errno = MESHLINK_EINVAL;
+ goto done;
+ }
+
+ free(mesh->myport);
+ xasprintf(&mesh->myport, "%d", port);
+
+ /* Close down the network. This also deletes mesh->self. */
+ close_network_connections(mesh);
+
+ /* Recreate mesh->self. */
+ mesh->self = new_node();
+ mesh->self->name = xstrdup(mesh->name);
+ mesh->self->devclass = mesh->devclass;
+ mesh->self->session_id = mesh->session_id;
+ xasprintf(&mesh->myport, "%d", port);
+
+ if(!node_read_public_key(mesh, mesh->self)) {
+ logger(NULL, MESHLINK_ERROR, "Could not read our host configuration file!");
+ meshlink_errno = MESHLINK_ESTORAGE;
+ free_node(mesh->self);
+ mesh->self = NULL;
+ goto done;
+ } else if(!setup_network(mesh)) {
+ meshlink_errno = MESHLINK_ENETWORK;
+ goto done;
+ }
+
+ /* Rebuild our own list of recent addresses */
+ memset(mesh->self->recent, 0, sizeof(mesh->self->recent));
+ add_local_addresses(mesh);
+
+ /* Write meshlink.conf with the updated port number */
+ write_main_config_files(mesh);
+
+ rval = config_sync(mesh, "current");
+
+done:
+ pthread_mutex_unlock(&mesh->mutex);
+
+ return rval && meshlink_get_port(mesh) == port;
+}
+
+void meshlink_set_invitation_timeout(meshlink_handle_t *mesh, int timeout) {
+ mesh->invitation_timeout = timeout;
+}
+
+char *meshlink_invite_ex(meshlink_handle_t *mesh, meshlink_submesh_t *submesh, const char *name, uint32_t flags) {
+ meshlink_submesh_t *s = NULL;
+
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ if(submesh) {
+ s = (meshlink_submesh_t *)lookup_submesh(mesh, submesh->name);
+
+ if(s != submesh) {
+ logger(mesh, MESHLINK_DEBUG, "Invalid SubMesh Handle.\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+ } else {
+ s = (meshlink_submesh_t *)mesh->self->submesh;
+ }
+
+ pthread_mutex_lock(&mesh->mutex);
+
+ // Check validity of the new node's name
+ if(!check_id(name)) {
+ logger(mesh, MESHLINK_ERROR, "Invalid name for node.\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ pthread_mutex_unlock(&mesh->mutex);
+ return NULL;
+ }
+
+ // Ensure no host configuration file with that name exists
+ if(config_exists(mesh, "current", name)) {
+ logger(mesh, MESHLINK_ERROR, "A host config file for %s already exists!\n", name);
+ meshlink_errno = MESHLINK_EEXIST;
+ pthread_mutex_unlock(&mesh->mutex);
+ return NULL;
+ }
+
+ // Ensure no other nodes know about this name
+ if(lookup_node(mesh, name)) {
+ logger(mesh, MESHLINK_ERROR, "A node with name %s is already known!\n", name);
+ meshlink_errno = MESHLINK_EEXIST;
+ pthread_mutex_unlock(&mesh->mutex);
+ return NULL;
+ }
+
+ // Get the local address
+ char *address = get_my_hostname(mesh, flags);
+
+ if(!address) {
+ logger(mesh, MESHLINK_ERROR, "No Address known for ourselves!\n");
+ meshlink_errno = MESHLINK_ERESOLV;
+ pthread_mutex_unlock(&mesh->mutex);
+ return NULL;
+ }
+
+ if(!refresh_invitation_key(mesh)) {
+ meshlink_errno = MESHLINK_EINTERNAL;
+ pthread_mutex_unlock(&mesh->mutex);
+ return NULL;
+ }
+
+ // If we changed our own host config file, write it out now
+ if(mesh->self->status.dirty) {
+ if(!node_write_config(mesh, mesh->self)) {
+ logger(mesh, MESHLINK_ERROR, "Could not write our own host config file!\n");
+ pthread_mutex_unlock(&mesh->mutex);
+ return NULL;
+ }
+ }
+
+ char hash[64];
+
+ // Create a hash of the key.
+ char *fingerprint = ecdsa_get_base64_public_key(mesh->invitation_key);
+ sha512(fingerprint, strlen(fingerprint), hash);
+ b64encode_urlsafe(hash, hash, 18);
+
+ // Create a random cookie for this invitation.
+ char cookie[25];
+ randomize(cookie, 18);
+
+ // Create a filename that doesn't reveal the cookie itself
+ char buf[18 + strlen(fingerprint)];
+ char cookiehash[64];
+ memcpy(buf, cookie, 18);
+ memcpy(buf + 18, fingerprint, sizeof(buf) - 18);
+ sha512(buf, sizeof(buf), cookiehash);
+ b64encode_urlsafe(cookiehash, cookiehash, 18);
+
+ b64encode_urlsafe(cookie, cookie, 18);
+
+ free(fingerprint);
+
+ /* Construct the invitation file */
+ uint8_t outbuf[4096];
+ packmsg_output_t inv = {outbuf, sizeof(outbuf)};
+
+ packmsg_add_uint32(&inv, MESHLINK_INVITATION_VERSION);
+ packmsg_add_str(&inv, name);
+ packmsg_add_str(&inv, s ? s->name : CORE_MESH);
+ packmsg_add_int32(&inv, DEV_CLASS_UNKNOWN); /* TODO: allow this to be set by inviter? */
+
+ /* TODO: Add several host config files to bootstrap connections.
+ * Note: make sure we only add config files of nodes that are in the core mesh or the same submesh,
+ * and are not blacklisted.
+ */
+ config_t configs[5];
+ memset(configs, 0, sizeof(configs));
+ int count = 0;
+
+ if(config_read(mesh, "current", mesh->self->name, &configs[count], mesh->config_key)) {
+ count++;
+ }
+
+ /* Append host config files to the invitation file */
+ packmsg_add_array(&inv, count);
+
+ for(int i = 0; i < count; i++) {
+ packmsg_add_bin(&inv, configs[i].buf, configs[i].len);
+ config_free(&configs[i]);
+ }
+
+ config_t config = {outbuf, packmsg_output_size(&inv, outbuf)};
+
+ if(!invitation_write(mesh, "current", cookiehash, &config, mesh->config_key)) {
+ logger(mesh, MESHLINK_DEBUG, "Could not create invitation file %s: %s\n", cookiehash, strerror(errno));
+ meshlink_errno = MESHLINK_ESTORAGE;
+ pthread_mutex_unlock(&mesh->mutex);
+ return NULL;
+ }
+
+ // Create an URL from the local address, key hash and cookie
+ char *url;
+ xasprintf(&url, "%s/%s%s", address, hash, cookie);
+ free(address);
+
+ pthread_mutex_unlock(&mesh->mutex);
+ return url;
+}
+
+char *meshlink_invite(meshlink_handle_t *mesh, meshlink_submesh_t *submesh, const char *name) {
+ return meshlink_invite_ex(mesh, submesh, name, 0);
+}
+
+bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) {
+ if(!mesh || !invitation) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ join_state_t state = {
+ .mesh = mesh,
+ .sock = -1,
+ };