]> git.meshlink.io Git - meshlink-tiny/blobdiff - src/meshlink.c
Remove MTU and traffic statistics.
[meshlink-tiny] / src / meshlink.c
index 0d607fe2626997e86d518e4be04df38d08901e09..89a146e9bbc79e3d980933d0ea95c42a564abe1c 100644 (file)
@@ -20,7 +20,6 @@
 #include "system.h"
 #include <pthread.h>
 
-#include "adns.h"
 #include "crypto.h"
 #include "ecdsagen.h"
 #include "logger.h"
@@ -28,7 +27,6 @@
 #include "net.h"
 #include "netutl.h"
 #include "node.h"
-#include "submesh.h"
 #include "packmsg.h"
 #include "prf.h"
 #include "protocol.h"
@@ -37,9 +35,7 @@
 #include "utils.h"
 #include "xalloc.h"
 #include "ed25519/sha512.h"
-#include "discovery.h"
 #include "devtools.h"
-#include "graph.h"
 
 #ifndef MSG_NOSIGNAL
 #define MSG_NOSIGNAL 0
@@ -152,353 +148,6 @@ static int socket_in_netns(int domain, int type, int protocol, int netns) {
 
 }
 
-// Find out what local address a socket would use if we connect to the given address.
-// We do this using connect() on a UDP socket, so the kernel has to resolve the address
-// of both endpoints, but this will actually not send any UDP packet.
-static bool getlocaladdr(const char *destaddr, sockaddr_t *sa, socklen_t *salen, int netns) {
-       struct addrinfo *rai = NULL;
-       const struct addrinfo hint = {
-               .ai_family = AF_UNSPEC,
-               .ai_socktype = SOCK_DGRAM,
-               .ai_protocol = IPPROTO_UDP,
-               .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV,
-       };
-
-       if(getaddrinfo(destaddr, "80", &hint, &rai) || !rai) {
-               return false;
-       }
-
-       int sock = socket_in_netns(rai->ai_family, rai->ai_socktype, rai->ai_protocol, netns);
-
-       if(sock == -1) {
-               freeaddrinfo(rai);
-               return false;
-       }
-
-       if(connect(sock, rai->ai_addr, rai->ai_addrlen) && !sockwouldblock(errno)) {
-               closesocket(sock);
-               freeaddrinfo(rai);
-               return false;
-       }
-
-       freeaddrinfo(rai);
-
-       if(getsockname(sock, &sa->sa, salen)) {
-               closesocket(sock);
-               return false;
-       }
-
-       closesocket(sock);
-       return true;
-}
-
-static bool getlocaladdrname(const char *destaddr, char *host, socklen_t hostlen, int netns) {
-       sockaddr_t sa;
-       socklen_t salen = sizeof(sa);
-
-       if(!getlocaladdr(destaddr, &sa, &salen, netns)) {
-               return false;
-       }
-
-       if(getnameinfo(&sa.sa, salen, host, hostlen, NULL, 0, NI_NUMERICHOST | NI_NUMERICSERV)) {
-               return false;
-       }
-
-       return true;
-}
-
-char *meshlink_get_external_address(meshlink_handle_t *mesh) {
-       return meshlink_get_external_address_for_family(mesh, AF_UNSPEC);
-}
-
-char *meshlink_get_external_address_for_family(meshlink_handle_t *mesh, int family) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_get_external_address_for_family(%d)", family);
-       const char *url = mesh->external_address_url;
-
-       if(!url) {
-               url = "http://meshlink.io/host.cgi";
-       }
-
-       /* Find the hostname part between the slashes */
-       if(strncmp(url, "http://", 7)) {
-               abort();
-               meshlink_errno = MESHLINK_EINTERNAL;
-               return NULL;
-       }
-
-       const char *begin = url + 7;
-
-       const char *end = strchr(begin, '/');
-
-       if(!end) {
-               end = begin + strlen(begin);
-       }
-
-       /* Make a copy */
-       char host[end - begin + 1];
-       strncpy(host, begin, end - begin);
-       host[end - begin] = 0;
-
-       char *port = strchr(host, ':');
-
-       if(port) {
-               *port++ = 0;
-       }
-
-       logger(mesh, MESHLINK_DEBUG, "Trying to discover externally visible hostname...\n");
-       struct addrinfo *ai = adns_blocking_request(mesh, xstrdup(host), xstrdup(port ? port : "80"), SOCK_STREAM, 5);
-       char line[256];
-       char *hostname = NULL;
-
-       for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) {
-               if(family != AF_UNSPEC && aip->ai_family != family) {
-                       continue;
-               }
-
-               int s = socket_in_netns(aip->ai_family, aip->ai_socktype, aip->ai_protocol, mesh->netns);
-
-#ifdef SO_NOSIGPIPE
-               int nosigpipe = 1;
-               setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
-#endif
-
-               if(s >= 0) {
-                       set_timeout(s, 5000);
-
-                       if(connect(s, aip->ai_addr, aip->ai_addrlen)) {
-                               closesocket(s);
-                               s = -1;
-                       }
-               }
-
-               if(s >= 0) {
-                       send(s, "GET ", 4, 0);
-                       send(s, url, strlen(url), 0);
-                       send(s, " HTTP/1.0\r\n\r\n", 13, 0);
-                       int len = recv(s, line, sizeof(line) - 1, MSG_WAITALL);
-
-                       if(len > 0) {
-                               line[len] = 0;
-
-                               if(line[len - 1] == '\n') {
-                                       line[--len] = 0;
-                               }
-
-                               char *p = strrchr(line, '\n');
-
-                               if(p && p[1]) {
-                                       hostname = xstrdup(p + 1);
-                               }
-                       }
-
-                       closesocket(s);
-
-                       if(hostname) {
-                               break;
-                       }
-               }
-       }
-
-       if(ai) {
-               freeaddrinfo(ai);
-       }
-
-       // Check that the hostname is reasonable
-       if(hostname && !is_valid_hostname(hostname)) {
-               free(hostname);
-               hostname = NULL;
-       }
-
-       if(!hostname) {
-               meshlink_errno = MESHLINK_ERESOLV;
-       }
-
-       return hostname;
-}
-
-static bool is_localaddr(sockaddr_t *sa) {
-       switch(sa->sa.sa_family) {
-       case AF_INET:
-               return *(uint8_t *)(&sa->in.sin_addr.s_addr) == 127;
-
-       case AF_INET6: {
-               uint16_t first = sa->in6.sin6_addr.s6_addr[0] << 8 | sa->in6.sin6_addr.s6_addr[1];
-               return first == 0 || (first & 0xffc0) == 0xfe80;
-       }
-
-       default:
-               return false;
-       }
-}
-
-#ifdef HAVE_GETIFADDRS
-struct getifaddrs_in_netns_params {
-       struct ifaddrs **ifa;
-       int netns;
-};
-
-#ifdef HAVE_SETNS
-static void *getifaddrs_in_netns_thread(void *arg) {
-       struct getifaddrs_in_netns_params *params = arg;
-
-       if(setns(params->netns, CLONE_NEWNET) == -1) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return NULL;
-       }
-
-       if(getifaddrs(params->ifa) != 0) {
-               *params->ifa = NULL;
-       }
-
-       return NULL;
-}
-#endif // HAVE_SETNS
-
-static int getifaddrs_in_netns(struct ifaddrs **ifa, int netns) {
-       if(netns == -1) {
-               return getifaddrs(ifa);
-       }
-
-#ifdef HAVE_SETNS
-       struct getifaddrs_in_netns_params params = {ifa, netns};
-       pthread_t thr;
-
-       if(pthread_create(&thr, NULL, getifaddrs_in_netns_thread, &params) == 0) {
-               if(pthread_join(thr, NULL) != 0) {
-                       abort();
-               }
-       }
-
-       return *params.ifa ? 0 : -1;
-#else
-       return -1;
-#endif // HAVE_SETNS
-
-}
-#endif
-
-char *meshlink_get_local_address_for_family(meshlink_handle_t *mesh, int family) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_get_local_address_for_family(%d)", family);
-
-       if(!mesh) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return NULL;
-       }
-
-       // Determine address of the local interface used for outgoing connections.
-       char localaddr[NI_MAXHOST];
-       bool success = false;
-
-       if(family == AF_INET) {
-               success = getlocaladdrname("93.184.216.34", localaddr, sizeof(localaddr), mesh->netns);
-       } else if(family == AF_INET6) {
-               success = getlocaladdrname("2606:2800:220:1:248:1893:25c8:1946", localaddr, sizeof(localaddr), mesh->netns);
-       }
-
-#ifdef HAVE_GETIFADDRS
-
-       if(!success) {
-               struct ifaddrs *ifa = NULL;
-               getifaddrs_in_netns(&ifa, mesh->netns);
-
-               for(struct ifaddrs *ifap = ifa; ifap; ifap = ifap->ifa_next) {
-                       sockaddr_t *sa = (sockaddr_t *)ifap->ifa_addr;
-
-                       if(!sa || sa->sa.sa_family != family) {
-                               continue;
-                       }
-
-                       if(is_localaddr(sa)) {
-                               continue;
-                       }
-
-                       if(!getnameinfo(&sa->sa, SALEN(sa->sa), localaddr, sizeof(localaddr), NULL, 0, NI_NUMERICHOST | NI_NUMERICSERV)) {
-                               success = true;
-                               break;
-                       }
-               }
-
-               freeifaddrs(ifa);
-       }
-
-#endif
-
-       if(!success) {
-               meshlink_errno = MESHLINK_ENETWORK;
-               return NULL;
-       }
-
-       return xstrdup(localaddr);
-}
-
-static bool try_bind(meshlink_handle_t *mesh, int port) {
-       struct addrinfo *ai = NULL;
-       struct addrinfo hint = {
-               .ai_flags = AI_PASSIVE,
-               .ai_family = AF_UNSPEC,
-               .ai_socktype = SOCK_STREAM,
-               .ai_protocol = IPPROTO_TCP,
-       };
-
-       char portstr[16];
-       snprintf(portstr, sizeof(portstr), "%d", port);
-
-       if(getaddrinfo(NULL, portstr, &hint, &ai) || !ai) {
-               return false;
-       }
-
-       bool success = false;
-
-       for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) {
-               /* Try to bind to TCP. */
-
-               int tcp_fd = setup_tcp_listen_socket(mesh, aip);
-
-               if(tcp_fd == -1) {
-                       if(errno == EADDRINUSE) {
-                               /* If this port is in use for any address family, avoid it. */
-                               success = false;
-                               break;
-                       } else {
-                               continue;
-                       }
-               }
-
-               /* If TCP worked, then we require that UDP works as well. */
-
-               int udp_fd = setup_udp_listen_socket(mesh, aip);
-
-               if(udp_fd == -1) {
-                       closesocket(tcp_fd);
-                       success = false;
-                       break;
-               }
-
-               closesocket(tcp_fd);
-               closesocket(udp_fd);
-               success = true;
-       }
-
-       freeaddrinfo(ai);
-       return success;
-}
-
-int check_port(meshlink_handle_t *mesh) {
-       for(int i = 0; i < 1000; i++) {
-               int port = 0x1000 + prng(mesh, 0x8000);
-
-               if(try_bind(mesh, port)) {
-                       free(mesh->myport);
-                       xasprintf(&mesh->myport, "%d", port);
-                       return port;
-               }
-       }
-
-       meshlink_errno = MESHLINK_ENETWORK;
-       logger(mesh, MESHLINK_DEBUG, "Could not find any available network port.\n");
-       return 0;
-}
-
 static bool write_main_config_files(meshlink_handle_t *mesh) {
        if(!mesh->confbase) {
                return true;
@@ -558,28 +207,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;
        }
 
@@ -587,8 +227,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
@@ -656,10 +294,6 @@ static bool finalize_join(join_state_t *state, const void *buf, uint16_t len) {
                        }
                }
 
