From 6bad600979ebea04def1ad53d3de0b06aac60b1e Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Mon, 11 Mar 2019 22:02:30 +0100 Subject: [PATCH] Add support for opening a MeshLink instance without permanent storage. --- src/conf.c | 77 ++++++++++++++++++++++++++ src/conf.h | 3 + src/meshlink.c | 131 ++++++++++++++++++++++++++++---------------- src/meshlink.h | 24 ++++++++ src/net.h | 2 +- src/net_setup.c | 73 ++++++++++++------------ test/.gitignore | 2 + test/Makefile.am | 5 ++ test/ephemeral.c | 74 +++++++++++++++++++++++++ test/ephemeral.test | 3 + 10 files changed, 312 insertions(+), 82 deletions(-) create mode 100644 test/ephemeral.c create mode 100755 test/ephemeral.test diff --git a/src/conf.c b/src/conf.c index 9e93c9a1..9664e2b7 100644 --- a/src/conf.c +++ b/src/conf.c @@ -75,6 +75,10 @@ static void deltree(const char *dirname) { /// Create a fresh configuration directory bool config_init(meshlink_handle_t *mesh) { + if(!mesh->confbase) { + return true; + } + if(mkdir(mesh->confbase, 0700) && errno != EEXIST) { logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", mesh->confbase, strerror(errno)); return false; @@ -131,6 +135,10 @@ bool config_destroy(const char *confbase) { /// Check the presence of the main configuration file. bool main_config_exists(meshlink_handle_t *mesh) { + if(!mesh->confbase) { + return false; + } + char path[PATH_MAX]; make_main_path(mesh, path, sizeof(path)); @@ -139,6 +147,10 @@ bool main_config_exists(meshlink_handle_t *mesh) { /// Lock the main configuration file. bool main_config_lock(meshlink_handle_t *mesh) { + if(!mesh->confbase) { + return true; + } + char path[PATH_MAX]; make_main_path(mesh, path, sizeof(path)); @@ -181,6 +193,10 @@ void main_config_unlock(meshlink_handle_t *mesh) { /// Read a configuration file from a FILE handle. bool config_read_file(meshlink_handle_t *mesh, FILE *f, config_t *config) { + if(!mesh->confbase) { + return false; + } + (void)mesh; long len; @@ -228,6 +244,10 @@ bool config_read_file(meshlink_handle_t *mesh, FILE *f, config_t *config) { /// Write a configuration file to a FILE handle. bool config_write_file(meshlink_handle_t *mesh, FILE *f, const config_t *config) { + if(!mesh->confbase) { + return true; + } + if(mesh->config_key) { uint8_t buf[config->len + 16]; size_t len = sizeof(buf); @@ -266,6 +286,10 @@ void config_free(config_t *config) { /// Check the presence of a host configuration file. bool config_exists(meshlink_handle_t *mesh, const char *name) { + if(!mesh->confbase) { + return false; + } + char path[PATH_MAX]; make_host_path(mesh, name, path, sizeof(path)); @@ -274,6 +298,10 @@ bool config_exists(meshlink_handle_t *mesh, const char *name) { /// Read a host configuration file. bool config_read(meshlink_handle_t *mesh, const char *name, config_t *config) { + if(!mesh->confbase) { + return false; + } + char path[PATH_MAX]; make_host_path(mesh, name, path, sizeof(path)); @@ -294,8 +322,37 @@ bool config_read(meshlink_handle_t *mesh, const char *name, config_t *config) { return true; } +void config_scan_all(meshlink_handle_t *mesh, config_scan_action_t action) { + if(!mesh->confbase) { + return; + } + + DIR *dir; + struct dirent *ent; + char dname[PATH_MAX]; + make_host_path(mesh, NULL, dname, sizeof(dname)); + + dir = opendir(dname); + + if(!dir) { + logger(mesh, MESHLINK_ERROR, "Could not open %s: %s", dname, strerror(errno)); + meshlink_errno = MESHLINK_ESTORAGE; + return; + } + + while((ent = readdir(dir))) { + action(mesh, ent->d_name); + } + + closedir(dir); +} + /// Write a host configuration file. bool config_write(meshlink_handle_t *mesh, const char *name, const config_t *config) { + if(!mesh->confbase) { + return true; + } + char path[PATH_MAX]; make_host_path(mesh, name, path, sizeof(path)); @@ -318,6 +375,10 @@ bool config_write(meshlink_handle_t *mesh, const char *name, const config_t *con /// Read the main configuration file. bool main_config_read(meshlink_handle_t *mesh, config_t *config) { + if(!mesh->confbase) { + return false; + } + char path[PATH_MAX]; make_main_path(mesh, path, sizeof(path)); @@ -340,6 +401,10 @@ bool main_config_read(meshlink_handle_t *mesh, config_t *config) { /// Write the main configuration file. bool main_config_write(meshlink_handle_t *mesh, const config_t *config) { + if(!mesh->confbase) { + return true; + } + char path[PATH_MAX]; make_main_path(mesh, path, sizeof(path)); @@ -362,6 +427,10 @@ bool main_config_write(meshlink_handle_t *mesh, const config_t *config) { /// Read an invitation file, and immediately delete it. bool invitation_read(meshlink_handle_t *mesh, const char *name, config_t *config) { + if(!mesh->confbase) { + return false; + } + char path[PATH_MAX]; char used_path[PATH_MAX]; make_invitation_path(mesh, name, path, sizeof(path)); @@ -416,6 +485,10 @@ bool invitation_read(meshlink_handle_t *mesh, const char *name, config_t *config /// Write an invitation file. bool invitation_write(meshlink_handle_t *mesh, const char *name, const config_t *config) { + if(!mesh->confbase) { + return true; + } + char path[PATH_MAX]; make_invitation_path(mesh, name, path, sizeof(path)); @@ -438,6 +511,10 @@ bool invitation_write(meshlink_handle_t *mesh, const char *name, const config_t /// Purge old invitation files size_t invitation_purge_old(meshlink_handle_t *mesh, time_t deadline) { + if(!mesh->confbase) { + return true; + } + char path[PATH_MAX]; make_invitation_path(mesh, "", path, sizeof(path)); diff --git a/src/conf.h b/src/conf.h index f2bb0762..63d5fef4 100644 --- a/src/conf.h +++ b/src/conf.h @@ -27,6 +27,8 @@ typedef struct config_t { size_t len; } config_t; +typedef void (*config_scan_action_t)(struct meshlink_handle *mesh, const char *name); + //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); @@ -43,6 +45,7 @@ extern bool main_config_write(struct meshlink_handle *mesh, const struct config_ 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 void config_scan_all(struct meshlink_handle *mesh, config_scan_action_t action); 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 *); diff --git a/src/meshlink.c b/src/meshlink.c index eb1a9c84..0697c025 100644 --- a/src/meshlink.c +++ b/src/meshlink.c @@ -598,36 +598,28 @@ static bool finalize_join(meshlink_handle_t *mesh, const void *buf, uint16_t len return false; } - packmsg_input_t in2 = {data, len}; - uint32_t version = packmsg_get_uint32(&in2); + config_t config = {data, len}; + node_t *n = new_node(); - if(version != MESHLINK_CONFIG_VERSION) { + if(!node_read_from_config(mesh, n, &config)) { + free_node(n); logger(mesh, MESHLINK_ERROR, "Invalid host config file in invitation file!\n"); + meshlink_errno = MESHLINK_EPEER; return false; } - char *host = packmsg_get_str_dup(&in2); - - if(!check_id(host)) { - logger(mesh, MESHLINK_ERROR, "Invalid node name in invitation file!\n"); - free(host); - return false; - } - - if(!strcmp(host, name)) { + if(!strcmp(n->name, name)) { logger(mesh, MESHLINK_DEBUG, "Secondary chunk would overwrite our own host config file.\n"); - free(host); + free_node(n); + meshlink_errno = MESHLINK_EPEER; return false; } - config_t config = {data, len}; - config_write(mesh, host, &config); - - node_t *n = new_node(); - n->name = host; - node_read_full(mesh, n); - n->devclass = mesh->devclass; node_add(mesh, n); + + if(!config_write(mesh, n->name, &config)) { + return false; + } } sptps_send_record(&(mesh->sptps), 1, ecdsa_get_public_key(mesh->private_key), 32); @@ -639,8 +631,6 @@ static bool finalize_join(meshlink_handle_t *mesh, const void *buf, uint16_t len logger(mesh, MESHLINK_DEBUG, "Configuration stored in: %s\n", mesh->confbase); - load_all_nodes(mesh); - return true; } @@ -1083,6 +1073,12 @@ void meshlink_open_params_free(meshlink_open_params_t *params) { } 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 = {NULL}; @@ -1096,6 +1092,12 @@ meshlink_handle_t *meshlink_open(const char *confbase, const char *name, const c } 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 = {NULL}; @@ -1112,18 +1114,24 @@ meshlink_handle_t *meshlink_open_encrypted(const char *confbase, const char *nam return meshlink_open_ex(¶ms); } +meshlink_handle_t *meshlink_open_ephemeral(const char *name, const char *appname, dev_class_t devclass) { + /* Create a temporary struct on the stack, to avoid allocating and freeing one. */ + meshlink_open_params_t params = {NULL}; + + 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) { // Validate arguments provided by the application bool usingname = false; logger(NULL, MESHLINK_DEBUG, "meshlink_open called\n"); - if(!params->confbase || !*params->confbase) { - logger(NULL, MESHLINK_ERROR, "No confbase given!\n"); - meshlink_errno = MESHLINK_EINVAL; - return NULL; - } - if(!params->appname || !*params->appname) { logger(NULL, MESHLINK_ERROR, "No appname given!\n"); meshlink_errno = MESHLINK_EINVAL; @@ -1163,7 +1171,11 @@ meshlink_handle_t *meshlink_open_ex(const meshlink_open_params_t *params) { } meshlink_handle_t *mesh = xzalloc(sizeof(meshlink_handle_t)); - mesh->confbase = xstrdup(params->confbase); + + if(params->confbase) { + mesh->confbase = xstrdup(params->confbase); + } + mesh->appname = xstrdup(params->appname); mesh->devclass = params->devclass; mesh->discovery = true; @@ -1426,7 +1438,7 @@ void meshlink_stop(meshlink_handle_t *mesh) { } void meshlink_close(meshlink_handle_t *mesh) { - if(!mesh || !mesh->confbase) { + if(!mesh) { meshlink_errno = MESHLINK_EINVAL; return; } @@ -2369,38 +2381,64 @@ char *meshlink_export(meshlink_handle_t *mesh) { return NULL; } - config_t config; + // Create a config file on the fly. - // Get our 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_str(&out, CORE_MESH); pthread_mutex_lock(&(mesh->mesh_mutex)); - if(!config_read(mesh, mesh->self->name, &config)) { - meshlink_errno = MESHLINK_ESTORAGE; - pthread_mutex_unlock(&mesh->mesh_mutex); - return NULL; + packmsg_add_int32(&out, mesh->self->devclass); + packmsg_add_bool(&out, mesh->self->status.blacklisted); + packmsg_add_bin(&out, ecdsa_get_public_key(mesh->private_key), 32); + packmsg_add_str(&out, mesh->self->canonical_address ? mesh->self->canonical_address : ""); + + uint32_t count = 0; + + for(uint32_t i = 0; i < 5; i++) { + if(mesh->self->recent[i].sa.sa_family) { + count++; + } else { + break; + } + } + + packmsg_add_array(&out, count); + + for(uint32_t i = 0; i < count; i++) { + packmsg_add_sockaddr(&out, &mesh->self->recent[i]); } pthread_mutex_unlock(&(mesh->mesh_mutex)); + if(!packmsg_output_ok(&out)) { + logger(mesh, MESHLINK_DEBUG, "Error creating export data\n"); + meshlink_errno = MESHLINK_EINTERNAL; + return NULL; + } + // Prepare a base64-encoded packmsg array containing our config file - 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); + uint32_t len = packmsg_output_size(&out, buf); + uint32_t len2 = ((len + 4) * 4) / 3 + 4; + uint8_t *buf2 = xmalloc(len2); + packmsg_output_t out2 = {buf2, len2}; + packmsg_add_array(&out2, 1); + packmsg_add_bin(&out2, buf, packmsg_output_size(&out, buf)); - if(!packmsg_output_ok(&out)) { + if(!packmsg_output_ok(&out2)) { logger(mesh, MESHLINK_DEBUG, "Error creating export data\n"); meshlink_errno = MESHLINK_EINTERNAL; - free(buf); + free(buf2); return NULL; } - b64encode_urlsafe(buf, (char *)buf, packmsg_output_size(&out, buf)); + b64encode_urlsafe(buf2, (char *)buf2, packmsg_output_size(&out2, buf2)); - return (char *)buf; + return (char *)buf2; } bool meshlink_import(meshlink_handle_t *mesh, const char *data) { @@ -2500,8 +2538,9 @@ bool meshlink_import(meshlink_handle_t *mesh, const char *data) { } if(!packmsg_done(&in2) || keylen != 32) { - packmsg_input_invalidate(&in2); + packmsg_input_invalidate(&in); free_node(n); + break; } else { config_t config = {data, len}; config_write(mesh, n->name, &config); diff --git a/src/meshlink.h b/src/meshlink.h index 61e59b6b..4a64cdb3 100644 --- a/src/meshlink.h +++ b/src/meshlink.h @@ -266,6 +266,30 @@ extern meshlink_handle_t *meshlink_open(const char *confbase, const char *name, */ 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); +/// Create an ephemeral MeshLink instance that does not store any state. +/** This function creates a MeshLink instance. + * No state is ever saved, so once this instance is closed, all its state is gone. + * + * 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 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. + * + * @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_ephemeral(const char *name, const char *appname, dev_class_t devclass); + /// Create Sub-Mesh. /** This function causes MeshLink to open a new Sub-Mesh network * create a new thread, which will handle all network I/O. diff --git a/src/net.h b/src/net.h index 19a53d21..d44de43a 100644 --- a/src/net.h +++ b/src/net.h @@ -108,7 +108,7 @@ 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_public_key(struct meshlink_handle *mesh, struct node_t *); -extern bool node_read_full(struct meshlink_handle *mesh, struct node_t *); +extern bool node_read_from_config(struct meshlink_handle *mesh, struct node_t *, const config_t *config); 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 *); diff --git a/src/net_setup.c b/src/net_setup.c index 905e442d..4f2022b7 100644 --- a/src/net_setup.c +++ b/src/net_setup.c @@ -128,19 +128,36 @@ bool node_read_public_key(meshlink_handle_t *mesh, node_t *n) { return true; } -/// 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) { +/// Fill in node details from a config blob. +bool node_read_from_config(meshlink_handle_t *mesh, node_t *n, const config_t *config) { if(n->canonical_address) { return true; } - config_t config; - packmsg_input_t in; + packmsg_input_t in = {config->buf, config->len}; + uint32_t version = packmsg_get_uint32(&in); - if(!node_get_config(mesh, n, &config, &in)) { + if(version != MESHLINK_CONFIG_VERSION) { + return false; + } + + char *name = packmsg_get_str_dup(&in); + + if(!name) { return false; } + if(n->name) { + if(strcmp(n->name, name)) { + free(name); + return false; + } + + free(name); + } else { + n->name = name; + } + char *submesh_name = packmsg_get_str_dup(&in); if(!strcmp(submesh_name, CORE_MESH)) { @@ -155,10 +172,10 @@ bool node_read_full(meshlink_handle_t *mesh, node_t *n) { } } - packmsg_get_int32(&in); /* devclass */ - packmsg_get_bool(&in); /* blacklisted */ + n->devclass = packmsg_get_int32(&in); + n->status.blacklisted = packmsg_get_bool(&in); const void *key; - uint32_t len = packmsg_get_bin_raw(&in, &key); /* public key */ + uint32_t len = packmsg_get_bin_raw(&in, &key); if(len != 32) { return false; @@ -169,7 +186,6 @@ bool node_read_full(meshlink_handle_t *mesh, node_t *n) { } n->canonical_address = packmsg_get_str_dup(&in); - uint32_t count = packmsg_get_array(&in); if(count > 5) { @@ -180,8 +196,6 @@ bool node_read_full(meshlink_handle_t *mesh, node_t *n) { n->recent[i] = packmsg_get_sockaddr(&in); } - config_free(&config); - return packmsg_done(&in); } @@ -228,37 +242,26 @@ bool node_write_config(meshlink_handle_t *mesh, node_t *n) { return config_write(mesh, n->name, &config); } -void load_all_nodes(meshlink_handle_t *mesh) { - DIR *dir; - struct dirent *ent; - char dname[PATH_MAX]; - - snprintf(dname, PATH_MAX, "%s" SLASH "hosts", mesh->confbase); - dir = opendir(dname); - - if(!dir) { - logger(mesh, MESHLINK_ERROR, "Could not open %s: %s", dname, strerror(errno)); +static void load_node(meshlink_handle_t *mesh, const char *name) { + if(!check_id(name)) { return; } - while((ent = readdir(dir))) { - if(!check_id(ent->d_name)) { - continue; - } + node_t *n = lookup_node(mesh, name); - node_t *n = lookup_node(mesh, ent->d_name); + if(n) { + return; + } - if(n) { - continue; - } + n = new_node(); + n->name = xstrdup(name); - n = new_node(); - n->name = xstrdup(ent->d_name); - node_read_partial(mesh, n); - node_add(mesh, n); + if(!node_read_partial(mesh, n)) { + free_node(n); + return; } - closedir(dir); + node_add(mesh, n); } /* @@ -373,7 +376,7 @@ bool setup_myself(meshlink_handle_t *mesh) { graph(mesh); - load_all_nodes(mesh); + config_scan_all(mesh, load_node); /* Open sockets */ diff --git a/test/.gitignore b/test/.gitignore index cbb1c1ee..98cd87cd 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -7,6 +7,8 @@ /channels-fork /duplicate /echo-fork +/encrypted +/ephemeral /import-export /invite-join /sign-verify diff --git a/test/Makefile.am b/test/Makefile.am index e96213ed..45ad064c 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -6,6 +6,7 @@ TESTS = \ channels-cornercases.test \ duplicate.test \ encrypted.test \ + ephemeral.test \ import-export.test \ invite-join.test \ sign-verify.test \ @@ -29,6 +30,7 @@ check_PROGRAMS = \ duplicate \ echo-fork \ encrypted \ + ephemeral \ import-export \ invite-join \ sign-verify \ @@ -62,6 +64,9 @@ echo_fork_LDADD = ../src/libmeshlink.la encrypted_SOURCES = encrypted.c encrypted_LDADD = ../src/libmeshlink.la +ephemeral_SOURCES = ephemeral.c +ephemeral_LDADD = ../src/libmeshlink.la + import_export_SOURCES = import-export.c import_export_LDADD = ../src/libmeshlink.la diff --git a/test/ephemeral.c b/test/ephemeral.c new file mode 100644 index 00000000..10a6e2be --- /dev/null +++ b/test/ephemeral.c @@ -0,0 +1,74 @@ +#include +#include +#include + +#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 two ephemeral meshlink instance. + + meshlink_handle_t *mesh1 = meshlink_open_ephemeral("foo", "ephemeral", DEV_CLASS_BACKBONE); + meshlink_handle_t *mesh2 = meshlink_open_ephemeral("bar", "ephemeral", DEV_CLASS_BACKBONE); + + assert(mesh1); + assert(mesh2); + + meshlink_set_log_cb(mesh1, MESHLINK_DEBUG, log_cb); + meshlink_set_log_cb(mesh2, MESHLINK_DEBUG, log_cb); + + // Exchange data + + assert(meshlink_import(mesh1, meshlink_export(mesh2))); + assert(meshlink_import(mesh2, meshlink_export(mesh1))); + + // Check that they know each other + + assert(meshlink_get_node(mesh1, "bar")); + assert(meshlink_get_node(mesh2, "foo")); + + // Close the ephemeral instances and reopen them. + + meshlink_close(mesh1); + meshlink_close(mesh2); + + mesh1 = meshlink_open_ephemeral("foo", "ephemeral", DEV_CLASS_BACKBONE); + mesh2 = meshlink_open_ephemeral("bar", "ephemeral", DEV_CLASS_BACKBONE); + + assert(mesh1); + assert(mesh2); + + meshlink_set_log_cb(mesh1, MESHLINK_DEBUG, log_cb); + meshlink_set_log_cb(mesh2, MESHLINK_DEBUG, log_cb); + + // Check that the nodes no longer know each other + + assert(!meshlink_get_node(mesh1, "bar")); + assert(!meshlink_get_node(mesh2, "foo")); + + // That's it. + + meshlink_close(mesh1); + meshlink_close(mesh2); + + return 0; +} diff --git a/test/ephemeral.test b/test/ephemeral.test new file mode 100755 index 00000000..9c49dbe8 --- /dev/null +++ b/test/ephemeral.test @@ -0,0 +1,3 @@ +#!/bin/sh + +./ephemeral -- 2.39.5