return true;
}
+/// Delete a host configuration file.
+bool config_delete(meshlink_handle_t *mesh, const char *conf_subdir, const char *name) {
+ assert(conf_subdir);
+ assert(name);
+
+ if(!mesh->confbase) {
+ return true;
+ }
+
+ char path[PATH_MAX];
+ make_host_path(mesh, conf_subdir, name, path, sizeof(path));
+
+ if(unlink(path) && errno != ENOENT) {
+ logger(mesh, MESHLINK_ERROR, "Failed to unlink `%s': %s", path, strerror(errno));
+ meshlink_errno = MESHLINK_ESTORAGE;
+ return false;
+ }
+
+ return true;
+}
+
/// Read the main configuration file.
bool main_config_read(meshlink_handle_t *mesh, const char *conf_subdir, config_t *config, void *key) {
assert(conf_subdir);
extern bool config_exists(struct meshlink_handle *mesh, const char *conf_subdir, const char *name) __attribute__((__warn_unused_result__));
extern bool config_read(struct meshlink_handle *mesh, const char *conf_subdir, const char *name, struct config_t *, void *key) __attribute__((__warn_unused_result__));
extern bool config_write(struct meshlink_handle *mesh, const char *conf_subdir, const char *name, const struct config_t *, void *key) __attribute__((__warn_unused_result__));
+extern bool config_delete(struct meshlink_handle *mesh, const char *conf_subdir, const char *name) __attribute__((__warn_unused_result__));
extern bool config_scan_all(struct meshlink_handle *mesh, const char *conf_subdir, const char *conf_type, config_scan_action_t action, void *arg) __attribute__((__warn_unused_result__));
extern bool invitation_read(struct meshlink_handle *mesh, const char *conf_subdir, const char *name, struct config_t *, void *key) __attribute__((__warn_unused_result__));
return meshlink_import(handle, data);
}
+ /// Forget any information about a node.
+ /** This function allows the local node to forget any information it has about a node,
+ * and if possible will remove any data it has stored on disk about the node.
+ *
+ * Any open channels to this node must be closed before calling this function.
+ *
+ * After this call returns, the node handle is invalid and may no longer be used, regardless
+ * of the return value of this call.
+ *
+ * Note that this function does not prevent MeshLink from actually forgetting about a node,
+ * or re-learning information about a node at a later point in time. It is merely a hint that
+ * the application does not care about this node anymore and that any resources kept could be
+ * cleaned up.
+ *
+ * \memberof meshlink_node
+ * @param node A pointer to a struct meshlink_node describing the node to be forgotten.
+ *
+ * @return This function returns true if all currently known data about the node has been forgotten, false otherwise.
+ */
+ bool forget_node(node *node) {
+ return meshlink_forget_node(handle, node);
+ }
+
/// Blacklist a node from the mesh.
/** This function causes the local node to blacklist another node.
* The local node will drop any existing connections to that node,
return meshlink_blacklist(handle, node);
}
+ /// Blacklist a node from the mesh by name.
+ /** This function causes the local node to blacklist another node by name.
+ * The local node will drop any existing connections to that node,
+ * and will not send data to it nor accept any data received from it any more.
+ *
+ * If no node by the given name is known, it is created.
+ *
+ * @param name The name of the node to blacklist.
+ *
+ * @return This function returns true if the node has been blacklisted, false otherwise.
+ */
+ bool blacklist_by_name(const char *name) {
+ return meshlink_blacklist_by_name(handle, name);
+ }
+
/// Whitelist a node on the mesh.
/** This function causes the local node to whitelist another node.
* The local node will allow connections to and from that node,
return meshlink_whitelist(handle, node);
}
+ /// Whitelist a node on the mesh by name.
+ /** This function causes the local node to whitelist a node by name.
+ * The local node will allow connections to and from that node,
+ * and will send data to it and accept any data received from it.
+ *
+ * If no node by the given name is known, it is created.
+ * This is useful if new nodes are blacklisted by default.
+ *
+ * \memberof meshlink_node
+ * @param node A pointer to a struct meshlink_node describing the node to be whitelisted.
+ *
+ * @return This function returns true if the node has been whitelisted, false otherwise.
+ */
+ bool whitelist_by_name(const char *name) {
+ return meshlink_whitelist_by_name(handle, name);
+ }
+
/// Set the poll callback.
/** This functions sets the callback that is called whenever data can be sent to another node.
* The callback is run in MeshLink's own thread.
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 true;
}
-bool meshlink_blacklist(meshlink_handle_t *mesh, meshlink_node_t *node) {
- if(!mesh || !node) {
- meshlink_errno = MESHLINK_EINVAL;
- return false;
- }
-
- 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 false;
}
if(n->status.blacklisted) {
- logger(mesh, MESHLINK_DEBUG, "Node %s already blacklisted\n", node->name);
- pthread_mutex_unlock(&mesh->mutex);
+ logger(mesh, MESHLINK_DEBUG, "Node %s already blacklisted\n", n->name);
return true;
}
n->mtuprobes = 0;
n->status.udp_confirmed = false;
- if(!node_write_config(mesh, n)) {
- pthread_mutex_unlock(&mesh->mutex);
+ /* 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);
+ }
+
+ return node_write_config(mesh, n) && config_sync(mesh, "current");
+}
+
+bool meshlink_blacklist(meshlink_handle_t *mesh, meshlink_node_t *node) {
+ if(!mesh || !node) {
+ meshlink_errno = MESHLINK_EINVAL;
return false;
}
- logger(mesh, MESHLINK_DEBUG, "Blacklisted %s.\n", node->name);
+ pthread_mutex_lock(&mesh->mutex);
+
+ if(!blacklist(mesh, (node_t *)node)) {
+ pthread_mutex_unlock(&mesh->mutex);
+ return false;
+ }
pthread_mutex_unlock(&mesh->mutex);
- return config_sync(mesh, "current");
+ logger(mesh, MESHLINK_DEBUG, "Blacklisted %s.\n", node->name);
+ return true;
}
-bool meshlink_whitelist(meshlink_handle_t *mesh, meshlink_node_t *node) {
- if(!mesh || !node) {
+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 = (node_t *)node;
+ 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 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", node->name);
+ logger(mesh, MESHLINK_ERROR, "%s whitelisting itself?\n", n->name);
meshlink_errno = MESHLINK_EINVAL;
- 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, "Node %s was already whitelisted\n", n->name);
return true;
}
update_node_status(mesh, n);
}
- if(!node_write_config(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 config_sync(mesh, "current");
+ 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.
*/
*/
extern bool meshlink_import(struct meshlink_handle *mesh, const char *data) __attribute__((__warn_unused_result__));
+/// Forget any information about a node.
+/** This function allows the local node to forget any information it has about a node,
+ * and if possible will remove any data it has stored on disk about the node.
+ *
+ * Any open channels to this node must be closed before calling this function.
+ *
+ * After this call returns, the node handle is invalid and may no longer be used, regardless
+ * of the return value of this call.
+ *
+ * Note that this function does not prevent MeshLink from actually forgetting about a node,
+ * or re-learning information about a node at a later point in time. It is merely a hint that
+ * the application does not care about this node anymore and that any resources kept could be
+ * cleaned up.
+ *
+ * \memberof meshlink_node
+ * @param mesh A handle which represents an instance of MeshLink.
+ * @param node A pointer to a struct meshlink_node describing the node to be forgotten.
+ *
+ * @return This function returns true if all currently known data about the node has been forgotten, false otherwise.
+ */
+extern bool meshlink_forget_node(struct meshlink_handle *mesh, struct meshlink_node *node);
+
/// Blacklist a node from the mesh.
/** This function causes the local node to blacklist another node.
* The local node will drop any existing connections to that node,
*/
extern bool meshlink_blacklist(struct meshlink_handle *mesh, struct meshlink_node *node) __attribute__((__warn_unused_result__));
+/// Blacklist a node from the mesh by name.
+/** This function causes the local node to blacklist another node by name.
+ * The local node will drop any existing connections to that node,
+ * and will not send data to it nor accept any data received from it any more.
+ *
+ * If no node by the given name is known, it is created.
+ *
+ * \memberof meshlink_node
+ * @param mesh A handle which represents an instance of MeshLink.
+ * @param name The name of the node to blacklist.
+ *
+ * @return This function returns true if the node has been blacklisted, false otherwise.
+ */
+extern bool meshlink_blacklist_by_name(struct meshlink_handle *mesh, const char *name) __attribute__((__warn_unused_result__));
+
/// Whitelist a node on the mesh.
/** This function causes the local node to whitelist a previously blacklisted node.
* The local node will allow connections to and from that node,
*/
extern bool meshlink_whitelist(struct meshlink_handle *mesh, struct meshlink_node *node) __attribute__((__warn_unused_result__));
+/// Whitelist a node on the mesh by name.
+/** This function causes the local node to whitelist a node by name.
+ * The local node will allow connections to and from that node,
+ * and will send data to it and accept any data received from it.
+ *
+ * If no node by the given name is known, it is created.
+ * This is useful if new nodes are blacklisted by default.
+ *
+ * \memberof meshlink_node
+ * @param mesh A handle which represents an instance of MeshLink.
+ * @param node A pointer to a struct meshlink_node describing the node to be whitelisted.
+ *
+ * @return This function returns true if the node has been whitelisted, false otherwise.
+ */
+extern bool meshlink_whitelist_by_name(struct meshlink_handle *mesh, const char *name) __attribute__((__warn_unused_result__));
+
/// Set whether new nodes are blacklisted by default.
/** This function sets the blacklist behaviour for newly discovered nodes.
* If set to true, new nodes will be automatically blacklisted.
meshlink_add_address
meshlink_add_external_address
meshlink_blacklist
+meshlink_blacklist_by_name
meshlink_channel_aio_fd_receive
meshlink_channel_aio_fd_send
meshlink_channel_aio_receive
meshlink_encrypted_key_rotate
meshlink_errno
meshlink_export
+meshlink_forget_node
meshlink_get_all_nodes
meshlink_get_all_nodes_by_dev_class
meshlink_get_all_nodes_by_submesh
meshlink_submesh_open
meshlink_verify
meshlink_whitelist
+meshlink_whitelist_by_name
TESTS = \
basic \
basicpp \
+ blacklist \
channels \
channels-aio \
channels-aio-fd \
check_PROGRAMS = \
basic \
basicpp \
+ blacklist \
channels \
channels-aio \
channels-aio-fd \
basicpp_SOURCES = basicpp.cpp utils.c utils.h
basicpp_LDADD = ../src/libmeshlink.la
+blacklist_SOURCES = blacklist.c utils.c utils.h
+blacklist_LDADD = ../src/libmeshlink.la
+
channels_SOURCES = channels.c utils.c utils.h
channels_LDADD = ../src/libmeshlink.la
--- /dev/null
+#define _GNU_SOURCE
+
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/time.h>
+
+#include "meshlink.h"
+#include "devtools.h"
+#include "utils.h"
+
+static struct sync_flag bar_connected;
+static struct sync_flag bar_disconnected;
+static struct sync_flag baz_connected;
+
+static void foo_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) {
+ (void)mesh;
+ (void)reachable;
+
+ if(!strcmp(node->name, "bar")) {
+ if(reachable) {
+ set_sync_flag(&bar_connected, true);
+ } else {
+ set_sync_flag(&bar_disconnected, true);
+ }
+ }
+}
+
+static void baz_status_cb(meshlink_handle_t *mesh, meshlink_node_t *node, bool reachable) {
+ (void)mesh;
+ (void)reachable;
+
+ if(!strcmp(node->name, "bar")) {
+ if(reachable) {
+ set_sync_flag(&baz_connected, true);
+ }
+ }
+}
+
+int main() {
+ meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
+
+ // Create three instances.
+
+ const char *name[3] = {"foo", "bar", "baz"};
+ meshlink_handle_t *mesh[3];
+ char *data[3];
+
+ for(int i = 0; i < 3; i++) {
+ char *path = NULL;
+ assert(asprintf(&path, "blacklist_conf.%d", i) != -1 && path);
+
+ assert(meshlink_destroy(path));
+ mesh[i] = meshlink_open(path, name[i], "trio", DEV_CLASS_BACKBONE);
+ assert(mesh[i]);
+ free(path);
+
+ assert(meshlink_add_address(mesh[i], "localhost"));
+
+ data[i] = meshlink_export(mesh[i]);
+ assert(data[i]);
+
+ // Enable default blacklist on all nodes.
+ meshlink_set_default_blacklist(mesh[i], true);
+ }
+
+ // The first node knows the two other nodes.
+
+ for(int i = 1; i < 3; i++) {
+ assert(meshlink_import(mesh[i], data[0]));
+ assert(meshlink_import(mesh[0], data[i]));
+
+ assert(meshlink_get_node(mesh[i], name[0]));
+ assert(meshlink_get_node(mesh[0], name[i]));
+
+ }
+
+ for(int i = 0; i < 3; i++) {
+ free(data[i]);
+ }
+
+ // Second and third node should not know each other yet.
+
+ assert(!meshlink_get_node(mesh[1], name[2]));
+ assert(!meshlink_get_node(mesh[2], name[1]));
+
+ // Whitelisting and blacklisting by name should work.
+
+ assert(meshlink_whitelist_by_name(mesh[0], "quux"));
+ assert(meshlink_blacklist_by_name(mesh[0], "xyzzy"));
+
+ // Since these nodes now exist we should be able to forget them.
+
+ assert(meshlink_forget_node(mesh[0], meshlink_get_node(mesh[0], "quux")));
+
+ // Start the nodes.
+
+ meshlink_set_node_status_cb(mesh[0], foo_status_cb);
+ meshlink_set_node_status_cb(mesh[2], baz_status_cb);
+
+ for(int i = 0; i < 3; i++) {
+ assert(meshlink_start(mesh[i]));
+ }
+
+ // Wait for them to connect.
+
+ assert(wait_sync_flag(&bar_connected, 5));
+
+ // Blacklist bar
+
+ set_sync_flag(&bar_disconnected, false);
+ assert(meshlink_blacklist(mesh[0], meshlink_get_node(mesh[0], name[1])));
+ assert(wait_sync_flag(&bar_disconnected, 5));
+
+ // Whitelist bar
+
+ set_sync_flag(&bar_connected, false);
+ assert(meshlink_whitelist(mesh[0], meshlink_get_node(mesh[0], name[1])));
+ assert(wait_sync_flag(&bar_connected, 15));
+
+ // Bar should not connect to baz
+
+ assert(wait_sync_flag(&baz_connected, 5) == false);
+
+ // But it should know about baz by now
+
+ meshlink_node_t *bar = meshlink_get_node(mesh[2], "bar");
+ meshlink_node_t *baz = meshlink_get_node(mesh[1], "baz");
+ assert(bar);
+ assert(baz);
+
+ // Have bar and baz whitelist each other
+
+ assert(meshlink_whitelist(mesh[1], baz));
+ assert(meshlink_whitelist(mesh[2], bar));
+
+ // They should connect to each other
+
+ assert(wait_sync_flag(&baz_connected, 15));
+
+ // Trying to forget an active node should fail.
+
+ assert(!meshlink_forget_node(mesh[1], baz));
+
+ // We need to re-acquire the handle to baz
+
+ baz = meshlink_get_node(mesh[1], "baz");
+ assert(baz);
+
+ // Stop the mesh.
+
+ for(int i = 0; i < 3; i++) {
+ meshlink_stop(mesh[i]);
+ }
+
+ // Forgetting a node should work now.
+
+ assert(meshlink_forget_node(mesh[1], baz));
+
+ // Clean up.
+
+ for(int i = 0; i < 3; i++) {
+ meshlink_close(mesh[i]);
+ }
+
+ // Check that foo has a config file for xyzzy but not quux
+ assert(access("blacklist_conf.0/current/hosts/xyzzy", F_OK) == 0);
+ assert(access("blacklist_conf.0/current/hosts/quux", F_OK) != 0 && errno == ENOENT);
+
+ // Check that bar has no config file for baz
+ assert(access("blacklist_conf.2/current/hosts/bar", F_OK) == 0);
+ assert(access("blacklist_conf.1/current/hosts/baz", F_OK) != 0 && errno == ENOENT);
+}