-               /* Clear the reachability times, since we ourself have never seen these nodes yet */
-               n->last_reachable = 0;
-               n->last_unreachable = 0;
-
                if(!node_write_config(mesh, n, true)) {
                        free_node(n);
                        return false;
@@ -854,54 +488,16 @@ 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);
-
-               if(timespec_lt(&t, &tmin)) {
-                       tmin = t;
-               }
-       }
 
-       return tmin;
-}
-
-// Get our local address(es) by simulating connecting to an Internet host.
-static void add_local_addresses(meshlink_handle_t *mesh) {
-       sockaddr_t sa;
-       sa.storage.ss_family = AF_UNKNOWN;
-       socklen_t salen = sizeof(sa);
-
-       // IPv4 example.org
-
-       if(getlocaladdr("93.184.216.34", &sa, &salen, mesh->netns)) {
-               sa.in.sin_port = ntohs(atoi(mesh->myport));
-               node_add_recent_address(mesh, mesh->self, &sa);
-       }
-
-       // IPv6 example.org
-
-       salen = sizeof(sa);
-
-       if(getlocaladdr("2606:2800:220:1:248:1893:25c8:1946", &sa, &salen, mesh->netns)) {
-               sa.in6.sin6_port = ntohs(atoi(mesh->myport));
-               node_add_recent_address(mesh, mesh->self, &sa);
+       if(mesh->peer && mesh->peer->utcp) {
+               return utcp_timeout(mesh->peer->utcp);
+       } else {
+               return (struct timespec) {
+                       3600, 0
+               };
        }
 }
 
@@ -929,10 +525,7 @@ static bool meshlink_setup(meshlink_handle_t *mesh) {
                return false;
        }
 
-       if(check_port(mesh) == 0) {
-               meshlink_errno = MESHLINK_ENETWORK;
-               return false;
-       }
+       mesh->myport = xstrdup("0");
 
        /* Create a node for ourself */
 
@@ -1369,9 +962,7 @@ meshlink_handle_t *meshlink_open_ex(const meshlink_open_params_t *params) {
 
        mesh->appname = xstrdup(params->appname);
        mesh->devclass = params->devclass;
-       mesh->discovery.enabled = true;
        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));
