+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;
+
+ 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);
+
+ 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.
+ */
+void meshlink_hint_address(meshlink_handle_t *mesh, meshlink_node_t *node, const struct sockaddr *addr) {
+ if(!mesh || !node || !addr) {
+ meshlink_errno = EINVAL;
+ return;
+ }
+
+ pthread_mutex_lock(&mesh->mutex);
+
+ node_t *n = (node_t *)node;
+
+ if(node_add_recent_address(mesh, n, (sockaddr_t *)addr)) {
+ 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?
+}
+
+static bool channel_pre_accept(struct utcp *utcp, uint16_t port) {