}
/* Ensure the configuration directory metadata is on disk */
- if(!config_sync(mesh, "current")) {
+ if(!config_sync(mesh, "current") || !sync_path(mesh->confbase)) {
return false;
}
[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) {
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));
return false;
}
- if(!main_config_lock(mesh)) {
- logger(NULL, MESHLINK_ERROR, "Cannot lock main config file\n");
- meshlink_errno = MESHLINK_ESTORAGE;
- return false;
- }
-
return true;
}
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;
- }
-
config_t config;
if(!main_config_read(mesh, "current", &config, mesh->config_key)) {
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!");
devtool_keyrotate_probe(1);
- main_config_unlock(mesh);
-
// 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;
- main_config_lock(mesh);
pthread_mutex_unlock(&mesh->mutex);
return false;
}
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;
- main_config_lock(mesh);
pthread_mutex_unlock(&mesh->mutex);
return false;
}
devtool_keyrotate_probe(3);
- if(!main_config_lock(mesh)) {
- pthread_mutex_unlock(&mesh->mutex);
- return false;
- }
-
// Cleanup the "old" confbase sub-directory
if(!config_destroy(mesh->confbase, "old")) {
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));
if(usingname) {
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)) {
}
add_local_addresses(mesh);
- node_write_config(mesh, mesh->self);
+
+ if(!node_write_config(mesh, mesh->self)) {
+ logger(NULL, MESHLINK_ERROR, "Cannot update configuration\n");
+ return NULL;
+ }
idle_set(&mesh->loop, idle, mesh);
pthread_cond_wait(&mesh->cond, &mesh->mutex);
mesh->threadstarted = true;
+ mesh->self->last_reachable = time(NULL);
+ mesh->self->status.dirty = true;
pthread_mutex_unlock(&mesh->mutex);
return true;
pthread_mutex_lock(&mesh->mutex);
logger(mesh, MESHLINK_DEBUG, "meshlink_stop called\n");
+ if(mesh->self) {
+ mesh->self->last_unreachable = time(NULL);
+ mesh->self->status.dirty = true;
+ }
+
// Shut down the main thread
event_loop_stop(&mesh->loop);
exit_outgoings(mesh);
- // Write out any changed node config files
+ // 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) {
- node_write_config(mesh, n);
- n->status.dirty = false;
+ n->status.dirty = !node_write_config(mesh, n);
}
}
}
return false;
}
- if(!config_destroy(confbase, "current")) {
- logger(NULL, MESHLINK_ERROR, "Cannot remove confbase sub-directories %s: %s\n", confbase, strerror(errno));
+ /* 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;
}
- config_destroy(confbase, "new");
- config_destroy(confbase, "old");
+ fclose(lockfile);
- if(rmdir(confbase) && errno != ENOENT) {
- logger(NULL, MESHLINK_ERROR, "Cannot remove directory %s: %s\n", confbase, strerror(errno));
+ /* TODO: do we need to remove confbase? Potential race condition? */
+ if(!sync_path(confbase)) {
+ logger(NULL, MESHLINK_ERROR, "Cannot sync directory %s: %s\n", confbase, strerror(errno));
meshlink_errno = MESHLINK_ESTORAGE;
return false;
}
if(n->status.blacklisted) {
logger(mesh, MESHLINK_ERROR, "Node %s blacklisted, dropping packet\n", n->name);
+ meshlink_errno = MESHLINK_EBLACKLISTED;
return false;
}
return NULL;
}
- meshlink_node_t *node = NULL;
+ node_t *n = NULL;
pthread_mutex_lock(&mesh->mutex);
- node = (meshlink_node_t *)lookup_node(mesh, (char *)name); // TODO: make lookup_node() use const
+ n = lookup_node(mesh, (char *)name); // TODO: make lookup_node() use const
pthread_mutex_unlock(&mesh->mutex);
- if(!node) {
+ if(!n) {
meshlink_errno = MESHLINK_ENOENT;
}
- return node;
+ return (meshlink_node_t *)n;
}
meshlink_submesh_t *meshlink_get_submesh(meshlink_handle_t *mesh, const char *name) {
*nmemb = 0;
for splay_each(node_t, n, mesh->nodes) {
- if(true == search_node(n, condition)) {
- *nmemb = *nmemb + 1;
+ if(search_node(n, condition)) {
+ ++*nmemb;
}
}
meshlink_node_t **p = result;
for splay_each(node_t, n, mesh->nodes) {
- if(true == search_node(n, condition)) {
+ if(search_node(n, condition)) {
*p++ = (meshlink_node_t *)n;
}
}
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 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;
node_t *n = (node_t *)node;
free(n->canonical_address);
n->canonical_address = canonical_address;
- node_write_config(mesh, n);
+
+ if(!node_write_config(mesh, n)) {
+ pthread_mutex_unlock(&mesh->mutex);
+ return false;
+ }
pthread_mutex_unlock(&mesh->mutex);
- return true;
+ return config_sync(mesh, "current");
}
bool meshlink_add_address(meshlink_handle_t *mesh, const char *address) {
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)) {
uint32_t count = 0;
- for(uint32_t i = 0; i < 5; i++) {
+ for(uint32_t i = 0; i < MAX_RECENT; i++) {
if(mesh->self->recent[i].sa.sa_family) {
count++;
} else {
packmsg_add_sockaddr(&out, &mesh->self->recent[i]);
}
+ packmsg_add_int64(&out, 0);
+ packmsg_add_int64(&out, 0);
+
pthread_mutex_unlock(&mesh->mutex);
if(!packmsg_output_ok(&out)) {
break;
}
- config_write(mesh, "current", n->name, &config, mesh->config_key);
+ if(!config_write(mesh, "current", n->name, &config, mesh->config_key)) {
+ free_node(n);
+ return false;
+ }
+
node_add(mesh, n);
}
return true;
}
-void meshlink_blacklist(meshlink_handle_t *mesh, meshlink_node_t *node) {
- if(!mesh || !node) {
- meshlink_errno = MESHLINK_EINVAL;
- return;
- }
-
- pthread_mutex_lock(&mesh->mutex);
-
- node_t *n;
- n = (node_t *)node;
-
+static bool blacklist(meshlink_handle_t *mesh, node_t *n) {
if(n == mesh->self) {
- logger(mesh, MESHLINK_ERROR, "%s blacklisting itself?\n", node->name);
+ logger(mesh, MESHLINK_ERROR, "%s blacklisting itself?\n", n->name);
meshlink_errno = MESHLINK_EINVAL;
- pthread_mutex_unlock(&mesh->mutex);
- return;
+ return false;
}
if(n->status.blacklisted) {
- logger(mesh, MESHLINK_DEBUG, "Node %s already blacklisted\n", node->name);
- pthread_mutex_unlock(&mesh->mutex);
- return;
+ logger(mesh, MESHLINK_DEBUG, "Node %s already blacklisted\n", n->name);
+ return true;
}
n->status.blacklisted = true;
- node_write_config(mesh, n);
- config_sync(mesh, "current");
-
- logger(mesh, MESHLINK_DEBUG, "Blacklisted %s.\n", node->name);
- //Immediately terminate any connections we have with the blacklisted node
+ /* Immediately shut down any connections we have with the blacklisted node.
+ * We can't call terminate_connection(), because we might be called from a callback function.
+ */
for list_each(connection_t, c, mesh->connections) {
if(c->node == n) {
- terminate_connection(mesh, c, c->status.active);
+ shutdown(c->socket, SHUT_RDWR);
}
}
n->mtuprobes = 0;
n->status.udp_confirmed = false;
- if(n->status.reachable) {
- update_node_status(mesh, n);
+ /* Graph updates will suppress status updates for blacklisted nodes, so we need to
+ * manually call the status callback if necessary.
+ */
+ if(n->status.reachable && mesh->node_status_cb) {
+ mesh->node_status_cb(mesh, (meshlink_node_t *)n, false);
}
- pthread_mutex_unlock(&mesh->mutex);
+ return node_write_config(mesh, n) && config_sync(mesh, "current");
}
-void meshlink_whitelist(meshlink_handle_t *mesh, meshlink_node_t *node) {
+bool meshlink_blacklist(meshlink_handle_t *mesh, meshlink_node_t *node) {
if(!mesh || !node) {
meshlink_errno = MESHLINK_EINVAL;
- return;
+ return false;
}
pthread_mutex_lock(&mesh->mutex);
- node_t *n = (node_t *)node;
+ if(!blacklist(mesh, (node_t *)node)) {
+ pthread_mutex_unlock(&mesh->mutex);
+ return false;
+ }
- if(!n->status.blacklisted) {
- logger(mesh, MESHLINK_DEBUG, "Node %s was already whitelisted\n", node->name);
+ pthread_mutex_unlock(&mesh->mutex);
+
+ logger(mesh, MESHLINK_DEBUG, "Blacklisted %s.\n", node->name);
+ return true;
+}
+
+bool meshlink_blacklist_by_name(meshlink_handle_t *mesh, const char *name) {
+ if(!mesh || !name) {
meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ pthread_mutex_lock(&mesh->mutex);
+
+ node_t *n = lookup_node(mesh, (char *)name);
+
+ if(!n) {
+ n = new_node();
+ n->name = xstrdup(name);
+ node_add(mesh, n);
+ }
+
+ if(!blacklist(mesh, (node_t *)n)) {
pthread_mutex_unlock(&mesh->mutex);
- return;
+ return false;
+ }
+
+ pthread_mutex_unlock(&mesh->mutex);
+
+ logger(mesh, MESHLINK_DEBUG, "Blacklisted %s.\n", name);
+ return true;
+}
+
+static bool whitelist(meshlink_handle_t *mesh, node_t *n) {
+ if(n == mesh->self) {
+ logger(mesh, MESHLINK_ERROR, "%s whitelisting itself?\n", n->name);
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ if(!n->status.blacklisted) {
+ logger(mesh, MESHLINK_DEBUG, "Node %s was already whitelisted\n", n->name);
+ return true;
}
n->status.blacklisted = false;
- node_write_config(mesh, n);
- config_sync(mesh, "current");
if(n->status.reachable) {
update_node_status(mesh, n);
}
+ return node_write_config(mesh, n) && config_sync(mesh, "current");
+}
+
+bool meshlink_whitelist(meshlink_handle_t *mesh, meshlink_node_t *node) {
+ if(!mesh || !node) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ pthread_mutex_lock(&mesh->mutex);
+
+ if(!whitelist(mesh, (node_t *)node)) {
+ pthread_mutex_unlock(&mesh->mutex);
+ return false;
+ }
+
+ pthread_mutex_unlock(&mesh->mutex);
+
+ logger(mesh, MESHLINK_DEBUG, "Whitelisted %s.\n", node->name);
+ return true;
+}
+
+bool meshlink_whitelist_by_name(meshlink_handle_t *mesh, const char *name) {
+ if(!mesh || !name) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ pthread_mutex_lock(&mesh->mutex);
+
+ node_t *n = lookup_node(mesh, (char *)name);
+
+ if(!n) {
+ n = new_node();
+ n->name = xstrdup(name);
+ node_add(mesh, n);
+ }
+
+ if(!whitelist(mesh, (node_t *)n)) {
+ pthread_mutex_unlock(&mesh->mutex);
+ return false;
+ }
+
pthread_mutex_unlock(&mesh->mutex);
- return;
+
+ logger(mesh, MESHLINK_DEBUG, "Whitelisted %s.\n", name);
+ return true;
}
void meshlink_set_default_blacklist(meshlink_handle_t *mesh, bool blacklist) {
mesh->default_blacklist = blacklist;
}
+bool meshlink_forget_node(meshlink_handle_t *mesh, meshlink_node_t *node) {
+ if(!mesh || !node) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ node_t *n = (node_t *)node;
+
+ pthread_mutex_lock(&mesh->mutex);
+
+ /* Check that the node is not reachable */
+ if(n->status.reachable || n->connection) {
+ pthread_mutex_unlock(&mesh->mutex);
+ logger(mesh, MESHLINK_WARNING, "Could not forget %s: still reachable", n->name);
+ return false;
+ }
+
+ /* Check that we don't have any active UTCP connections */
+ if(n->utcp && utcp_is_active(n->utcp)) {
+ pthread_mutex_unlock(&mesh->mutex);
+ logger(mesh, MESHLINK_WARNING, "Could not forget %s: active UTCP connections", n->name);
+ return false;
+ }
+
+ /* Check that we have no active connections to this node */
+ for list_each(connection_t, c, mesh->connections) {
+ if(c->node == n) {
+ pthread_mutex_unlock(&mesh->mutex);
+ logger(mesh, MESHLINK_WARNING, "Could not forget %s: active connection", n->name);
+ return false;
+ }
+ }
+
+ /* Remove any pending outgoings to this node */
+ if(mesh->outgoings) {
+ for list_each(outgoing_t, outgoing, mesh->outgoings) {
+ if(outgoing->node == n) {
+ list_delete_node(mesh->outgoings, node);
+ }
+ }
+ }
+
+ /* Delete the config file for this node */
+ if(!config_delete(mesh, "current", n->name)) {
+ pthread_mutex_unlock(&mesh->mutex);
+ return false;
+ }
+
+ /* Delete the node struct and any remaining edges referencing this node */
+ node_del(mesh, n);
+
+ pthread_mutex_unlock(&mesh->mutex);
+
+ return config_sync(mesh, "current");
+}
+
/* Hint that a hostname may be found at an address
* See header file for detailed comment.
*/
pthread_mutex_lock(&mesh->mutex);
node_t *n = (node_t *)node;
- memmove(n->recent + 1, n->recent, 4 * sizeof(*n->recent));
+ memmove(n->recent + 1, n->recent, (MAX_RECENT - 1) * sizeof(*n->recent));
memcpy(n->recent, addr, SALEN(*addr));
- node_write_config(mesh, n);
+
+ if(!node_write_config(mesh, n)) {
+ logger(mesh, MESHLINK_DEBUG, "Could not update %s\n", n->name);
+ }
pthread_mutex_unlock(&mesh->mutex);
// @TODO do we want to fire off a connection attempt right away?
}
meshlink_channel_t *meshlink_channel_open_ex(meshlink_handle_t *mesh, meshlink_node_t *node, uint16_t port, meshlink_channel_receive_cb_t cb, const void *data, size_t len, uint32_t flags) {
- if(data || len) {
+ if(data && len) {
abort(); // TODO: handle non-NULL data
}
if(n->status.blacklisted) {
logger(mesh, MESHLINK_ERROR, "Cannot open a channel with blacklisted node\n");
+ meshlink_errno = MESHLINK_EBLACKLISTED;
pthread_mutex_unlock(&mesh->mutex);
return NULL;
}
meshlink_channel_t *channel = xzalloc(sizeof(*channel));
channel->node = n;
channel->receive_cb = cb;
+
+ if(data && !len) {
+ channel->priv = (void *)data;
+ }
+
channel->c = utcp_connect_ex(n->utcp, port, channel_recv, channel, flags);
pthread_mutex_unlock(&mesh->mutex);