@@ -1409,8 +1000,6 @@ meshlink_handle_t *meshlink_open_ex(const meshlink_open_params_t *params) {
        pthread_mutex_init(&mesh->mutex, &attr);
        pthread_cond_init(&mesh->cond, NULL);
 
-       pthread_cond_init(&mesh->adns_cond, NULL);
-
        mesh->threadstarted = false;
        event_loop_init(&mesh->loop);
        mesh->loop.data = mesh;
@@ -1486,8 +1075,6 @@ meshlink_handle_t *meshlink_open_ex(const meshlink_open_params_t *params) {
                return NULL;
        }
 
-       add_local_addresses(mesh);
-
        if(!node_write_config(mesh, mesh->self, new_configuration)) {
                logger(NULL, MESHLINK_ERROR, "Cannot update configuration\n");
                return NULL;
@@ -1499,35 +1086,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;
 
@@ -1545,10 +1103,6 @@ static void *meshlink_main_loop(void *arg) {
 #endif // HAVE_SETNS
        }
 
-       if(mesh->discovery.enabled) {
-               discovery_start(mesh);
-       }
-
        if(pthread_mutex_lock(&mesh->mutex) != 0) {
                abort();
        }
@@ -1560,11 +1114,6 @@ static void *meshlink_main_loop(void *arg) {
 
        pthread_mutex_unlock(&mesh->mutex);
 
-       // Stop discovery
-       if(mesh->discovery.enabled) {
-               discovery_stop(mesh);
-       }
-
        return NULL;
 }
 
@@ -1591,19 +1140,11 @@ bool meshlink_start(meshlink_handle_t *mesh) {
                return true;
        }
 
-       if(mesh->listen_socket[0].tcp.fd < 0) {
-               logger(mesh, MESHLINK_ERROR, "Listening socket not open\n");
-               meshlink_errno = MESHLINK_ENETWORK;
-               return false;
-       }
-
        // 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;
        }
 
-       // TODO: open listening sockets first
-
        //Check that a valid name is set
        if(!mesh->name) {
                logger(mesh, MESHLINK_ERROR, "No name given!\n");
@@ -1613,7 +1154,6 @@ bool meshlink_start(meshlink_handle_t *mesh) {
        }
 
        init_outgoings(mesh);
-       init_adns(mesh);
 
        // Start the main thread
 
@@ -1636,9 +1176,6 @@ bool meshlink_start(meshlink_handle_t *mesh) {
        pthread_cond_wait(&mesh->cond, &mesh->mutex);
        mesh->threadstarted = true;
 
-       // Ensure we are considered reachable
-       graph(mesh);
-
        pthread_mutex_unlock(&mesh->mutex);
        return true;
 }
@@ -1658,20 +1195,7 @@ void meshlink_stop(meshlink_handle_t *mesh) {
        // Shut down the main thread
        event_loop_stop(&mesh->loop);
 
-       // Send ourselves a UDP packet to kick the event loop
-       for(int i = 0; i < mesh->listen_sockets; i++) {
-               sockaddr_t sa;
-               socklen_t salen = sizeof(sa);
-
-               if(getsockname(mesh->listen_socket[i].udp.fd, &sa.sa, &salen) == -1) {
-                       logger(mesh, MESHLINK_ERROR, "System call `%s' failed: %s", "getsockname", sockstrerror(sockerrno));
-                       continue;
-               }
-
-               if(sendto(mesh->listen_socket[i].udp.fd, "", 1, MSG_NOSIGNAL, &sa.sa, salen) == -1) {
-                       logger(mesh, MESHLINK_ERROR, "Could not send a UDP packet to ourself: %s", sockstrerror(sockerrno));
-               }
-       }
+       // TODO: send something to a local socket to kick the event loop
 
        if(mesh->threadstarted) {
                // Wait for the main thread to finish
@@ -1689,31 +1213,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_adns(mesh);
        exit_outgoings(mesh);
 
-       // Ensure we are considered unreachable
-       if(mesh->nodes) {
-               graph(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
                }
        }
 
@@ -1909,22 +1419,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);
 
@@ -2045,359 +1539,101 @@ bool meshlink_send(meshlink_handle_t *mesh, meshlink_node_t *destination, const
        // Prepare the packet
        vpn_packet_t *packet = malloc(sizeof(*packet));
 
-       if(!packet) {
-               meshlink_errno = MESHLINK_ENOMEM;
-               return false;
-       }
-
-       if(!prepare_packet(mesh, destination, data, len, packet)) {
-               free(packet);
-               return false;
-       }
-
-       // Queue it
-       if(!meshlink_queue_push(&mesh->outpacketqueue, packet)) {
-               free(packet);
-               meshlink_errno = MESHLINK_ENOMEM;
-               return false;
-       }
-
-       logger(mesh, MESHLINK_DEBUG, "Adding packet of %zu bytes to packet queue", len);
-
-       // Notify event loop
-       signal_trigger(&mesh->loop, &mesh->datafromapp);
-
-       return true;
-}
-
-void meshlink_send_from_queue(event_loop_t *loop, void *data) {
-       (void)loop;
-       meshlink_handle_t *mesh = data;
-
-       logger(mesh, MESHLINK_DEBUG, "Flushing the packet queue");
-
-       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);
-               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;
-               return NULL;
-       }
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       node_t *n = (node_t *)node;
-
-       if(!node_read_public_key(mesh, n) || !n->ecdsa) {
-               meshlink_errno = MESHLINK_EINTERNAL;
-               pthread_mutex_unlock(&mesh->mutex);
-               return false;
-       }
-
-       char *fingerprint = ecdsa_get_base64_public_key(n->ecdsa);
-
-       if(!fingerprint) {
-               meshlink_errno = MESHLINK_EINTERNAL;
-       }
-
-       pthread_mutex_unlock(&mesh->mutex);
-       return fingerprint;
-}
-
-meshlink_node_t *meshlink_get_self(meshlink_handle_t *mesh) {
-       if(!mesh) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return NULL;
-       }
-
-       return (meshlink_node_t *)mesh->self;
-}
-
-meshlink_node_t *meshlink_get_node(meshlink_handle_t *mesh, const char *name) {
-       if(!mesh || !name) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return NULL;
-       }
-
-       node_t *n = NULL;
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       n = lookup_node(mesh, (char *)name); // TODO: make lookup_node() use const
-       pthread_mutex_unlock(&mesh->mutex);
-
-       if(!n) {
-               meshlink_errno = MESHLINK_ENOENT;
-       }
-
-       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;
-};
-
-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(!packet) {
+               meshlink_errno = MESHLINK_ENOMEM;
+               return false;
        }
 
