/// 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;
/// 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));
/// 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));
/// 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;
/// 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);
/// 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));
/// 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));
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));
/// 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));
/// 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));
/// 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));
/// 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));
/// 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));
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);
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 *);
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);
logger(mesh, MESHLINK_DEBUG, "Configuration stored in: %s\n", mesh->confbase);
- load_all_nodes(mesh);
-
return true;
}
}
-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) {
+static meshlink_handle_t *meshlink_open_internal(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_DEBUG, "meshlink_open called\n");
-
- 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(!name || !*name) {
- logger(NULL, MESHLINK_ERROR, "No name given!\n");
- //return NULL;
- } else { //check name only if there is a name != NULL
+ if(name) {
if(!check_id(name)) {
logger(NULL, MESHLINK_ERROR, "Invalid name given!\n");
meshlink_errno = MESHLINK_EINVAL;
return NULL;
- } else {
- usingname = true;
}
+
+ usingname = true;
}
if((int)devclass < 0 || devclass > _DEV_CLASS_MAX) {
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));
+
+ if(confbase) {
+ mesh->confbase = xstrdup(confbase);
}
- meshlink_handle_t *mesh = xzalloc(sizeof(meshlink_handle_t));
- mesh->confbase = xstrdup(confbase);
mesh->appname = xstrdup(appname);
mesh->devclass = devclass;
mesh->discovery = true;
}
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);
+ if(!confbase || !*confbase) {
+ logger(NULL, MESHLINK_ERROR, "No confbase given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ return meshlink_open_internal(confbase, name, appname, devclass, NULL, 0);
+}
+
+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;
+ }
+
+ if(!key || !keylen) {
+ logger(NULL, MESHLINK_ERROR, "No key given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ return meshlink_open_internal(confbase, name, appname, devclass, key, keylen);
}
+meshlink_handle_t *meshlink_open_ephemeral(const char *name, const char *appname, dev_class_t devclass) {
+ if(!name || !*name) {
+ logger(NULL, MESHLINK_ERROR, "No name given!\n");
+ meshlink_errno = MESHLINK_EINVAL;
+ return NULL;
+ }
+
+ return meshlink_open_internal(NULL, name, appname, devclass, NULL, 0);
+}
+
+
static void *meshlink_main_loop(void *arg) {
meshlink_handle_t *mesh = arg;
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);
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) {
*/
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);
+
/// 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.
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 *);
return true;
}
+/// 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;
+ }
+
+ packmsg_input_t in = {config->buf, config->len};
+ uint32_t version = packmsg_get_uint32(&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;
+ }
+
+ 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);
+
+ if(len != 32) {
+ return false;
+ }
+
+ if(!ecdsa_active(n->ecdsa)) {
+ n->ecdsa = ecdsa_set_public_key(key);
+ }
+
+ n->canonical_address = packmsg_get_str_dup(&in);
+ uint32_t count = packmsg_get_array(&in);
+
+ if(count > 5) {
+ count = 5;
+ }
+
+ for(uint32_t i = 0; i < count; i++) {
+ n->recent[i] = packmsg_get_sockaddr(&in);
+ }
+
+ return packmsg_done(&in);
+}
+
/// 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 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, ent->d_name);
-
- if(n) {
- continue;
- }
+ node_t *n = lookup_node(mesh, name);
- n = new_node();
- n->name = xstrdup(ent->d_name);
- node_read_devclass(mesh, n);
- node_add(mesh, n);
+ if(n) {
+ return;
}
- closedir(dir);
+ n = new_node();
+ n->name = xstrdup(name);
+ node_read_devclass(mesh, n);
+ node_add(mesh, n);
}
/*
graph(mesh);
- load_all_nodes(mesh);
+ config_scan_all(mesh, load_node);
/* Open sockets */
/channels-fork
/duplicate
/echo-fork
+/encrypted
+/ephemeral
/import-export
/invite-join
/sign-verify
channels-cornercases.test \
duplicate.test \
encrypted.test \
+ ephemeral.test \
import-export.test \
invite-join.test \
sign-verify.test \
duplicate \
echo-fork \
encrypted \
+ ephemeral \
import-export \
invite-join \
sign-verify \
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
--- /dev/null
+#include <stdio.h>
+#include <sys/time.h>
+#include <assert.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 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;
+}
--- /dev/null
+#!/bin/sh
+
+./ephemeral