]> git.meshlink.io Git - meshlink-tiny/blobdiff - src/meshlink.c
Add a metering test.
[meshlink-tiny] / src / meshlink.c
index 59776da25a36382b481995a68a2b9c9aa4c40adf..aadbaf337cca4798500544ed19c67aef67fa1bcd 100644 (file)
 #include "net.h"
 #include "netutl.h"
 #include "node.h"
-#include "submesh.h"
 #include "packmsg.h"
 #include "prf.h"
 #include "protocol.h"
-#include "route.h"
 #include "sockaddr.h"
 #include "utils.h"
 #include "xalloc.h"
@@ -47,6 +45,8 @@ meshlink_log_level_t global_log_level;
 
 typedef bool (*search_node_by_condition_t)(const node_t *, const void *);
 
+#define BUFSIZE 128
+
 static int rstrip(char *value) {
        int len = strlen(value);
 
@@ -154,7 +154,7 @@ static bool write_main_config_files(meshlink_handle_t *mesh) {
                return true;
        }
 
-       uint8_t buf[4096];
+       uint8_t buf[BUFSIZE];
 
        /* Write the main config file */
        packmsg_output_t out = {buf, sizeof buf};
@@ -193,8 +193,8 @@ typedef struct {
        char *data;
        size_t thedatalen;
        size_t blen;
-       char line[4096];
-       char buffer[4096];
+       char line[BUFSIZE];
+       char buffer[BUFSIZE];
 } join_state_t;
 
 static bool finalize_join(join_state_t *state, const void *buf, uint16_t len) {
@@ -208,28 +208,19 @@ static bool finalize_join(join_state_t *state, const void *buf, uint16_t len) {
        }
 
        char *name = packmsg_get_str_dup(&in);
-       char *submesh_name = packmsg_get_str_dup(&in);
+       packmsg_skip_element(&in); // submesh_name
        dev_class_t devclass = packmsg_get_int32(&in);
        uint32_t count = packmsg_get_array(&in);
 
        if(!name || !check_id(name)) {
                logger(mesh, MESHLINK_DEBUG, "No valid Name found in invitation!\n");
                free(name);
-               free(submesh_name);
-               return false;
-       }
-
-       if(!submesh_name || (strcmp(submesh_name, CORE_MESH) && !check_id(submesh_name))) {
-               logger(mesh, MESHLINK_DEBUG, "No valid Submesh found in invitation!\n");
-               free(name);
-               free(submesh_name);
                return false;
        }
 
        if(!count) {
                logger(mesh, MESHLINK_ERROR, "Incomplete invitation file!\n");
                free(name);
-               free(submesh_name);
                return false;
        }
 
@@ -237,8 +228,6 @@ static bool finalize_join(join_state_t *state, const void *buf, uint16_t len) {
        free(mesh->self->name);
        mesh->name = name;
        mesh->self->name = xstrdup(name);
-       mesh->self->submesh = strcmp(submesh_name, CORE_MESH) ? lookup_or_create_submesh(mesh, submesh_name) : NULL;
-       free(submesh_name);
        mesh->self->devclass = devclass == DEV_CLASS_UNKNOWN ? mesh->devclass : devclass;
 
        // Initialize configuration directory
@@ -325,7 +314,9 @@ static bool finalize_join(join_state_t *state, const void *buf, uint16_t len) {
 
        sptps_send_record(&state->sptps, 1, ecdsa_get_public_key(mesh->private_key), 32);
 
-       logger(mesh, MESHLINK_DEBUG, "Configuration stored in: %s\n", mesh->confbase);
+       if(mesh->confbase) {
+               logger(mesh, MESHLINK_DEBUG, "Configuration stored in: %s\n", mesh->confbase);
+       }
 
        return true;
 }
@@ -428,7 +419,7 @@ static bool recvline(join_state_t *state) {
 }
 
 static bool sendline(int fd, const char *format, ...) {
-       char buffer[4096];
+       char buffer[BUFSIZE];
        char *p = buffer;
        int blen = 0;
        va_list ap;
@@ -500,32 +491,13 @@ static bool ecdsa_keygen(meshlink_handle_t *mesh) {
        return true;
 }
 
-static bool timespec_lt(const struct timespec *a, const struct timespec *b) {
-       if(a->tv_sec == b->tv_sec) {
-               return a->tv_nsec < b->tv_nsec;
-       } else {
-               return a->tv_sec < b->tv_sec;
-       }
-}
-
 static struct timespec idle(event_loop_t *loop, void *data) {
        (void)loop;
-       meshlink_handle_t *mesh = data;
-       struct timespec t, tmin = {3600, 0};
-
-       for splay_each(node_t, n, mesh->nodes) {
-               if(!n->utcp) {
-                       continue;
-               }
-
-               t = utcp_timeout(n->utcp);
+       (void)data;
 
-               if(timespec_lt(&t, &tmin)) {
-                       tmin = t;
-               }
-       }
-
-       return tmin;
+       return (struct timespec) {
+               3600, 0
+       };
 }
 
 static bool meshlink_setup(meshlink_handle_t *mesh) {
@@ -990,7 +962,6 @@ meshlink_handle_t *meshlink_open_ex(const meshlink_open_params_t *params) {
        mesh->appname = xstrdup(params->appname);
        mesh->devclass = params->devclass;
        mesh->netns = params->netns;
-       mesh->submeshes = NULL;
        mesh->log_cb = global_log_cb;
        mesh->log_level = global_log_level;
        mesh->packet = xmalloc(sizeof(vpn_packet_t));
@@ -1114,35 +1085,6 @@ meshlink_handle_t *meshlink_open_ex(const meshlink_open_params_t *params) {
        return mesh;
 }
 
-meshlink_submesh_t *meshlink_submesh_open(meshlink_handle_t *mesh, const char *submesh) {
-       logger(NULL, MESHLINK_DEBUG, "meshlink_submesh_open(%s)", submesh);
-
-       meshlink_submesh_t *s = NULL;
-
-       if(!mesh) {
-               logger(NULL, MESHLINK_ERROR, "No mesh handle given!\n");
-               meshlink_errno = MESHLINK_EINVAL;
-               return NULL;
-       }
-
-       if(!submesh || !*submesh) {
-               logger(NULL, MESHLINK_ERROR, "No submesh name given!\n");
-               meshlink_errno = MESHLINK_EINVAL;
-               return NULL;
-       }
-
-       //lock mesh->nodes
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       s = (meshlink_submesh_t *)create_submesh(mesh, submesh);
-
-       pthread_mutex_unlock(&mesh->mutex);
-
-       return s;
-}
-
 static void *meshlink_main_loop(void *arg) {
        meshlink_handle_t *mesh = arg;
 
@@ -1198,11 +1140,10 @@ bool meshlink_start(meshlink_handle_t *mesh) {
        }
 
        // Reset node connection timers
-       for splay_each(node_t, n, mesh->nodes) {
-               n->last_connect_try = 0;
+       if(mesh->peer) {
+               mesh->peer->last_connect_try = 0;
        }
 
-
        //Check that a valid name is set
        if(!mesh->name) {
                logger(mesh, MESHLINK_ERROR, "No name given!\n");
@@ -1271,25 +1212,17 @@ void meshlink_stop(meshlink_handle_t *mesh) {
        }
 
        // Close all metaconnections
-       if(mesh->connections) {
-               for(list_node_t *node = mesh->connections->head, *next; node; node = next) {
-                       next = node->next;
-                       connection_t *c = node->data;
-                       c->outgoing = NULL;
-                       terminate_connection(mesh, c, false);
-               }
+       if(mesh->connection) {
+               mesh->connection->outgoing = NULL;
+               terminate_connection(mesh, mesh->connection, false);
        }
 
        exit_outgoings(mesh);
 
        // 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) {
-                               if(!node_write_config(mesh, n, false)) {
-                                       // ignore
-                               }
-                       }
+       if(mesh->peer && mesh->peer->status.dirty) {
+               if(!node_write_config(mesh, mesh->peer, false)) {
+                       // ignore
                }
        }
 
@@ -1485,22 +1418,6 @@ void meshlink_set_node_status_cb(meshlink_handle_t *mesh, meshlink_node_status_c
        pthread_mutex_unlock(&mesh->mutex);
 }
 
-void meshlink_set_node_pmtu_cb(meshlink_handle_t *mesh, meshlink_node_pmtu_cb_t cb) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_set_node_pmtu_cb(%p)", (void *)(intptr_t)cb);
-
-       if(!mesh) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return;
-       }
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       mesh->node_pmtu_cb = cb;
-       pthread_mutex_unlock(&mesh->mutex);
-}
-
 void meshlink_set_node_duplicate_cb(meshlink_handle_t *mesh, meshlink_node_duplicate_cb_t cb) {
        logger(mesh, MESHLINK_DEBUG, "meshlink_set_node_duplicate_cb(%p)", (void *)(intptr_t)cb);
 
@@ -1550,56 +1467,6 @@ void meshlink_set_error_cb(struct meshlink_handle *mesh, meshlink_error_cb_t cb)
        pthread_mutex_unlock(&mesh->mutex);
 }
 
-static bool prepare_packet(meshlink_handle_t *mesh, meshlink_node_t *destination, const void *data, size_t len, vpn_packet_t *packet) {
-       meshlink_packethdr_t *hdr;
-
-       if(len > MAXSIZE - sizeof(*hdr)) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return false;
-       }
-
-       node_t *n = (node_t *)destination;
-
-       if(n->status.blacklisted) {
-               logger(mesh, MESHLINK_ERROR, "Node %s blacklisted, dropping packet\n", n->name);
-               meshlink_errno = MESHLINK_EBLACKLISTED;
-               return false;
-       }
-
-       // Prepare the packet
-       packet->probe = false;
-       packet->tcp = false;
-       packet->len = len + sizeof(*hdr);
-
-       hdr = (meshlink_packethdr_t *)packet->data;
-       memset(hdr, 0, sizeof(*hdr));
-       // leave the last byte as 0 to make sure strings are always
-       // null-terminated if they are longer than the buffer
-       strncpy((char *)hdr->destination, destination->name, sizeof(hdr->destination) - 1);
-       strncpy((char *)hdr->source, mesh->self->name, sizeof(hdr->source) - 1);
-
-       memcpy(packet->data + sizeof(*hdr), data, len);
-
-       return true;
-}
-
-static bool meshlink_send_immediate(meshlink_handle_t *mesh, meshlink_node_t *destination, const void *data, size_t len) {
-       assert(mesh);
-       assert(destination);
-       assert(data);
-       assert(len);
-
-       // Prepare the packet
-       if(!prepare_packet(mesh, destination, data, len, mesh->packet)) {
-               return false;
-       }
-
-       // Send it immediately
-       route(mesh, mesh->self, mesh->packet);
-
-       return true;
-}
-
 bool meshlink_send(meshlink_handle_t *mesh, meshlink_node_t *destination, const void *data, size_t len) {
        logger(mesh, MESHLINK_DEBUG, "meshlink_send(%s, %p, %zu)", destination ? destination->name : "(null)", data, len);
 
@@ -1613,7 +1480,7 @@ bool meshlink_send(meshlink_handle_t *mesh, meshlink_node_t *destination, const
                return true;
        }
 
-       if(!data) {
+       if(!data || len > MTU) {
                meshlink_errno = MESHLINK_EINVAL;
                return false;
        }
@@ -1626,10 +1493,8 @@ bool meshlink_send(meshlink_handle_t *mesh, meshlink_node_t *destination, const
                return false;
        }
 
-       if(!prepare_packet(mesh, destination, data, len, packet)) {
-               free(packet);
-               return false;
-       }
+       packet->len = len;
+       memcpy(packet->data, data, len);
 
        // Queue it
        if(!meshlink_queue_push(&mesh->outpacketqueue, packet)) {
@@ -1654,38 +1519,11 @@ void meshlink_send_from_queue(event_loop_t *loop, void *data) {
 
        for(vpn_packet_t *packet; (packet = meshlink_queue_pop(&mesh->outpacketqueue));) {
                logger(mesh, MESHLINK_DEBUG, "Removing packet of %d bytes from packet queue", packet->len);
-               mesh->self->in_packets++;
-               mesh->self->in_bytes += packet->len;
-               route(mesh, mesh->self, packet);
+               send_raw_packet(mesh, mesh->peer->connection, packet);
                free(packet);
        }
 }
 
-ssize_t meshlink_get_pmtu(meshlink_handle_t *mesh, meshlink_node_t *destination) {
-       if(!mesh || !destination) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return -1;
-       }
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       node_t *n = (node_t *)destination;
-
-       if(!n->status.reachable) {
-               pthread_mutex_unlock(&mesh->mutex);
-               return 0;
-
-       } else if(n->mtuprobes > 30 && n->minmtu) {
-               pthread_mutex_unlock(&mesh->mutex);
-               return n->minmtu;
-       } else {
-               pthread_mutex_unlock(&mesh->mutex);
-               return MTU;
-       }
-}
-
 char *meshlink_get_fingerprint(meshlink_handle_t *mesh, meshlink_node_t *node) {
        if(!mesh || !node) {
                meshlink_errno = MESHLINK_EINVAL;
@@ -1745,202 +1583,6 @@ meshlink_node_t *meshlink_get_node(meshlink_handle_t *mesh, const char *name) {
        return (meshlink_node_t *)n;
 }
 
-meshlink_submesh_t *meshlink_get_submesh(meshlink_handle_t *mesh, const char *name) {
-       if(!mesh || !name) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return NULL;
-       }
-
-       meshlink_submesh_t *submesh = NULL;
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       submesh = (meshlink_submesh_t *)lookup_submesh(mesh, name);
-       pthread_mutex_unlock(&mesh->mutex);
-
-       if(!submesh) {
-               meshlink_errno = MESHLINK_ENOENT;
-       }
-
-       return submesh;
-}
-
-meshlink_node_t **meshlink_get_all_nodes(meshlink_handle_t *mesh, meshlink_node_t **nodes, size_t *nmemb) {
-       if(!mesh || !nmemb || (*nmemb && !nodes)) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return NULL;
-       }
-
-       meshlink_node_t **result;
-
-       //lock mesh->nodes
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       *nmemb = mesh->nodes->count;
-       result = realloc(nodes, *nmemb * sizeof(*nodes));
-
-       if(result) {
-               meshlink_node_t **p = result;
-
-               for splay_each(node_t, n, mesh->nodes) {
-                       *p++ = (meshlink_node_t *)n;
-               }
-       } else {
-               *nmemb = 0;
-               free(nodes);
-               meshlink_errno = MESHLINK_ENOMEM;
-       }
-
-       pthread_mutex_unlock(&mesh->mutex);
-
-       return result;
-}
-
-static meshlink_node_t **meshlink_get_all_nodes_by_condition(meshlink_handle_t *mesh, const void *condition, meshlink_node_t **nodes, size_t *nmemb, search_node_by_condition_t search_node) {
-       meshlink_node_t **result;
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       *nmemb = 0;
-
-       for splay_each(node_t, n, mesh->nodes) {
-               if(search_node(n, condition)) {
-                       ++*nmemb;
-               }
-       }
-
-       if(*nmemb == 0) {
-               free(nodes);
-               pthread_mutex_unlock(&mesh->mutex);
-               return NULL;
-       }
-
-       result = realloc(nodes, *nmemb * sizeof(*nodes));
-
-       if(result) {
-               meshlink_node_t **p = result;
-
-               for splay_each(node_t, n, mesh->nodes) {
-                       if(search_node(n, condition)) {
-                               *p++ = (meshlink_node_t *)n;
-                       }
-               }
-       } else {
-               *nmemb = 0;
-               free(nodes);
-               meshlink_errno = MESHLINK_ENOMEM;
-       }
-
-       pthread_mutex_unlock(&mesh->mutex);
-
-       return result;
-}
-
-static bool search_node_by_dev_class(const node_t *node, const void *condition) {
-       dev_class_t *devclass = (dev_class_t *)condition;
-
-       if(*devclass == (dev_class_t)node->devclass) {
-               return true;
-       }
-
-       return false;
-}
-
-static bool search_node_by_submesh(const node_t *node, const void *condition) {
-       if(condition == node->submesh) {
-               return true;
-       }
-
-       return false;
-}
-
-struct time_range {
-       time_t start;
-       time_t 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 NULL;
-       }
-
-       return meshlink_get_all_nodes_by_condition(mesh, &devclass, nodes, nmemb, search_node_by_dev_class);
-}
-
-meshlink_node_t **meshlink_get_all_nodes_by_submesh(meshlink_handle_t *mesh, meshlink_submesh_t *submesh, meshlink_node_t **nodes, size_t *nmemb) {
-       if(!mesh || !submesh || !nmemb) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return NULL;
-       }
-
-       return meshlink_get_all_nodes_by_condition(mesh, submesh, nodes, nmemb, search_node_by_submesh);
-}
-
-dev_class_t meshlink_get_node_dev_class(meshlink_handle_t *mesh, meshlink_node_t *node) {
-       if(!mesh || !node) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return -1;
-       }
-
-       dev_class_t devclass;
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       devclass = ((node_t *)node)->devclass;
-
-       pthread_mutex_unlock(&mesh->mutex);
-
-       return devclass;
-}
-
-meshlink_submesh_t *meshlink_get_node_submesh(meshlink_handle_t *mesh, meshlink_node_t *node) {
-       if(!mesh || !node) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return NULL;
-       }
-
-       node_t *n = (node_t *)node;
-
-       meshlink_submesh_t *s;
-
-       s = (meshlink_submesh_t *)n->submesh;
-
-       return s;
-}
-
-bool meshlink_get_node_reachability(struct meshlink_handle *mesh, struct meshlink_node *node, time_t *last_reachable, time_t *last_unreachable) {
-       if(!mesh || !node) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return NULL;
-       }
-
-       node_t *n = (node_t *)node;
-       bool reachable;
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       reachable = n->status.reachable && !n->status.blacklisted;
-
-       // TODO: handle reachable times?
-       (void)last_reachable;
-       (void)last_unreachable;
-
-       pthread_mutex_unlock(&mesh->mutex);
-
-       return reachable;
-}
-
 bool meshlink_sign(meshlink_handle_t *mesh, const void *data, size_t len, void *signature, size_t *siglen) {
        logger(mesh, MESHLINK_DEBUG, "meshlink_sign(%p, %zu, %p, %p)", data, len, signature, (void *)siglen);
 
@@ -2089,16 +1731,15 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) {
                return false;
        }
 
-       join_state_t state = {
-               .mesh = mesh,
-               .sock = -1,
-       };
+       join_state_t *state = xzalloc(sizeof * state);
+       state->mesh = mesh;
+       state->sock = -1;
 
        ecdsa_t *key = NULL;
        ecdsa_t *hiskey = NULL;
 
        //TODO: think of a better name for this variable, or of a different way to tokenize the invitation URL.
-       char copy[strlen(invitation) + 1];
+       char *copy = xstrdup(invitation);
 
        if(pthread_mutex_lock(&mesh->mutex) != 0) {
                abort();
@@ -2112,14 +1753,12 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) {
        }
 
        // Refuse to join a mesh if we are already part of one. We are part of one if we know at least one other node.
-       if(mesh->nodes->count > 1) {
+       if(mesh->peer) {
                logger(mesh, MESHLINK_ERROR, "Already part of an existing mesh\n");
                meshlink_errno = MESHLINK_EINVAL;
                goto exit;
        }
 
-       strcpy(copy, invitation);
-
        // Split the invitation URL into a list of hostname/port tuples, a key hash and a cookie.
 
        char *slash = strchr(copy, '/');
@@ -2137,16 +1776,19 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) {
        char *address = copy;
        char *port = NULL;
 
-       if(!b64decode(slash, state.hash, 18) || !b64decode(slash + 24, state.cookie, 18)) {
+       if(!b64decode(slash, state->hash, 18) || !b64decode(slash + 24, state->cookie, 18)) {
                goto invalid;
        }
 
+       logger(mesh, MESHLINK_DEBUG, "Done");
+
        if(mesh->inviter_commits_first) {
-               memcpy(state.cookie + 18, ecdsa_get_public_key(mesh->private_key), 32);
+               memcpy(state->cookie + 18, ecdsa_get_public_key(mesh->private_key), 32);
        }
 
        // Generate a throw-away key for the invitation.
        key = ecdsa_generate();
+       logger(mesh, MESHLINK_DEBUG, "Done");
 
        if(!key) {
                meshlink_errno = MESHLINK_EINTERNAL;
@@ -2194,9 +1836,9 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) {
 
                if(ai) {
                        for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) {
-                               state.sock = socket_in_netns(aip->ai_family, SOCK_STREAM, IPPROTO_TCP, mesh->netns);
+                               state->sock = socket_in_netns(aip->ai_family, SOCK_STREAM, IPPROTO_TCP, mesh->netns);
 
-                               if(state.sock == -1) {
+                               if(state->sock == -1) {
                                        logger(mesh, MESHLINK_DEBUG, "Could not open socket: %s\n", strerror(errno));
                                        meshlink_errno = MESHLINK_ENETWORK;
                                        continue;
@@ -2204,16 +1846,16 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) {
 
 #ifdef SO_NOSIGPIPE
                                int nosigpipe = 1;
-                               setsockopt(state.sock, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
+                               setsockopt(state->sock, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
 #endif
 
-                               set_timeout(state.sock, 5000);
+                               set_timeout(state->sock, 5000);
 
-                               if(connect(state.sock, aip->ai_addr, aip->ai_addrlen)) {
+                               if(connect(state->sock, aip->ai_addr, aip->ai_addrlen)) {
                                        logger(mesh, MESHLINK_DEBUG, "Could not connect to %s port %s: %s\n", address, port, strerror(errno));
                                        meshlink_errno = MESHLINK_ENETWORK;
-                                       closesocket(state.sock);
-                                       state.sock = -1;
+                                       closesocket(state->sock);
+                                       state->sock = -1;
                                        continue;
                                }
 
@@ -2225,14 +1867,14 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) {
                        meshlink_errno = MESHLINK_ERESOLV;
                }
 
-               if(state.sock != -1 || !comma) {
+               if(state->sock != -1 || !comma) {
                        break;
                }
 
                address = comma;
        }
 
-       if(state.sock == -1) {
+       if(state->sock == -1) {
                goto exit;
        }
 
@@ -2240,9 +1882,9 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) {
 
        // Tell him we have an invitation, and give him our throw-away key.
 
-       state.blen = 0;
+       state->blen = 0;
 
-       if(!sendline(state.sock, "0 ?%s %d.%d %s", b64key, PROT_MAJOR, PROT_MINOR, mesh->appname)) {
+       if(!sendline(state->sock, "0 ?%s %d.%d %s", b64key, PROT_MAJOR, PROT_MINOR, mesh->appname)) {
                logger(mesh, MESHLINK_ERROR, "Error sending request to %s port %s: %s\n", address, port, strerror(errno));
                meshlink_errno = MESHLINK_ENETWORK;
                goto exit;
@@ -2250,27 +1892,27 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) {
 
        free(b64key);
 
-       char hisname[4096] = "";
+       char hisname[BUFSIZE] = "";
        int code, hismajor, hisminor = 0;
 
-       if(!recvline(&state) || sscanf(state.line, "%d %s %d.%d", &code, hisname, &hismajor, &hisminor) < 3 || code != 0 || hismajor != PROT_MAJOR || !check_id(hisname) || !recvline(&state) || !rstrip(state.line) || sscanf(state.line, "%d ", &code) != 1 || code != ACK || strlen(state.line) < 3) {
+       if(!recvline(state) || sscanf(state->line, "%d %s %d.%d", &code, hisname, &hismajor, &hisminor) < 3 || code != 0 || hismajor != PROT_MAJOR || !check_id(hisname) || !recvline(state) || !rstrip(state->line) || sscanf(state->line, "%d ", &code) != 1 || code != ACK || strlen(state->line) < 3) {
                logger(mesh, MESHLINK_ERROR, "Cannot read greeting from peer\n");
                meshlink_errno = MESHLINK_ENETWORK;
                goto exit;
        }
 
        // Check if the hash of the key he gave us matches the hash in the URL.
-       char *fingerprint = state.line + 2;
+       char *fingerprint = state->line + 2;
        char hishash[64];
 
        if(sha512(fingerprint, strlen(fingerprint), hishash)) {
-               logger(mesh, MESHLINK_ERROR, "Could not create hash\n%s\n", state.line + 2);
+               logger(mesh, MESHLINK_ERROR, "Could not create hash\n%s\n", state->line + 2);
                meshlink_errno = MESHLINK_EINTERNAL;
                goto exit;
        }
 
-       if(memcmp(hishash, state.hash, 18)) {
-               logger(mesh, MESHLINK_ERROR, "Peer has an invalid key!\n%s\n", state.line + 2);
+       if(memcmp(hishash, state->hash, 18)) {
+               logger(mesh, MESHLINK_ERROR, "Peer has an invalid key!\n%s\n", state->line + 2);
                meshlink_errno = MESHLINK_EPEER;
                goto exit;
        }
@@ -2283,47 +1925,55 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) {
        }
 
        // Start an SPTPS session
-       if(!sptps_start(&state.sptps, &state, true, false, key, hiskey, meshlink_invitation_label, sizeof(meshlink_invitation_label), invitation_send, invitation_receive)) {
+       if(!sptps_start(&state->sptps, state, true, false, key, hiskey, meshlink_invitation_label, sizeof(meshlink_invitation_label), invitation_send, invitation_receive)) {
                meshlink_errno = MESHLINK_EINTERNAL;
                goto exit;
        }
 
        // Feed rest of input buffer to SPTPS
-       if(!sptps_receive_data(&state.sptps, state.buffer, state.blen)) {
+       if(!sptps_receive_data(&state->sptps, state->buffer, state->blen)) {
                meshlink_errno = MESHLINK_EPEER;
                goto exit;
        }
 
        ssize_t len;
-       logger(mesh, MESHLINK_DEBUG, "Starting invitation recv loop: %d %zu\n", state.sock, sizeof(state.line));
+       logger(mesh, MESHLINK_DEBUG, "Starting invitation recv loop: %d %zu\n", state->sock, sizeof(state->line));
 
-       while((len = recv(state.sock, state.line, sizeof(state.line), 0))) {
+       while((len = recv(state->sock, state->line, sizeof(state->line), 0))) {
                if(len < 0) {
                        if(errno == EINTR) {
                                continue;
                        }
 
+                       if(errno == ENOTCONN) {
+                               break;
+                       }
+
                        logger(mesh, MESHLINK_ERROR, "Error reading data from %s port %s: %s\n", address, port, strerror(errno));
                        meshlink_errno = MESHLINK_ENETWORK;
                        goto exit;
                }
 
-               if(!sptps_receive_data(&state.sptps, state.line, len)) {
+               if(!sptps_receive_data(&state->sptps, state->line, len)) {
                        meshlink_errno = MESHLINK_EPEER;
                        goto exit;
                }
+
+               if(state->success) {
+                       break;
+               }
        }
 
-       if(!state.success) {
+       if(!state->success) {
                logger(mesh, MESHLINK_ERROR, "Connection closed by peer, invitation cancelled.\n");
                meshlink_errno = MESHLINK_EPEER;
                goto exit;
        }
 
-       sptps_stop(&state.sptps);
+       sptps_stop(&state->sptps);
        ecdsa_free(hiskey);
        ecdsa_free(key);
-       closesocket(state.sock);
+       closesocket(state->sock);
 
        pthread_mutex_unlock(&mesh->mutex);
        return true;
@@ -2332,15 +1982,16 @@ invalid:
        logger(mesh, MESHLINK_ERROR, "Invalid invitation URL\n");
        meshlink_errno = MESHLINK_EINVAL;
 exit:
-       sptps_stop(&state.sptps);
+       sptps_stop(&state->sptps);
        ecdsa_free(hiskey);
        ecdsa_free(key);
 
-       if(state.sock != -1) {
-               closesocket(state.sock);
+       if(state->sock != -1) {
+               closesocket(state->sock);
        }
 
        pthread_mutex_unlock(&mesh->mutex);
+       free(copy);
        return false;
 }
 
@@ -2352,7 +2003,7 @@ char *meshlink_export(meshlink_handle_t *mesh) {
 
        // Create a config file on the fly.
 
-       uint8_t buf[4096];
+       uint8_t buf[BUFSIZE];
        packmsg_output_t out = {buf, sizeof(buf)};
        packmsg_add_uint32(&out, MESHLINK_CONFIG_VERSION);
        packmsg_add_str(&out, mesh->name);
@@ -2524,521 +2175,15 @@ bool meshlink_import(meshlink_handle_t *mesh, const char *data) {
        return true;
 }
 
-bool meshlink_forget_node(meshlink_handle_t *mesh, meshlink_node_t *node) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_forget_node(%s)", node ? node->name : "(null)");
+/* 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) {
+       logger(mesh, MESHLINK_DEBUG, "meshlink_hint_address(%s, %p)", node ? node->name : "(null)", (const void *)addr);
 
-       if(!mesh || !node) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return false;
-       }
-
-       node_t *n = (node_t *)node;
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       /* 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, list_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) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_hint_address(%s, %p)", node ? node->name : "(null)", (const void *)addr);
-
-       if(!mesh || !node || !addr) {
-               meshlink_errno = EINVAL;
-               return;
-       }
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       node_t *n = (node_t *)node;
-
-       if(node_add_recent_address(mesh, n, (sockaddr_t *)addr)) {
-               if(!node_write_config(mesh, n, false)) {
-                       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) {
-       (void)port;
-       node_t *n = utcp->priv;
-       meshlink_handle_t *mesh = n->mesh;
-
-       if(mesh->channel_accept_cb && mesh->channel_listen_cb) {
-               return mesh->channel_listen_cb(mesh, (meshlink_node_t *)n, port);
-       } else {
-               return mesh->channel_accept_cb;
-       }
-}
-
-/* Finish one AIO buffer, return true if the channel is still open. */
-static bool aio_finish_one(meshlink_handle_t *mesh, meshlink_channel_t *channel, meshlink_aio_buffer_t **head) {
-       meshlink_aio_buffer_t *aio = *head;
-       *head = aio->next;
-
-       if(channel->c) {
-               channel->in_callback = true;
-
-               if(aio->data) {
-                       if(aio->cb.buffer) {
-                               aio->cb.buffer(mesh, channel, aio->data, aio->done, aio->priv);
-                       }
-               } else {
-                       if(aio->cb.fd) {
-                               aio->cb.fd(mesh, channel, aio->fd, aio->done, aio->priv);
-                       }
-               }
-
-               channel->in_callback = false;
-
-               if(!channel->c) {
-                       free(aio);
-                       free(channel);
-                       return false;
-               }
-       }
-
-       free(aio);
-       return true;
-}
-
-/* Finish all AIO buffers, return true if the channel is still open. */
-static bool aio_abort(meshlink_handle_t *mesh, meshlink_channel_t *channel, meshlink_aio_buffer_t **head) {
-       while(*head) {
-               if(!aio_finish_one(mesh, channel, head)) {
-                       return false;
-               }
-       }
-
-       return true;
-}
-
-static ssize_t channel_recv(struct utcp_connection *connection, const void *data, size_t len) {
-       meshlink_channel_t *channel = connection->priv;
-
-       if(!channel) {
-               abort();
-       }
-
-       node_t *n = channel->node;
-       meshlink_handle_t *mesh = n->mesh;
-
-       if(n->status.destroyed) {
-               meshlink_channel_close(mesh, channel);
-               return len;
-       }
-
-       const char *p = data;
-       size_t left = len;
-
-       while(channel->aio_receive) {
-               if(!len) {
-                       /* This receive callback signalled an error, abort all outstanding AIO buffers. */
-                       if(!aio_abort(mesh, channel, &channel->aio_receive)) {
-                               return len;
-                       }
-
-                       break;
-               }
-
-               meshlink_aio_buffer_t *aio = channel->aio_receive;
-               size_t todo = aio->len - aio->done;
-
-               if(todo > left) {
-                       todo = left;
-               }
-
-               if(aio->data) {
-                       memcpy((char *)aio->data + aio->done, p, todo);
-               } else {
-                       ssize_t result = write(aio->fd, p, todo);
-
-                       if(result <= 0) {
-                               if(result < 0 && errno == EINTR) {
-                                       continue;
-                               }
-
-                               /* Writing to fd failed, cancel just this AIO buffer. */
-                               logger(mesh, MESHLINK_ERROR, "Writing to AIO fd %d failed: %s", aio->fd, strerror(errno));
-
-                               if(!aio_finish_one(mesh, channel, &channel->aio_receive)) {
-                                       return len;
-                               }
-
-                               continue;
-                       }
-
-                       todo = result;
-               }
-
-               aio->done += todo;
-               p += todo;
-               left -= todo;
-
-               if(aio->done == aio->len) {
-                       if(!aio_finish_one(mesh, channel, &channel->aio_receive)) {
-                               return len;
-                       }
-               }
-
-               if(!left) {
-                       return len;
-               }
-       }
-
-       if(channel->receive_cb) {
-               channel->receive_cb(mesh, channel, p, left);
-       }
-
-       return len;
-}
-
-static void channel_accept(struct utcp_connection *utcp_connection, uint16_t port) {
-       node_t *n = utcp_connection->utcp->priv;
-
-       if(!n) {
-               abort();
-       }
-
-       meshlink_handle_t *mesh = n->mesh;
-
-       if(!mesh->channel_accept_cb) {
-               return;
-       }
-
-       meshlink_channel_t *channel = xzalloc(sizeof(*channel));
-       channel->node = n;
-       channel->c = utcp_connection;
-
-       if(mesh->channel_accept_cb(mesh, channel, port, NULL, 0)) {
-               utcp_accept(utcp_connection, channel_recv, channel);
-       } else {
-               free(channel);
-       }
-}
-
-static void channel_retransmit(struct utcp_connection *utcp_connection) {
-       node_t *n = utcp_connection->utcp->priv;
-       meshlink_handle_t *mesh = n->mesh;
-
-       if(n->mtuprobes == 31 && n->mtutimeout.cb) {
-               timeout_set(&mesh->loop, &n->mtutimeout, &(struct timespec) {
-                       0, 0
-               });
-       }
-}
-
-static ssize_t channel_send(struct utcp *utcp, const void *data, size_t len) {
-       node_t *n = utcp->priv;
-
-       if(n->status.destroyed) {
-               return -1;
-       }
-
-       meshlink_handle_t *mesh = n->mesh;
-       return meshlink_send_immediate(mesh, (meshlink_node_t *)n, data, len) ? (ssize_t)len : -1;
-}
-
-void meshlink_set_channel_receive_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, meshlink_channel_receive_cb_t cb) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_set_channel_receive_cb(%p, %p)", (void *)channel, (void *)(intptr_t)cb);
-
-       if(!mesh || !channel) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return;
-       }
-
-       channel->receive_cb = cb;
-}
-
-static void channel_receive(meshlink_handle_t *mesh, meshlink_node_t *source, const void *data, size_t len) {
-       (void)mesh;
-       node_t *n = (node_t *)source;
-
-       if(!n->utcp) {
-               abort();
-       }
-
-       utcp_recv(n->utcp, data, len);
-}
-
-static void channel_poll(struct utcp_connection *connection, size_t len) {
-       meshlink_channel_t *channel = connection->priv;
-
-       if(!channel) {
-               abort();
-       }
-
-       node_t *n = channel->node;
-       meshlink_handle_t *mesh = n->mesh;
-
-       while(channel->aio_send) {
-               if(!len) {
-                       /* This poll callback signalled an error, abort all outstanding AIO buffers. */
-                       if(!aio_abort(mesh, channel, &channel->aio_send)) {
-                               return;
-                       }
-
-                       break;
-               }
-
-               /* We have at least one AIO buffer. Send as much as possible from the buffers. */
-               meshlink_aio_buffer_t *aio = channel->aio_send;
-               size_t todo = aio->len - aio->done;
-               ssize_t sent;
-
-               if(todo > len) {
-                       todo = len;
-               }
-
-               if(aio->data) {
-                       sent = utcp_send(connection, (char *)aio->data + aio->done, todo);
-               } else {
-                       /* Limit the amount we read at once to avoid stack overflows */
-                       if(todo > 65536) {
-                               todo = 65536;
-                       }
-
-                       char buf[todo];
-                       ssize_t result = read(aio->fd, buf, todo);
-
-                       if(result > 0) {
-                               todo = result;
-                               sent = utcp_send(connection, buf, todo);
-                       } else {
-                               if(result < 0 && errno == EINTR) {
-                                       continue;
-                               }
-
-                               /* Reading from fd failed, cancel just this AIO buffer. */
-                               if(result != 0) {
-                                       logger(mesh, MESHLINK_ERROR, "Reading from AIO fd %d failed: %s", aio->fd, strerror(errno));
-                               }
-
-                               if(!aio_finish_one(mesh, channel, &channel->aio_send)) {
-                                       return;
-                               }
-
-                               continue;
-                       }
-               }
-
-               if(sent != (ssize_t)todo) {
-                       /* Sending failed, abort all outstanding AIO buffers and send a poll callback. */
-                       if(!aio_abort(mesh, channel, &channel->aio_send)) {
-                               return;
-                       }
-
-                       len = 0;
-                       break;
-               }
-
-               aio->done += sent;
-               len -= sent;
-
-               /* If we didn't finish this buffer, exit early. */
-               if(aio->done < aio->len) {
-                       return;
-               }
-
-               /* Signal completion of this buffer, and go to the next one. */
-               if(!aio_finish_one(mesh, channel, &channel->aio_send)) {
-                       return;
-               }
-
-               if(!len) {
-                       return;
-               }
-       }
-
-       if(channel->poll_cb) {
-               channel->poll_cb(mesh, channel, len);
-       } else {
-               utcp_set_poll_cb(connection, NULL);
-       }
-}
-
-void meshlink_set_channel_poll_cb(meshlink_handle_t *mesh, meshlink_channel_t *channel, meshlink_channel_poll_cb_t cb) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_set_channel_poll_cb(%p, %p)", (void *)channel, (void *)(intptr_t)cb);
-
-       if(!mesh || !channel) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return;
-       }
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       channel->poll_cb = cb;
-       utcp_set_poll_cb(channel->c, (cb || channel->aio_send) ? channel_poll : NULL);
-       pthread_mutex_unlock(&mesh->mutex);
-}
-
-void meshlink_set_channel_listen_cb(meshlink_handle_t *mesh, meshlink_channel_listen_cb_t cb) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_set_channel_listen_cb(%p)", (void *)(intptr_t)cb);
-
-       if(!mesh) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return;
-       }
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       mesh->channel_listen_cb = cb;
-
-       pthread_mutex_unlock(&mesh->mutex);
-}
-
-void meshlink_set_channel_accept_cb(meshlink_handle_t *mesh, meshlink_channel_accept_cb_t cb) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_set_channel_accept_cb(%p)", (void *)(intptr_t)cb);
-
-       if(!mesh) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return;
-       }
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       mesh->channel_accept_cb = cb;
-       mesh->receive_cb = channel_receive;
-
-       for splay_each(node_t, n, mesh->nodes) {
-               if(!n->utcp && n != mesh->self) {
-                       n->utcp = utcp_init(channel_accept, channel_pre_accept, channel_send, n);
-                       utcp_set_mtu(n->utcp, n->mtu - sizeof(meshlink_packethdr_t));
-                       utcp_set_retransmit_cb(n->utcp, channel_retransmit);
-               }
-       }
-
-       pthread_mutex_unlock(&mesh->mutex);
-}
-
-void meshlink_set_channel_sndbuf(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t size) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_set_channel_sndbuf(%p, %zu)", (void *)channel, size);
-
-       meshlink_set_channel_sndbuf_storage(mesh, channel, NULL, size);
-}
-
-void meshlink_set_channel_rcvbuf(meshlink_handle_t *mesh, meshlink_channel_t *channel, size_t size) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_set_channel_rcvbuf(%p, %zu)", (void *)channel, size);
-
-       meshlink_set_channel_rcvbuf_storage(mesh, channel, NULL, size);
-}
-
-void meshlink_set_channel_sndbuf_storage(meshlink_handle_t *mesh, meshlink_channel_t *channel, void *buf, size_t size) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_set_channel_sndbuf_storage(%p, %p, %zu)", (void *)channel, buf, size);
-
-       if(!mesh || !channel) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return;
-       }
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       utcp_set_sndbuf(channel->c, buf, size);
-       pthread_mutex_unlock(&mesh->mutex);
-}
-
-void meshlink_set_channel_rcvbuf_storage(meshlink_handle_t *mesh, meshlink_channel_t *channel, void *buf, size_t size) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_set_channel_rcvbuf_storage(%p, %p, %zu)", (void *)channel, buf, size);
-
-       if(!mesh || !channel) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return;
-       }
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       utcp_set_rcvbuf(channel->c, buf, size);
-       pthread_mutex_unlock(&mesh->mutex);
-}
-
-void meshlink_set_channel_flags(meshlink_handle_t *mesh, meshlink_channel_t *channel, uint32_t flags) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_set_channel_flags(%p, %u)", (void *)channel, flags);
-
-       if(!mesh || !channel) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return;
-       }
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       utcp_set_flags(channel->c, flags);
-       pthread_mutex_unlock(&mesh->mutex);
-}
-
-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) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_channel_open_ex(%s, %u, %p, %p, %zu, %u)", node ? node->name : "(null)", port, (void *)(intptr_t)cb, data, len, flags);
-
-       if(data && len) {
-               abort();        // TODO: handle non-NULL data
-       }
-
-       if(!mesh || !node) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return NULL;
+       if(!mesh || !node || !addr) {
+               meshlink_errno = EINVAL;
+               return;
        }
 
        if(pthread_mutex_lock(&mesh->mutex) != 0) {
@@ -3047,416 +2192,20 @@ meshlink_channel_t *meshlink_channel_open_ex(meshlink_handle_t *mesh, meshlink_n
 
        node_t *n = (node_t *)node;
 
-       if(!n->utcp) {
-               n->utcp = utcp_init(channel_accept, channel_pre_accept, channel_send, n);
-               utcp_set_mtu(n->utcp, n->mtu - sizeof(meshlink_packethdr_t));
-               utcp_set_retransmit_cb(n->utcp, channel_retransmit);
-               mesh->receive_cb = channel_receive;
-
-               if(!n->utcp) {
-                       meshlink_errno = errno == ENOMEM ? MESHLINK_ENOMEM : MESHLINK_EINTERNAL;
-                       pthread_mutex_unlock(&mesh->mutex);
-                       return NULL;
+       if(node_add_recent_address(mesh, n, (sockaddr_t *)addr)) {
+               if(!node_write_config(mesh, n, false)) {
+                       logger(mesh, MESHLINK_DEBUG, "Could not update %s\n", n->name);
                }
        }
 
-       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);
-
-       if(!channel->c) {
-               meshlink_errno = errno == ENOMEM ? MESHLINK_ENOMEM : MESHLINK_EINTERNAL;
-               free(channel);
-               return NULL;
-       }
-
-       return channel;
-}
-
-meshlink_channel_t *meshlink_channel_open(meshlink_handle_t *mesh, meshlink_node_t *node, uint16_t port, meshlink_channel_receive_cb_t cb, const void *data, size_t len) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_channel_open_ex(%s, %u, %p, %p, %zu)", node ? node->name : "(null)", port, (void *)(intptr_t)cb, data, len);
-
-       return meshlink_channel_open_ex(mesh, node, port, cb, data, len, MESHLINK_CHANNEL_TCP);
-}
-
-void meshlink_channel_shutdown(meshlink_handle_t *mesh, meshlink_channel_t *channel, int direction) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_channel_shutdown(%p, %d)", (void *)channel, direction);
-
-       if(!mesh || !channel) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return;
-       }
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       utcp_shutdown(channel->c, direction);
-       pthread_mutex_unlock(&mesh->mutex);
-}
-
-void meshlink_channel_close(meshlink_handle_t *mesh, meshlink_channel_t *channel) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_channel_close(%p)", (void *)channel);
-
-       if(!mesh || !channel) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return;
-       }
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       if(channel->c) {
-               utcp_close(channel->c);
-               channel->c = NULL;
-
-               /* Clean up any outstanding AIO buffers. */
-               aio_abort(mesh, channel, &channel->aio_send);
-               aio_abort(mesh, channel, &channel->aio_receive);
-       }
-
-       if(!channel->in_callback) {
-               free(channel);
-       }
-
-       pthread_mutex_unlock(&mesh->mutex);
-}
-
-void meshlink_channel_abort(meshlink_handle_t *mesh, meshlink_channel_t *channel) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_channel_abort(%p)", (void *)channel);
-
-       if(!mesh || !channel) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return;
-       }
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       if(channel->c) {
-               utcp_abort(channel->c);
-               channel->c = NULL;
-
-               /* Clean up any outstanding AIO buffers. */
-               aio_abort(mesh, channel, &channel->aio_send);
-               aio_abort(mesh, channel, &channel->aio_receive);
-       }
-
-       if(!channel->in_callback) {
-               free(channel);
-       }
-
-       pthread_mutex_unlock(&mesh->mutex);
-}
-
-ssize_t meshlink_channel_send(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_channel_send(%p, %p, %zu)", (void *)channel, data, len);
-
-       if(!mesh || !channel) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return -1;
-       }
-
-       if(!len) {
-               return 0;
-       }
-
-       if(!data) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return -1;
-       }
-
-       // TODO: more finegrained locking.
-       // Ideally we want to put the data into the UTCP connection's send buffer.
-       // Then, preferably only if there is room in the receiver window,
-       // kick the meshlink thread to go send packets.
-
-       ssize_t retval;
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       /* Disallow direct calls to utcp_send() while we still have AIO active. */
-       if(channel->aio_send) {
-               retval = 0;
-       } else {
-               retval = utcp_send(channel->c, data, len);
-       }
-
-       pthread_mutex_unlock(&mesh->mutex);
-
-       if(retval < 0) {
-               meshlink_errno = MESHLINK_ENETWORK;
-       }
-
-       return retval;
-}
-
-bool meshlink_channel_aio_send(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len, meshlink_aio_cb_t cb, void *priv) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_channel_aio_send(%p, %p, %zu, %p, %p)", (void *)channel, data, len, (void *)(intptr_t)cb, priv);
-
-       if(!mesh || !channel) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return false;
-       }
-
-       if(!len || !data) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return false;
-       }
-
-       meshlink_aio_buffer_t *aio = xzalloc(sizeof(*aio));
-       aio->data = data;
-       aio->len = len;
-       aio->cb.buffer = cb;
-       aio->priv = priv;
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       /* Append the AIO buffer descriptor to the end of the chain */
-       meshlink_aio_buffer_t **p = &channel->aio_send;
-
-       while(*p) {
-               p = &(*p)->next;
-       }
-
-       *p = aio;
-
-       /* Ensure the poll callback is set, and call it right now to push data if possible */
-       utcp_set_poll_cb(channel->c, channel_poll);
-       size_t todo = MIN(len, utcp_get_rcvbuf_free(channel->c));
-
-       if(todo) {
-               channel_poll(channel->c, todo);
-       }
-
-       pthread_mutex_unlock(&mesh->mutex);
-
-       return true;
-}
-
-bool meshlink_channel_aio_fd_send(meshlink_handle_t *mesh, meshlink_channel_t *channel, int fd, size_t len, meshlink_aio_fd_cb_t cb, void *priv) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_channel_aio_fd_send(%p, %d, %zu, %p, %p)", (void *)channel, fd, len, (void *)(intptr_t)cb, priv);
-
-       if(!mesh || !channel) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return false;
-       }
-
-       if(!len || fd == -1) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return false;
-       }
-
-       meshlink_aio_buffer_t *aio = xzalloc(sizeof(*aio));
-       aio->fd = fd;
-       aio->len = len;
-       aio->cb.fd = cb;
-       aio->priv = priv;
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       /* Append the AIO buffer descriptor to the end of the chain */
-       meshlink_aio_buffer_t **p = &channel->aio_send;
-
-       while(*p) {
-               p = &(*p)->next;
-       }
-
-       *p = aio;
-
-       /* Ensure the poll callback is set, and call it right now to push data if possible */
-       utcp_set_poll_cb(channel->c, channel_poll);
-       size_t left = utcp_get_rcvbuf_free(channel->c);
-
-       if(left) {
-               channel_poll(channel->c, left);
-       }
-
-       pthread_mutex_unlock(&mesh->mutex);
-
-       return true;
-}
-
-bool meshlink_channel_aio_receive(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len, meshlink_aio_cb_t cb, void *priv) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_channel_aio_receive(%p, %p, %zu, %p, %p)", (void *)channel, data, len, (void *)(intptr_t)cb, priv);
-
-       if(!mesh || !channel) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return false;
-       }
-
-       if(!len || !data) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return false;
-       }
-
-       meshlink_aio_buffer_t *aio = xzalloc(sizeof(*aio));
-       aio->data = data;
-       aio->len = len;
-       aio->cb.buffer = cb;
-       aio->priv = priv;
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       /* Append the AIO buffer descriptor to the end of the chain */
-       meshlink_aio_buffer_t **p = &channel->aio_receive;
-
-       while(*p) {
-               p = &(*p)->next;
-       }
-
-       *p = aio;
-
-       pthread_mutex_unlock(&mesh->mutex);
-
-       return true;
-}
-
-bool meshlink_channel_aio_fd_receive(meshlink_handle_t *mesh, meshlink_channel_t *channel, int fd, size_t len, meshlink_aio_fd_cb_t cb, void *priv) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_channel_aio_fd_receive(%p, %d, %zu, %p, %p)", (void *)channel, fd, len, (void *)(intptr_t)cb, priv);
-
-       if(!mesh || !channel) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return false;
-       }
-
-       if(!len || fd == -1) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return false;
-       }
-
-       meshlink_aio_buffer_t *aio = xzalloc(sizeof(*aio));
-       aio->fd = fd;
-       aio->len = len;
-       aio->cb.fd = cb;
-       aio->priv = priv;
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       /* Append the AIO buffer descriptor to the end of the chain */
-       meshlink_aio_buffer_t **p = &channel->aio_receive;
-
-       while(*p) {
-               p = &(*p)->next;
-       }
-
-       *p = aio;
-
-       pthread_mutex_unlock(&mesh->mutex);
-
-       return true;
-}
-
-uint32_t meshlink_channel_get_flags(meshlink_handle_t *mesh, meshlink_channel_t *channel) {
-       if(!mesh || !channel) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return -1;
-       }
-
-       return channel->c->flags;
-}
-
-size_t meshlink_channel_get_sendq(meshlink_handle_t *mesh, meshlink_channel_t *channel) {
-       if(!mesh || !channel) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return -1;
-       }
-
-       return utcp_get_sendq(channel->c);
-}
-
-size_t meshlink_channel_get_recvq(meshlink_handle_t *mesh, meshlink_channel_t *channel) {
-       if(!mesh || !channel) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return -1;
-       }
-
-       return utcp_get_recvq(channel->c);
-}
-
-size_t meshlink_channel_get_mss(meshlink_handle_t *mesh, meshlink_channel_t *channel) {
-       if(!mesh || !channel) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return -1;
-       }
-
-       return utcp_get_mss(channel->node->utcp);
-}
-
-void meshlink_set_node_channel_timeout(meshlink_handle_t *mesh, meshlink_node_t *node, int timeout) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_set_node_channel_timeout(%s, %d)", node ? node->name : "(null)", timeout);
-
-       if(!mesh || !node) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return;
-       }
-
-       node_t *n = (node_t *)node;
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       if(!n->utcp) {
-               n->utcp = utcp_init(channel_accept, channel_pre_accept, channel_send, n);
-               utcp_set_mtu(n->utcp, n->mtu - sizeof(meshlink_packethdr_t));
-               utcp_set_retransmit_cb(n->utcp, channel_retransmit);
-       }
-
-       utcp_set_user_timeout(n->utcp, timeout);
-
        pthread_mutex_unlock(&mesh->mutex);