-       if(range->end >= range->start) {
-               return start <= range->end && end >= range->start;
-       } else {
-               return start > range->start || end < range->end;
+       if(!prepare_packet(mesh, destination, data, len, packet)) {
+               free(packet);
+               return false;
        }
-}
 
-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;
+       // Queue it
+       if(!meshlink_queue_push(&mesh->outpacketqueue, packet)) {
+               free(packet);
+               meshlink_errno = MESHLINK_ENOMEM;
+               return false;
        }
 
-       return meshlink_get_all_nodes_by_condition(mesh, &devclass, nodes, nmemb, search_node_by_dev_class);
-}
+       logger(mesh, MESHLINK_DEBUG, "Adding packet of %zu bytes to packet queue", len);
 
-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;
-       }
+       // Notify event loop
+       signal_trigger(&mesh->loop, &mesh->datafromapp);
 
-       return meshlink_get_all_nodes_by_condition(mesh, submesh, nodes, nmemb, search_node_by_submesh);
+       return true;
 }
 
-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;
-       }
+void meshlink_send_from_queue(event_loop_t *loop, void *data) {
+       (void)loop;
+       meshlink_handle_t *mesh = data;
 
-       struct time_range range = {start, end};
+       logger(mesh, MESHLINK_DEBUG, "Flushing the packet queue");
 
-       return meshlink_get_all_nodes_by_condition(mesh, &range, nodes, nmemb, search_node_by_last_reachable);
+       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);
+               route(mesh, mesh->self, packet);
+               free(packet);
+       }
 }
 
