X-Git-Url: http://git.meshlink.io/?p=meshlink;a=blobdiff_plain;f=src%2Fmeshlink.c;h=9f3b6ecd2094a89c92e68c0e9e62537b55a04518;hp=30b386846a7322e261a1b0f4888331773352fe0c;hb=b70b090a28ff4cbdce29a3ad030f7d51ce9079b2;hpb=529b8fab8c21e7ae5af91d742ff202eab38e51f3 diff --git a/src/meshlink.c b/src/meshlink.c index 30b38684..9f3b6ecd 100644 --- a/src/meshlink.c +++ b/src/meshlink.c @@ -37,6 +37,7 @@ #include "ed25519/sha512.h" #include "discovery.h" #include "devtools.h" +#include "graph.h" #ifndef MSG_NOSIGNAL #define MSG_NOSIGNAL 0 @@ -220,12 +221,42 @@ char *meshlink_get_external_address(meshlink_handle_t *mesh) { } char *meshlink_get_external_address_for_family(meshlink_handle_t *mesh, int family) { - char *hostname = NULL; + 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 = str2addrinfo("meshlink.io", "80", SOCK_STREAM); - static const char request[] = "GET http://www.meshlink.io/host.cgi HTTP/1.0\r\n\r\n"; + struct addrinfo *ai = str2addrinfo(host, port ? port : "80", SOCK_STREAM); char line[256]; + char *hostname = NULL; for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) { if(family != AF_UNSPEC && aip->ai_family != family) { @@ -244,7 +275,9 @@ char *meshlink_get_external_address_for_family(meshlink_handle_t *mesh, int fami } if(s >= 0) { - send(s, request, sizeof(request) - 1, 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) { @@ -286,6 +319,21 @@ char *meshlink_get_external_address_for_family(meshlink_handle_t *mesh, int fami 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; + } +} + char *meshlink_get_local_address_for_family(meshlink_handle_t *mesh, int family) { (void)mesh; @@ -299,6 +347,34 @@ char *meshlink_get_local_address_for_family(meshlink_handle_t *mesh, int family) 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(&ifa); + + for(struct ifaddrs *ifap = ifa; ifap; ifap = ifap->ifa_next) { + sockaddr_t *sa = (sockaddr_t *)ifap->ifa_addr; + + if(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; @@ -345,10 +421,15 @@ void remove_duplicate_hostnames(char *host[], char *port[], int n) { // This gets the hostname part for use in invitation URLs static char *get_my_hostname(meshlink_handle_t *mesh, uint32_t flags) { - char *hostname[4] = {NULL}; - char *port[4] = {NULL}; + int count = 4 + (mesh->invitation_addresses ? mesh->invitation_addresses->count : 0); + int n = 0; + char *hostname[count]; + char *port[count]; char *hostport = NULL; + memset(hostname, 0, sizeof(hostname)); + memset(port, 0, sizeof(port)); + if(!(flags & (MESHLINK_INVITE_LOCAL | MESHLINK_INVITE_PUBLIC))) { flags |= MESHLINK_INVITE_LOCAL | MESHLINK_INVITE_PUBLIC; } @@ -357,44 +438,61 @@ static char *get_my_hostname(meshlink_handle_t *mesh, uint32_t flags) { flags |= MESHLINK_INVITE_IPV4 | MESHLINK_INVITE_IPV6; } + // Add all explicitly set invitation addresses + if(mesh->invitation_addresses) { + for list_each(char, combo, mesh->invitation_addresses) { + hostname[n] = xstrdup(combo); + char *slash = strrchr(hostname[n], '/'); + + if(slash) { + *slash = 0; + port[n] = xstrdup(slash + 1); + } + + n++; + } + } + // Add local addresses if requested if(flags & MESHLINK_INVITE_LOCAL) { if(flags & MESHLINK_INVITE_IPV4) { - hostname[0] = meshlink_get_local_address_for_family(mesh, AF_INET); + hostname[n++] = meshlink_get_local_address_for_family(mesh, AF_INET); } if(flags & MESHLINK_INVITE_IPV6) { - hostname[1] = meshlink_get_local_address_for_family(mesh, AF_INET6); + hostname[n++] = meshlink_get_local_address_for_family(mesh, AF_INET6); } } // Add public/canonical addresses if requested if(flags & MESHLINK_INVITE_PUBLIC) { // Try the CanonicalAddress first - get_canonical_address(mesh->self, &hostname[2], &port[2]); + get_canonical_address(mesh->self, &hostname[n], &port[n]); - if(!hostname[2]) { + if(!hostname[n] && count == 4) { if(flags & MESHLINK_INVITE_IPV4) { - hostname[2] = meshlink_get_external_address_for_family(mesh, AF_INET); + hostname[n++] = meshlink_get_external_address_for_family(mesh, AF_INET); } if(flags & MESHLINK_INVITE_IPV6) { - hostname[3] = meshlink_get_external_address_for_family(mesh, AF_INET6); + hostname[n++] = meshlink_get_external_address_for_family(mesh, AF_INET6); } + } else { + n++; } } - for(int i = 0; i < 4; i++) { + for(int i = 0; i < n; i++) { // Ensure we always have a port number if(hostname[i] && !port[i]) { port[i] = xstrdup(mesh->myport); } } - remove_duplicate_hostnames(hostname, port, 4); + remove_duplicate_hostnames(hostname, port, n); // Resolve the hostnames - for(int i = 0; i < 4; i++) { + for(int i = 0; i < n; i++) { if(!hostname[i]) { continue; } @@ -412,8 +510,10 @@ static char *get_my_hostname(meshlink_handle_t *mesh, uint32_t flags) { continue; } - // Remember the address - node_add_recent_address(mesh, mesh->self, (sockaddr_t *)ai_in->ai_addr); + // Remember the address(es) + for(struct addrinfo *aip = ai_in; aip; aip = aip->ai_next) { + node_add_recent_address(mesh, mesh->self, (sockaddr_t *)aip->ai_addr); + } if(flags & MESHLINK_INVITE_NUMERIC) { // We don't need to do any further conversion @@ -456,10 +556,10 @@ static char *get_my_hostname(meshlink_handle_t *mesh, uint32_t flags) { } // Remove duplicates again, since IPv4 and IPv6 addresses might map to the same hostname - remove_duplicate_hostnames(hostname, port, 4); + remove_duplicate_hostnames(hostname, port, n); // Concatenate all unique address to the hostport string - for(int i = 0; i < 4; i++) { + for(int i = 0; i < n; i++) { if(!hostname[i]) { continue; } @@ -477,7 +577,7 @@ static char *get_my_hostname(meshlink_handle_t *mesh, uint32_t flags) { return hostport; } -static bool try_bind(int port) { +static bool try_bind(meshlink_handle_t *mesh, int port) { struct addrinfo *ai = NULL; struct addrinfo hint = { .ai_flags = AI_PASSIVE, @@ -493,33 +593,47 @@ static bool try_bind(int port) { return false; } - //while(ai) { + bool success = false; + for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) { - int fd = socket(aip->ai_family, SOCK_STREAM, IPPROTO_TCP); + /* Try to bind to TCP. */ - if(!fd) { - freeaddrinfo(ai); - return false; + 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; + } } - int result = bind(fd, aip->ai_addr, aip->ai_addrlen); - closesocket(fd); + /* If TCP worked, then we require that UDP works as well. */ - if(result) { - freeaddrinfo(ai); - return false; + 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 true; + return success; } -static int check_port(meshlink_handle_t *mesh) { +int check_port(meshlink_handle_t *mesh) { for(int i = 0; i < 1000; i++) { int port = 0x1000 + prng(mesh, 0x8000); - if(try_bind(port)) { + if(try_bind(mesh, port)) { free(mesh->myport); xasprintf(&mesh->myport, "%d", port); return port; @@ -565,7 +679,22 @@ static bool write_main_config_files(meshlink_handle_t *mesh) { return true; } -static bool finalize_join(meshlink_handle_t *mesh, const void *buf, uint16_t len) { +typedef struct { + meshlink_handle_t *mesh; + int sock; + char cookie[18 + 32]; + char hash[18]; + bool success; + sptps_t sptps; + char *data; + size_t thedatalen; + size_t blen; + char line[4096]; + char buffer[4096]; +} join_state_t; + +static bool finalize_join(join_state_t *state, const void *buf, uint16_t len) { + meshlink_handle_t *mesh = state->mesh; packmsg_input_t in = {buf, len}; uint32_t version = packmsg_get_uint32(&in); @@ -662,11 +791,15 @@ static bool finalize_join(meshlink_handle_t *mesh, const void *buf, uint16_t len sockaddr_t sa; socklen_t salen = sizeof(sa); - if(getpeername(mesh->sock, &sa.sa, &salen) == 0) { + if(getpeername(state->sock, &sa.sa, &salen) == 0) { node_add_recent_address(mesh, n, &sa); } } + /* 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)) { free_node(n); return false; @@ -680,7 +813,11 @@ static bool finalize_join(meshlink_handle_t *mesh, const void *buf, uint16_t len return false; } - sptps_send_record(&mesh->sptps, 1, ecdsa_get_public_key(mesh->private_key), 32); + if(!mesh->inviter_commits_first) { + devtool_set_inviter_commits_first(false); + } + + 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); @@ -689,11 +826,11 @@ static bool finalize_join(meshlink_handle_t *mesh, const void *buf, uint16_t len static bool invitation_send(void *handle, uint8_t type, const void *data, size_t len) { (void)type; - meshlink_handle_t *mesh = handle; + join_state_t *state = handle; const char *ptr = data; while(len) { - int result = send(mesh->sock, ptr, len, 0); + int result = send(state->sock, ptr, len, 0); if(result == -1 && errno == EINTR) { continue; @@ -709,37 +846,57 @@ static bool invitation_send(void *handle, uint8_t type, const void *data, size_t } static bool invitation_receive(void *handle, uint8_t type, const void *msg, uint16_t len) { - meshlink_handle_t *mesh = handle; + join_state_t *state = handle; + meshlink_handle_t *mesh = state->mesh; - switch(type) { - case SPTPS_HANDSHAKE: - return sptps_send_record(&mesh->sptps, 0, mesh->cookie, sizeof(mesh)->cookie); + if(mesh->inviter_commits_first) { + switch(type) { + case SPTPS_HANDSHAKE: + return sptps_send_record(&state->sptps, 2, state->cookie, 18 + 32); - case 0: - return finalize_join(mesh, msg, len); + case 1: + break; - case 1: - logger(mesh, MESHLINK_DEBUG, "Invitation succesfully accepted.\n"); - shutdown(mesh->sock, SHUT_RDWR); - mesh->success = true; - break; + case 0: + if(!finalize_join(state, msg, len)) { + return false; + } - default: - return false; + logger(mesh, MESHLINK_DEBUG, "Invitation successfully accepted.\n"); + shutdown(state->sock, SHUT_RDWR); + state->success = true; + break; + + default: + return false; + } + } else { + switch(type) { + case SPTPS_HANDSHAKE: + return sptps_send_record(&state->sptps, 0, state->cookie, 18); + + case 0: + return finalize_join(state, msg, len); + + case 1: + logger(mesh, MESHLINK_DEBUG, "Invitation successfully accepted.\n"); + shutdown(state->sock, SHUT_RDWR); + state->success = true; + break; + + default: + return false; + } } return true; } -static bool recvline(meshlink_handle_t *mesh, size_t len) { +static bool recvline(join_state_t *state) { char *newline = NULL; - if(!mesh->sock) { - abort(); - } - - while(!(newline = memchr(mesh->buffer, '\n', mesh->blen))) { - int result = recv(mesh->sock, mesh->buffer + mesh->blen, sizeof(mesh)->buffer - mesh->blen, 0); + while(!(newline = memchr(state->buffer, '\n', state->blen))) { + int result = recv(state->sock, state->buffer + state->blen, sizeof(state)->buffer - state->blen, 0); if(result == -1 && errno == EINTR) { continue; @@ -747,19 +904,19 @@ static bool recvline(meshlink_handle_t *mesh, size_t len) { return false; } - mesh->blen += result; + state->blen += result; } - if((size_t)(newline - mesh->buffer) >= len) { + if((size_t)(newline - state->buffer) >= sizeof(state->line)) { return false; } - len = newline - mesh->buffer; + size_t len = newline - state->buffer; - memcpy(mesh->line, mesh->buffer, len); - mesh->line[len] = 0; - memmove(mesh->buffer, newline + 1, mesh->blen - len - 1); - mesh->blen -= len + 1; + memcpy(state->line, state->buffer, len); + state->line[len] = 0; + memmove(state->buffer, newline + 1, state->blen - len - 1); + state->blen -= len + 1; return true; } @@ -1471,9 +1628,6 @@ static void *meshlink_main_loop(void *arg) { } bool meshlink_start(meshlink_handle_t *mesh) { - assert(mesh->self); - assert(mesh->private_key); - if(!mesh) { meshlink_errno = MESHLINK_EINVAL; return false; @@ -1483,6 +1637,8 @@ bool meshlink_start(meshlink_handle_t *mesh) { pthread_mutex_lock(&mesh->mutex); + assert(mesh->self); + assert(mesh->private_key); assert(mesh->self->ecdsa); assert(!memcmp((uint8_t *)mesh->self->ecdsa + 64, (uint8_t *)mesh->private_key + 64, 32)); @@ -1498,8 +1654,6 @@ bool meshlink_start(meshlink_handle_t *mesh) { return false; } - mesh->thedatalen = 0; - // TODO: open listening sockets first //Check that a valid name is set @@ -1527,8 +1681,9 @@ bool meshlink_start(meshlink_handle_t *mesh) { pthread_cond_wait(&mesh->cond, &mesh->mutex); mesh->threadstarted = true; - mesh->self->last_reachable = time(NULL); - mesh->self->status.dirty = true; + + // Ensure we are considered reachable + graph(mesh); pthread_mutex_unlock(&mesh->mutex); return true; @@ -1543,11 +1698,6 @@ void meshlink_stop(meshlink_handle_t *mesh) { pthread_mutex_lock(&mesh->mutex); logger(mesh, MESHLINK_DEBUG, "meshlink_stop called\n"); - if(mesh->self) { - mesh->self->last_unreachable = time(NULL); - mesh->self->status.dirty = true; - } - // Shut down the main thread event_loop_stop(&mesh->loop); @@ -1587,6 +1737,11 @@ void meshlink_stop(meshlink_handle_t *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) { @@ -1643,8 +1798,13 @@ void meshlink_close(meshlink_handle_t *mesh) { free(mesh->appname); free(mesh->confbase); free(mesh->config_key); + free(mesh->external_address_url); ecdsa_free(mesh->private_key); + if(mesh->invitation_addresses) { + list_delete_list(mesh->invitation_addresses); + } + main_config_unlock(mesh); pthread_mutex_unlock(&mesh->mutex); @@ -2143,6 +2303,31 @@ meshlink_submesh_t *meshlink_get_node_submesh(meshlink_handle_t *mesh, meshlink_ 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; + + pthread_mutex_lock(&mesh->mutex); + reachable = n->status.reachable && !n->status.blacklisted; + + if(last_reachable) { + *last_reachable = n->last_reachable; + } + + if(last_unreachable) { + *last_unreachable = n->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) { if(!mesh || !data || !len || !signature || !siglen) { meshlink_errno = MESHLINK_EINVAL; @@ -2168,7 +2353,7 @@ bool meshlink_sign(meshlink_handle_t *mesh, const void *data, size_t len, void * } bool meshlink_verify(meshlink_handle_t *mesh, meshlink_node_t *source, const void *data, size_t len, const void *signature, size_t siglen) { - if(!mesh || !data || !len || !signature) { + if(!mesh || !source || !data || !len || !signature) { meshlink_errno = MESHLINK_EINVAL; return false; } @@ -2251,6 +2436,60 @@ bool meshlink_set_canonical_address(meshlink_handle_t *mesh, meshlink_node_t *no return config_sync(mesh, "current"); } +bool meshlink_add_invitation_address(struct meshlink_handle *mesh, const char *address, const char *port) { + if(!mesh || !address) { + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(!is_valid_hostname(address)) { + logger(mesh, MESHLINK_DEBUG, "Invalid character in address: %s\n", address); + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + if(port && !is_valid_port(port)) { + logger(mesh, MESHLINK_DEBUG, "Invalid character in port: %s\n", address); + meshlink_errno = MESHLINK_EINVAL; + return false; + } + + char *combo; + + if(port) { + xasprintf(&combo, "%s/%s", address, port); + } else { + combo = xstrdup(address); + } + + pthread_mutex_lock(&mesh->mutex); + + if(!mesh->invitation_addresses) { + mesh->invitation_addresses = list_alloc((list_action_t)free); + } + + list_insert_tail(mesh->invitation_addresses, combo); + pthread_mutex_unlock(&mesh->mutex); + + return true; +} + +void meshlink_clear_invitation_addresses(struct meshlink_handle *mesh) { + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + pthread_mutex_lock(&mesh->mutex); + + if(mesh->invitation_addresses) { + list_delete_list(mesh->invitation_addresses); + mesh->invitation_addresses = NULL; + } + + pthread_mutex_unlock(&mesh->mutex); +} + bool meshlink_add_address(meshlink_handle_t *mesh, const char *address) { return meshlink_set_canonical_address(mesh, (meshlink_node_t *)mesh->self, address, NULL); } @@ -2267,7 +2506,7 @@ bool meshlink_add_external_address(meshlink_handle_t *mesh) { return false; } - bool rval = meshlink_add_address(mesh, address); + bool rval = meshlink_set_canonical_address(mesh, (meshlink_node_t *)mesh->self, address, NULL); free(address); return rval; @@ -2303,7 +2542,7 @@ bool meshlink_set_port(meshlink_handle_t *mesh, int port) { return true; } - if(!try_bind(port)) { + if(!try_bind(mesh, port)) { meshlink_errno = MESHLINK_ENETWORK; return false; } @@ -2401,7 +2640,7 @@ char *meshlink_invite_ex(meshlink_handle_t *mesh, meshlink_submesh_t *submesh, c } // Ensure no other nodes know about this name - if(meshlink_get_node(mesh, name)) { + if(lookup_node(mesh, name)) { logger(mesh, MESHLINK_ERROR, "A node with name %s is already known!\n", name); meshlink_errno = MESHLINK_EEXIST; pthread_mutex_unlock(&mesh->mutex); @@ -2427,7 +2666,7 @@ char *meshlink_invite_ex(meshlink_handle_t *mesh, meshlink_submesh_t *submesh, c // If we changed our own host config file, write it out now if(mesh->self->status.dirty) { if(!node_write_config(mesh, mesh->self)) { - logger(mesh, MESHLINK_ERROR, "Could not write our own host conifg file!\n"); + logger(mesh, MESHLINK_ERROR, "Could not write our own host config file!\n"); pthread_mutex_unlock(&mesh->mutex); return NULL; } @@ -2513,26 +2752,33 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { return false; } + join_state_t state = { + .mesh = mesh, + .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]; + pthread_mutex_lock(&mesh->mutex); //Before doing meshlink_join make sure we are not connected to another mesh if(mesh->threadstarted) { logger(mesh, MESHLINK_ERROR, "Cannot join while started\n"); meshlink_errno = MESHLINK_EINVAL; - pthread_mutex_unlock(&mesh->mutex); - return false; + goto exit; } // 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) { logger(mesh, MESHLINK_ERROR, "Already part of an existing mesh\n"); meshlink_errno = MESHLINK_EINVAL; - pthread_mutex_unlock(&mesh->mutex); - return false; + goto exit; } - //TODO: think of a better name for this variable, or of a different way to tokenize the invitation URL. - char copy[strlen(invitation) + 1]; strcpy(copy, invitation); // Split the invitation URL into a list of hostname/port tuples, a key hash and a cookie. @@ -2552,22 +2798,24 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { char *address = copy; char *port = NULL; - if(!b64decode(slash, mesh->hash, 18) || !b64decode(slash + 24, mesh->cookie, 18)) { + if(!b64decode(slash, state.hash, 18) || !b64decode(slash + 24, state.cookie, 18)) { goto invalid; } + if(mesh->inviter_commits_first) { + memcpy(state.cookie + 18, ecdsa_get_public_key(mesh->private_key), 32); + } + // Generate a throw-away key for the invitation. - ecdsa_t *key = ecdsa_generate(); + key = ecdsa_generate(); if(!key) { meshlink_errno = MESHLINK_EINTERNAL; - pthread_mutex_unlock(&mesh->mutex); - return false; + goto exit; } char *b64key = ecdsa_get_base64_public_key(key); char *comma; - mesh->sock = -1; while(address && *address) { // We allow commas in the address part to support multiple addresses in one invitation URL. @@ -2607,23 +2855,25 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { if(ai) { for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) { - mesh->sock = socket_in_netns(aip->ai_family, aip->ai_socktype, aip->ai_protocol, mesh->netns); + state.sock = socket_in_netns(aip->ai_family, aip->ai_socktype, aip->ai_protocol, mesh->netns); - if(mesh->sock == -1) { + if(state.sock == -1) { logger(mesh, MESHLINK_DEBUG, "Could not open socket: %s\n", strerror(errno)); meshlink_errno = MESHLINK_ENETWORK; continue; } - set_timeout(mesh->sock, 5000); + set_timeout(state.sock, 5000); - if(connect(mesh->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(mesh->sock); - mesh->sock = -1; + closesocket(state.sock); + state.sock = -1; continue; } + + break; } freeaddrinfo(ai); @@ -2631,30 +2881,27 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { meshlink_errno = MESHLINK_ERESOLV; } - if(mesh->sock != -1 || !comma) { + if(state.sock != -1 || !comma) { break; } address = comma; } - if(mesh->sock == -1) { - pthread_mutex_unlock(&mesh->mutex); - return false; + if(state.sock == -1) { + goto exit; } logger(mesh, MESHLINK_DEBUG, "Connected to %s port %s...\n", address, port); // Tell him we have an invitation, and give him our throw-away key. - mesh->blen = 0; + state.blen = 0; - if(!sendline(mesh->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_DEBUG, "Error sending request to %s port %s: %s\n", address, port, strerror(errno)); - closesocket(mesh->sock); meshlink_errno = MESHLINK_ENETWORK; - pthread_mutex_unlock(&mesh->mutex); - return false; + goto exit; } free(b64key); @@ -2662,58 +2909,51 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { char hisname[4096] = ""; int code, hismajor, hisminor = 0; - if(!recvline(mesh, sizeof(mesh)->line) || sscanf(mesh->line, "%d %s %d.%d", &code, hisname, &hismajor, &hisminor) < 3 || code != 0 || hismajor != PROT_MAJOR || !check_id(hisname) || !recvline(mesh, sizeof(mesh)->line) || !rstrip(mesh->line) || sscanf(mesh->line, "%d ", &code) != 1 || code != ACK || strlen(mesh->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_DEBUG, "Cannot read greeting from peer\n"); - closesocket(mesh->sock); meshlink_errno = MESHLINK_ENETWORK; - pthread_mutex_unlock(&mesh->mutex); - return false; + goto exit; } // Check if the hash of the key he gave us matches the hash in the URL. - char *fingerprint = mesh->line + 2; + char *fingerprint = state.line + 2; char hishash[64]; if(sha512(fingerprint, strlen(fingerprint), hishash)) { - logger(mesh, MESHLINK_DEBUG, "Could not create hash\n%s\n", mesh->line + 2); + logger(mesh, MESHLINK_DEBUG, "Could not create hash\n%s\n", state.line + 2); meshlink_errno = MESHLINK_EINTERNAL; - pthread_mutex_unlock(&mesh->mutex); - return false; + goto exit; } - if(memcmp(hishash, mesh->hash, 18)) { - logger(mesh, MESHLINK_DEBUG, "Peer has an invalid key!\n%s\n", mesh->line + 2); + if(memcmp(hishash, state.hash, 18)) { + logger(mesh, MESHLINK_DEBUG, "Peer has an invalid key!\n%s\n", state.line + 2); meshlink_errno = MESHLINK_EPEER; - pthread_mutex_unlock(&mesh->mutex); - return false; - + goto exit; } - ecdsa_t *hiskey = ecdsa_set_base64_public_key(fingerprint); + hiskey = ecdsa_set_base64_public_key(fingerprint); if(!hiskey) { meshlink_errno = MESHLINK_EINTERNAL; - pthread_mutex_unlock(&mesh->mutex); - return false; + goto exit; } // Start an SPTPS session - if(!sptps_start(&mesh->sptps, mesh, 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; - pthread_mutex_unlock(&mesh->mutex); - return false; + goto exit; } // Feed rest of input buffer to SPTPS - if(!sptps_receive_data(&mesh->sptps, mesh->buffer, mesh->blen)) { + if(!sptps_receive_data(&state.sptps, state.buffer, state.blen)) { meshlink_errno = MESHLINK_EPEER; - pthread_mutex_unlock(&mesh->mutex); - return false; + goto exit; } - int len; + ssize_t len; + logger(mesh, MESHLINK_DEBUG, "Starting invitation recv loop: %d %zu\n", state.sock, sizeof(state.line)); - while((len = recv(mesh->sock, mesh->line, sizeof(mesh)->line, 0))) { + while((len = recv(state.sock, state.line, sizeof(state.line), 0))) { if(len < 0) { if(errno == EINTR) { continue; @@ -2721,35 +2961,41 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { logger(mesh, MESHLINK_DEBUG, "Error reading data from %s port %s: %s\n", address, port, strerror(errno)); meshlink_errno = MESHLINK_ENETWORK; - pthread_mutex_unlock(&mesh->mutex); - return false; + goto exit; } - if(!sptps_receive_data(&mesh->sptps, mesh->line, len)) { + if(!sptps_receive_data(&state.sptps, state.line, len)) { meshlink_errno = MESHLINK_EPEER; - pthread_mutex_unlock(&mesh->mutex); - return false; + goto exit; } } - sptps_stop(&mesh->sptps); - ecdsa_free(hiskey); - ecdsa_free(key); - closesocket(mesh->sock); - - if(!mesh->success) { + if(!state.success) { logger(mesh, MESHLINK_DEBUG, "Connection closed by peer, invitation cancelled.\n"); meshlink_errno = MESHLINK_EPEER; - pthread_mutex_unlock(&mesh->mutex); - return false; + goto exit; } + sptps_stop(&state.sptps); + ecdsa_free(hiskey); + ecdsa_free(key); + closesocket(state.sock); + pthread_mutex_unlock(&mesh->mutex); return true; invalid: logger(mesh, MESHLINK_DEBUG, "Invalid invitation URL\n"); meshlink_errno = MESHLINK_EINVAL; +exit: + sptps_stop(&state.sptps); + ecdsa_free(hiskey); + ecdsa_free(key); + + if(state.sock != -1) { + closesocket(state.sock); + } + pthread_mutex_unlock(&mesh->mutex); return false; } @@ -2892,7 +3138,11 @@ bool meshlink_import(meshlink_handle_t *mesh, const char *data) { break; } - if(!config_write(mesh, "current", n->name, &config, mesh->config_key)) { + /* 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)) { free_node(n); return false; } @@ -2948,6 +3198,10 @@ static bool blacklist(meshlink_handle_t *mesh, node_t *n) { n->mtuprobes = 0; n->status.udp_confirmed = false; + if(n->status.reachable) { + n->last_unreachable = mesh->loop.now.tv_sec; + } + /* Graph updates will suppress status updates for blacklisted nodes, so we need to * manually call the status callback if necessary. */ @@ -3019,6 +3273,7 @@ static bool whitelist(meshlink_handle_t *mesh, node_t *n) { n->status.blacklisted = false; if(n->status.reachable) { + n->last_reachable = mesh->loop.now.tv_sec; update_node_status(mesh, n); } @@ -3823,6 +4078,34 @@ void meshlink_set_dev_class_fast_retry_period(meshlink_handle_t *mesh, dev_class pthread_mutex_unlock(&mesh->mutex); } +extern void meshlink_set_inviter_commits_first(struct meshlink_handle *mesh, bool inviter_commits_first) { + if(!mesh) { + meshlink_errno = EINVAL; + return; + } + + pthread_mutex_lock(&mesh->mutex); + mesh->inviter_commits_first = inviter_commits_first; + pthread_mutex_unlock(&mesh->mutex); +} + +void meshlink_set_external_address_discovery_url(struct meshlink_handle *mesh, const char *url) { + if(!mesh) { + meshlink_errno = EINVAL; + return; + } + + if(url && (strncmp(url, "http://", 7) || strchr(url, ' '))) { + meshlink_errno = EINVAL; + return; + } + + pthread_mutex_lock(&mesh->mutex); + free(mesh->external_address_url); + mesh->external_address_url = url ? xstrdup(url) : NULL; + pthread_mutex_unlock(&mesh->mutex); +} + void handle_network_change(meshlink_handle_t *mesh, bool online) { (void)online; @@ -3848,7 +4131,6 @@ void call_error_cb(meshlink_handle_t *mesh, meshlink_errno_t meshlink_errno) { } } - static void __attribute__((constructor)) meshlink_init(void) { crypto_init(); }