+       // @TODO do we want to fire off a connection attempt right away?
 }
 
 void update_node_status(meshlink_handle_t *mesh, node_t *n) {
-       if(n->status.reachable && mesh->channel_accept_cb && !n->utcp) {
-               n->utcp = utcp_init(channel_accept, channel_pre_accept, channel_send, n);
-               utcp_set_mtu(n->utcp, n->mtu - sizeof(meshlink_packethdr_t));
-               utcp_set_retransmit_cb(n->utcp, channel_retransmit);
-       }
-
        if(mesh->node_status_cb) {
                mesh->node_status_cb(mesh, (meshlink_node_t *)n, n->status.reachable && !n->status.blacklisted);
        }
-
-       if(mesh->node_pmtu_cb) {
-               mesh->node_pmtu_cb(mesh, (meshlink_node_t *)n, n->minmtu);
-       }
-}
-
-void update_node_pmtu(meshlink_handle_t *mesh, node_t *n) {
-       utcp_set_mtu(n->utcp, (n->minmtu > MINMTU ? n->minmtu : MINMTU) - sizeof(meshlink_packethdr_t));
-
-       if(mesh->node_pmtu_cb && !n->status.blacklisted) {
-               mesh->node_pmtu_cb(mesh, (meshlink_node_t *)n, n->minmtu);
-       }
 }
 
 void handle_duplicate_node(meshlink_handle_t *mesh, node_t *n) {
@@ -3579,17 +2328,6 @@ void meshlink_set_inviter_commits_first(struct meshlink_handle *mesh, bool invit
        pthread_mutex_unlock(&mesh->mutex);
 }
 
-void meshlink_set_scheduling_granularity(struct meshlink_handle *mesh, long granularity) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_set_scheduling_granularity(%ld)", granularity);
-
-       if(!mesh || granularity < 0) {
-               meshlink_errno = EINVAL;
-               return;
-       }
-
-       utcp_set_clock_granularity(granularity);
-}
-
 void meshlink_set_storage_policy(struct meshlink_handle *mesh, meshlink_storage_policy_t policy) {
        logger(mesh, MESHLINK_DEBUG, "meshlink_set_storage_policy(%d)", policy);
 
@@ -3609,7 +2347,7 @@ void meshlink_set_storage_policy(struct meshlink_handle *mesh, meshlink_storage_
 void handle_network_change(meshlink_handle_t *mesh, bool online) {
        (void)online;
 
-       if(!mesh->connections || !mesh->loop.running) {
+       if(!mesh->loop.running) {
                return;
        }
 
@@ -3634,7 +2372,6 @@ void call_error_cb(meshlink_handle_t *mesh, meshlink_errno_t cb_errno) {
 
 static void __attribute__((constructor)) meshlink_init(void) {
        crypto_init();
-       utcp_set_clock_granularity(10000);
 }
 
 static void __attribute__((destructor)) meshlink_exit(void) {