-dev_class_t meshlink_get_node_dev_class(meshlink_handle_t *mesh, meshlink_node_t *node) {
+char *meshlink_get_fingerprint(meshlink_handle_t *mesh, meshlink_node_t *node) {
        if(!mesh || !node) {
                meshlink_errno = MESHLINK_EINVAL;
-               return -1;
+               return NULL;
        }
 
-       dev_class_t devclass;
-
        if(pthread_mutex_lock(&mesh->mutex) != 0) {
                abort();
        }
 
-       devclass = ((node_t *)node)->devclass;
+       node_t *n = (node_t *)node;
 
-       pthread_mutex_unlock(&mesh->mutex);
+       if(!node_read_public_key(mesh, n) || !n->ecdsa) {
+               meshlink_errno = MESHLINK_EINTERNAL;
+               pthread_mutex_unlock(&mesh->mutex);
+               return false;
+       }
+
+       char *fingerprint = ecdsa_get_base64_public_key(n->ecdsa);
+
+       if(!fingerprint) {
+               meshlink_errno = MESHLINK_EINTERNAL;
+       }
 
-       return devclass;
+       pthread_mutex_unlock(&mesh->mutex);
+       return fingerprint;
 }
 
-meshlink_submesh_t *meshlink_get_node_submesh(meshlink_handle_t *mesh, meshlink_node_t *node) {
-       if(!mesh || !node) {
+meshlink_node_t *meshlink_get_self(meshlink_handle_t *mesh) {
+       if(!mesh) {
                meshlink_errno = MESHLINK_EINVAL;
                return NULL;
        }
 
-       node_t *n = (node_t *)node;
-
-       meshlink_submesh_t *s;
-
-       s = (meshlink_submesh_t *)n->submesh;
-
-       return s;
+       return (meshlink_node_t *)mesh->self;
 }
 
-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_node_t *meshlink_get_node(meshlink_handle_t *mesh, const char *name) {
+       if(!mesh || !name) {
                meshlink_errno = MESHLINK_EINVAL;
                return NULL;
        }
 
-       node_t *n = (node_t *)node;
-       bool reachable;
+       node_t *n = NULL;
 
        if(pthread_mutex_lock(&mesh->mutex) != 0) {
                abort();
        }
 
-       reachable = n->status.reachable && !n->status.blacklisted;
-
-       if(last_reachable) {
-               *last_reachable = n->last_reachable;
-       }
+       n = lookup_node(mesh, (char *)name); // TODO: make lookup_node() use const
+       pthread_mutex_unlock(&mesh->mutex);
 
-       if(last_unreachable) {
-               *last_unreachable = n->last_unreachable;
+       if(!n) {
+               meshlink_errno = MESHLINK_ENOENT;
        }
 
-       pthread_mutex_unlock(&mesh->mutex);
-
-       return reachable;
+       return (meshlink_node_t *)n;
 }
 
 bool meshlink_sign(meshlink_handle_t *mesh, const void *data, size_t len, void *signature, size_t *siglen) {
@@ -2535,124 +1771,6 @@ bool meshlink_clear_canonical_address(meshlink_handle_t *mesh, meshlink_node_t *
        return config_sync(mesh, "current");
 }
 
-bool meshlink_add_address(meshlink_handle_t *mesh, const char *address) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_add_address(%s)", address ? address : "(null)");
-
-       return meshlink_set_canonical_address(mesh, (meshlink_node_t *)mesh->self, address, NULL);
-}
-
-bool meshlink_add_external_address(meshlink_handle_t *mesh) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_add_external_address()");
-
-       if(!mesh) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return false;
-       }
-
-       char *address = meshlink_get_external_address(mesh);
-
-       if(!address) {
-               return false;
-       }
-
-       bool rval = meshlink_set_canonical_address(mesh, (meshlink_node_t *)mesh->self, address, NULL);
-       free(address);
-
-       return rval;
-}
-
-int meshlink_get_port(meshlink_handle_t *mesh) {
-       if(!mesh) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return -1;
-       }
-
-       if(!mesh->myport) {
-               meshlink_errno = MESHLINK_EINTERNAL;
-               return -1;
-       }
-
-       int port;
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       port = atoi(mesh->myport);
-       pthread_mutex_unlock(&mesh->mutex);
-
-       return port;
-}
-
-bool meshlink_set_port(meshlink_handle_t *mesh, int port) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_set_port(%d)", port);
-
-       if(!mesh || port < 0 || port >= 65536 || mesh->threadstarted) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return false;
-       }
-
-       if(mesh->myport && port == atoi(mesh->myport)) {
-               return true;
-       }
-
-       if(!try_bind(mesh, port)) {
-               meshlink_errno = MESHLINK_ENETWORK;
-               return false;
-       }
-
-       devtool_trybind_probe();
-
-       bool rval = false;
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       if(mesh->threadstarted) {
-               meshlink_errno = MESHLINK_EINVAL;
-               goto done;
-       }
-
-       free(mesh->myport);
-       xasprintf(&mesh->myport, "%d", port);
-
-       /* Close down the network. This also deletes mesh->self. */
-       close_network_connections(mesh);
-
-       /* Recreate mesh->self. */
-       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)) {
-               logger(NULL, MESHLINK_ERROR, "Could not read our host configuration file!");
-               meshlink_errno = MESHLINK_ESTORAGE;
-               free_node(mesh->self);
-               mesh->self = NULL;
-               goto done;
-       } else if(!setup_network(mesh)) {
-               meshlink_errno = MESHLINK_ENETWORK;
-               goto done;
-       }
-
-       /* Rebuild our own list of recent addresses */
-       memset(mesh->self->recent, 0, sizeof(mesh->self->recent));
-       add_local_addresses(mesh);
-
-       /* Write meshlink.conf with the updated port number */
-       write_main_config_files(mesh);
-
-       rval = config_sync(mesh, "current");
-
-done:
-       pthread_mutex_unlock(&mesh->mutex);
-
-       return rval && meshlink_get_port(mesh) == port;
-}
-
 bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) {
        logger(mesh, MESHLINK_DEBUG, "meshlink_join(%s)", invitation ? invitation : "(null)");
 
@@ -2689,7 +1807,7 @@ 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;
@@ -2767,7 +1885,7 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) {
                }
 
                // Connect to the meshlink daemon mentioned in the URL.
-               struct addrinfo *ai = adns_blocking_request(mesh, xstrdup(address), xstrdup(port), SOCK_STREAM, 30);
+               struct addrinfo *ai = str2addrinfo(address, port, SOCK_STREAM);
 
                if(ai) {
                        for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) {
@@ -3075,10 +2193,6 @@ bool meshlink_import(meshlink_handle_t *mesh, const char *data) {
                        break;
                }
 
-               /* Clear the reachability times, since we ourself have never seen these nodes yet */
-               n->last_reachable = 0;
-               n->last_unreachable = 0;
-
                if(!node_write_config(mesh, n, true)) {
                        free_node(n);
                        free(buf);
@@ -3105,66 +2219,6 @@ 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)");
-
-       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.
  */
@@ -3350,17 +2404,6 @@ static void channel_accept(struct utcp_connection *utcp_connection, uint16_t por
        }
 }
 
-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;
 
@@ -3539,12 +2582,8 @@ void meshlink_set_channel_accept_cb(meshlink_handle_t *mesh, meshlink_channel_ac
        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);
-               }
+       if(mesh->peer) {
+               mesh->peer->utcp = utcp_init(channel_accept, channel_pre_accept, channel_send, mesh->peer);
        }
 
        pthread_mutex_unlock(&mesh->mutex);
@@ -3630,8 +2669,6 @@ meshlink_channel_t *meshlink_channel_open_ex(meshlink_handle_t *mesh, meshlink_n
 
        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) {
@@ -4007,8 +3044,6 @@ void meshlink_set_node_channel_timeout(meshlink_handle_t *mesh, meshlink_node_t
 
        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);
@@ -4019,25 +3054,11 @@ void meshlink_set_node_channel_timeout(meshlink_handle_t *mesh, meshlink_node_t
 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) {
@@ -4049,36 +3070,6 @@ void handle_duplicate_node(meshlink_handle_t *mesh, node_t *n) {
        mesh->node_duplicate_cb(mesh, (meshlink_node_t *)n);
 }
 
-void meshlink_enable_discovery(meshlink_handle_t *mesh, bool enable) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_enable_discovery(%d)", enable);
-
-       if(!mesh) {
-               meshlink_errno = MESHLINK_EINVAL;
-               return;
-       }
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       if(mesh->discovery.enabled == enable) {
-               goto end;
-       }
-
-       if(mesh->threadstarted) {
-               if(enable) {
-                       discovery_start(mesh);
-               } else {
-                       discovery_stop(mesh);
-               }
-       }
-
-       mesh->discovery.enabled = enable;
-
-end:
-       pthread_mutex_unlock(&mesh->mutex);
-}
-
 void meshlink_hint_network_change(struct meshlink_handle *mesh) {
        logger(mesh, MESHLINK_DEBUG, "meshlink_hint_network_change()");
 
@@ -4091,15 +3082,6 @@ void meshlink_hint_network_change(struct meshlink_handle *mesh) {
                abort();
        }
 
-       if(mesh->discovery.enabled) {
-               scan_ifaddrs(mesh);
-       }
-
-       if(mesh->loop.now.tv_sec > mesh->discovery.last_update + 5) {
-               mesh->discovery.last_update = mesh->loop.now.tv_sec;
-               handle_network_change(mesh, 1);
-       }
-
        pthread_mutex_unlock(&mesh->mutex);
 }
 
@@ -4180,10 +3162,6 @@ void meshlink_reset_timers(struct meshlink_handle *mesh) {
 
        handle_network_change(mesh, true);
 
-       if(mesh->discovery.enabled) {
-               discovery_refresh(mesh);
-       }
-
        pthread_mutex_unlock(&mesh->mutex);
 }
 
@@ -4203,28 +3181,6 @@ void meshlink_set_inviter_commits_first(struct meshlink_handle *mesh, bool invit
        pthread_mutex_unlock(&mesh->mutex);
 }
 
-void meshlink_set_external_address_discovery_url(struct meshlink_handle *mesh, const char *url) {
-       logger(mesh, MESHLINK_DEBUG, "meshlink_set_external_address_discovery_url(%s)", url ? url : "(null)");
-
-       if(!mesh) {
-               meshlink_errno = EINVAL;
-               return;
-       }
-
-       if(url && (strncmp(url, "http://", 7) || strchr(url, ' '))) {
-               meshlink_errno = EINVAL;
-               return;
-       }
-
-       if(pthread_mutex_lock(&mesh->mutex) != 0) {
-               abort();
-       }
-
-       free(mesh->external_address_url);
-       mesh->external_address_url = url ? xstrdup(url) : NULL;
-       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);
 
@@ -4255,7 +3211,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;
        }