X-Git-Url: http://git.meshlink.io/?a=blobdiff_plain;f=src%2Fmeshlink.c;h=1bb403f33d4cbfc7929f29ccb46ede5849a079ff;hb=c99339abe31cf11172fe5f903d70884336272513;hp=fd9b8827dc32f338a9426021a0eedf949e73cf73;hpb=2bd4182c4d960596b54793027a0f42e991eb12dd;p=meshlink diff --git a/src/meshlink.c b/src/meshlink.c index fd9b8827..1bb403f3 100644 --- a/src/meshlink.c +++ b/src/meshlink.c @@ -1,6 +1,6 @@ /* meshlink.c -- Implementation of the MeshLink API. - Copyright (C) 2014, 2017 Guus Sliepen + Copyright (C) 2014-2018 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -95,9 +95,8 @@ static int rstrip(char *value) { return len; } -static void scan_for_hostname(const char *filename, char **hostname, char **port) { +static void scan_for_canonical_address(const char *filename, char **hostname, char **port) { char line[4096]; - bool canonical = false; if(!filename || (*hostname && *port)) { return; @@ -141,21 +140,8 @@ static void scan_for_hostname(const char *filename, char **hostname, char **port if(!*port && !strcasecmp(line, "Port")) { *port = xstrdup(q); - } else if(!canonical && !*hostname && !strcasecmp(line, "Address")) { - // Check that the hostname is a symbolic name (it's not a numeric IPv4 or IPv6 address) - if(!q[strspn(q, "0123456789.")] || strchr(q, ':')) { - continue; - } - - *hostname = xstrdup(q); - - if(*p) { - free(*port); - *port = xstrdup(p); - } } else if(!strcasecmp(line, "CanonicalAddress")) { *hostname = xstrdup(q); - canonical = true; if(*p) { free(*port); @@ -163,7 +149,7 @@ static void scan_for_hostname(const char *filename, char **hostname, char **port } } - if(canonical && *hostname && *port) { + if(*hostname && *port) { break; } } @@ -240,6 +226,7 @@ static bool getlocaladdrname(char *destaddr, char *host, socklen_t hostlen) { } if(connect(sock, rai->ai_addr, rai->ai_addrlen) && !sockwouldblock(errno)) { + closesocket(sock); freeaddrinfo(rai); return false; } @@ -250,9 +237,12 @@ static bool getlocaladdrname(char *destaddr, char *host, socklen_t hostlen) { socklen_t sl = sizeof(sn); if(getsockname(sock, (struct sockaddr *)&sn, &sl)) { + closesocket(sock); return false; } + closesocket(sock); + if(getnameinfo((struct sockaddr *)&sn, sl, host, hostlen, NULL, 0, NI_NUMERICHOST | NI_NUMERICSERV)) { return false; } @@ -324,88 +314,201 @@ char *meshlink_get_external_address_for_family(meshlink_handle_t *mesh, int fami hostname = NULL; } - // If there is no hostname, determine the address used for an outgoing connection. if(!hostname) { - char localaddr[NI_MAXHOST]; - bool success = false; + meshlink_errno = MESHLINK_ERESOLV; + } - if(family == AF_INET) { - success = getlocaladdrname("93.184.216.34", localaddr, sizeof(localaddr)); - } else if(family == AF_INET6) { - success = getlocaladdrname("2606:2800:220:1:248:1893:25c8:1946", localaddr, sizeof(localaddr)); - } + return hostname; +} - if(success) { - hostname = xstrdup(localaddr); - } +char *meshlink_get_local_address_for_family(meshlink_handle_t *mesh, int family) { + (void)mesh; + + // 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)); + } else if(family == AF_INET6) { + success = getlocaladdrname("2606:2800:220:1:248:1893:25c8:1946", localaddr, sizeof(localaddr)); } - if(!hostname) { - meshlink_errno = MESHLINK_ERESOLV; + if(!success) { + meshlink_errno = MESHLINK_ENETWORK; + return NULL; } - return hostname; + return xstrdup(localaddr); +} + +void remove_duplicate_hostnames(char *host[], char *port[], int n) { + for(int i = 0; i < n; i++) { + if(!host[i]) { + continue; + } + + // Ignore duplicate hostnames + bool found = false; + + for(int j = 0; j < i; j++) { + if(!host[j]) { + continue; + } + + if(strcmp(host[i], host[j])) { + continue; + } + + if(strcmp(port[i], port[j])) { + continue; + } + + found = true; + break; + } + + if(found) { + free(host[i]); + free(port[i]); + host[i] = NULL; + port[i] = NULL; + continue; + } + } } // This gets the hostname part for use in invitation URLs -static char *get_my_hostname(meshlink_handle_t *mesh) { - char *hostname[2] = {NULL}; - char *port = NULL; +static char *get_my_hostname(meshlink_handle_t *mesh, uint32_t flags) { + char *hostname[4] = {NULL}; + char *port[4] = {NULL}; char *hostport = NULL; - char *name = mesh->self->name; - char filename[PATH_MAX] = ""; - // Use first Address statement in own host config file - snprintf(filename, sizeof(filename), "%s" SLASH "hosts" SLASH "%s", mesh->confbase, name); - scan_for_hostname(filename, &hostname[0], &port); + if(!(flags & (MESHLINK_INVITE_LOCAL | MESHLINK_INVITE_PUBLIC))) { + flags |= MESHLINK_INVITE_LOCAL | MESHLINK_INVITE_PUBLIC; + } - if(hostname[0]) { - goto done; + if(!(flags & (MESHLINK_INVITE_IPV4 | MESHLINK_INVITE_IPV6))) { + flags |= MESHLINK_INVITE_IPV4 | MESHLINK_INVITE_IPV6; } - hostname[0] = meshlink_get_external_address_for_family(mesh, AF_INET); - hostname[1] = meshlink_get_external_address_for_family(mesh, AF_INET6); + fprintf(stderr, "flags = %u\n", flags); - if(!hostname[0] && !hostname[1]) { - return NULL; + // 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); + } + + if(flags & MESHLINK_INVITE_IPV6) { + hostname[1] = meshlink_get_local_address_for_family(mesh, AF_INET6); + } + } + + // Add public/canonical addresses if requested + if(flags & MESHLINK_INVITE_PUBLIC) { + // Try the CanonicalAddress first + char filename[PATH_MAX] = ""; + snprintf(filename, sizeof(filename), "%s" SLASH "hosts" SLASH "%s", mesh->confbase, mesh->self->name); + scan_for_canonical_address(filename, &hostname[2], &port[2]); + + if(!hostname[2]) { + if(flags & MESHLINK_INVITE_IPV4) { + hostname[2] = 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); + } + } } - if(hostname[0] && hostname[1] && !strcmp(hostname[0], hostname[1])) { - free(hostname[1]); - hostname[1] = NULL; + for(int i = 0; i < 4; i++) { + // Ensure we always have a port number + if(hostname[i] && !port[i]) { + port[i] = xstrdup(mesh->myport); + } } - port = xstrdup(mesh->myport); + remove_duplicate_hostnames(hostname, port, 4); + + if(!(flags & MESHLINK_INVITE_NUMERIC)) { + for(int i = 0; i < 4; i++) { + if(!hostname[i]) { + continue; + } + + // Convert what we have to a sockaddr + struct addrinfo *ai_in, *ai_out; + struct addrinfo hint = { + .ai_family = AF_UNSPEC, + .ai_flags = AI_NUMERICSERV, + .ai_socktype = SOCK_STREAM, + }; + int err = getaddrinfo(hostname[i], port[i], &hint, &ai_in); - for(int i = 0; i < 2; i++) { - if(hostname[i]) { - char *tmphostport; - xasprintf(&tmphostport, "%s %s", hostname[i], port); - append_config_file(mesh, mesh->self->name, "Address", tmphostport); - free(tmphostport); + if(err || !ai_in) { + continue; + } + + // Convert it to a hostname + char resolved_host[NI_MAXHOST]; + char resolved_port[NI_MAXSERV]; + err = getnameinfo(ai_in->ai_addr, ai_in->ai_addrlen, resolved_host, sizeof resolved_host, resolved_port, sizeof resolved_port, NI_NUMERICSERV); + + if(err) { + freeaddrinfo(ai_in); + continue; + } + + // Convert the hostname back to a sockaddr + hint.ai_family = ai_in->ai_family; + err = getaddrinfo(resolved_host, resolved_port, &hint, &ai_out); + + if(err || !ai_out) { + freeaddrinfo(ai_in); + continue; + } + + // Check if it's still the same sockaddr + if(ai_in->ai_addrlen != ai_out->ai_addrlen || memcmp(ai_in->ai_addr, ai_out->ai_addr, ai_in->ai_addrlen)) { + freeaddrinfo(ai_in); + freeaddrinfo(ai_out); + continue; + } + + // Yes: replace the hostname with the resolved one + free(hostname[i]); + hostname[i] = xstrdup(resolved_host); + + freeaddrinfo(ai_in); + freeaddrinfo(ai_out); } } -done: + // Remove duplicates again, since IPv4 and IPv6 addresses might map to the same hostname + remove_duplicate_hostnames(hostname, port, 4); - for(int i = 0; i < 2; i++) { + // Concatenate all unique address to the hostport string + for(int i = 0; i < 4; i++) { if(!hostname[i]) { continue; } - char *newhostport; - xasprintf(&newhostport, (strchr(hostname[i], ':') ? "%s%s[%s]" : "%s%s%s"), hostport ? hostport : "", hostport ? "," : "", hostname[i]); - free(hostname[i]); - free(hostport); - hostport = newhostport; - } + // Ensure we have the same addresses in our own host config file. + char *tmphostport; + xasprintf(&tmphostport, "%s %s", hostname[i], port[i]); + append_config_file(mesh, mesh->self->name, "Address", tmphostport); + free(tmphostport); - if(port) { + // Append the address to the hostport string char *newhostport; - xasprintf(&newhostport, "%s:%s", hostport, port); - free(port); + xasprintf(&newhostport, (strchr(hostname[i], ':') ? "%s%s[%s]:%s" : "%s%s%s:%s"), hostport ? hostport : "", hostport ? "," : "", hostname[i], port[i]); free(hostport); hostport = newhostport; + + free(hostname[i]); + free(port[i]); } return hostport; @@ -870,6 +973,8 @@ static const char *errstr[] = { [MESHLINK_ESTORAGE] = "Storage error", [MESHLINK_ENETWORK] = "Network error", [MESHLINK_EPEER] = "Error communicating with peer", + [MESHLINK_ENOTSUP] = "Operation not supported", + [MESHLINK_EBUSY] = "MeshLink instance already in use", }; const char *meshlink_strerror(meshlink_errno_t err) { @@ -1117,6 +1222,34 @@ meshlink_handle_t *meshlink_open(const char *confbase, const char *name, const c } } + // Open the configuration file and lock it + + mesh->conffile = fopen(filename, "r"); + + if(!mesh->conffile) { + logger(NULL, MESHLINK_ERROR, "Cannot not open %s: %s\n", filename, strerror(errno)); + meshlink_close(mesh); + meshlink_errno = MESHLINK_ESTORAGE; + return NULL; + } + +#ifdef FD_CLOEXEC + fcntl(fileno(mesh->conffile), F_SETFD, FD_CLOEXEC); +#endif + +#ifdef HAVE_MINGW + // TODO: use _locking()? +#else + + if(flock(fileno(mesh->conffile), LOCK_EX | LOCK_NB) != 0) { + logger(NULL, MESHLINK_ERROR, "Cannot lock %s: %s\n", filename, strerror(errno)); + meshlink_close(mesh); + meshlink_errno = MESHLINK_EBUSY; + return NULL; + } + +#endif + // Read the configuration init_configuration(&mesh->config); @@ -1215,10 +1348,14 @@ bool meshlink_start(meshlink_handle_t *mesh) { mesh->threadstarted = true; +#if HAVE_CATTA + if(mesh->discovery) { discovery_start(mesh); } +#endif + pthread_mutex_unlock(&(mesh->mesh_mutex)); return true; } @@ -1232,11 +1369,15 @@ void meshlink_stop(meshlink_handle_t *mesh) { pthread_mutex_lock(&(mesh->mesh_mutex)); logger(mesh, MESHLINK_DEBUG, "meshlink_stop called\n"); +#if HAVE_CATTA + // Stop discovery if(mesh->discovery) { discovery_stop(mesh); } +#endif + // Shut down the main thread event_loop_stop(&mesh->loop); @@ -1318,6 +1459,10 @@ void meshlink_close(meshlink_handle_t *mesh) { free(mesh->confbase); pthread_mutex_destroy(&(mesh->mesh_mutex)); + if(mesh->conffile) { + fclose(mesh->conffile); + } + memset(mesh, 0, sizeof(*mesh)); free(mesh); @@ -1370,6 +1515,17 @@ void meshlink_set_node_status_cb(meshlink_handle_t *mesh, meshlink_node_status_c pthread_mutex_unlock(&(mesh->mesh_mutex)); } +void meshlink_set_node_duplicate_cb(meshlink_handle_t *mesh, meshlink_node_duplicate_cb_t cb) { + if(!mesh) { + meshlink_errno = MESHLINK_EINVAL; + return; + } + + pthread_mutex_lock(&(mesh->mesh_mutex)); + mesh->node_duplicate_cb = cb; + pthread_mutex_unlock(&(mesh->mesh_mutex)); +} + void meshlink_set_log_cb(meshlink_handle_t *mesh, meshlink_log_level_t level, meshlink_log_cb_t cb) { if(mesh) { pthread_mutex_lock(&(mesh->mesh_mutex)); @@ -1857,7 +2013,7 @@ void meshlink_set_invitation_timeout(meshlink_handle_t *mesh, int timeout) { mesh->invitation_timeout = timeout; } -char *meshlink_invite(meshlink_handle_t *mesh, const char *name) { +char *meshlink_invite_ex(meshlink_handle_t *mesh, const char *name, uint32_t flags) { if(!mesh) { meshlink_errno = MESHLINK_EINVAL; return NULL; @@ -1893,7 +2049,7 @@ char *meshlink_invite(meshlink_handle_t *mesh, const char *name) { } // Get the local address - char *address = get_my_hostname(mesh); + char *address = get_my_hostname(mesh, flags); if(!address) { logger(mesh, MESHLINK_DEBUG, "No Address known for ourselves!\n"); @@ -1995,6 +2151,10 @@ char *meshlink_invite(meshlink_handle_t *mesh, const char *name) { return url; } +char *meshlink_invite(meshlink_handle_t *mesh, const char *name) { + return meshlink_invite_ex(mesh, name, 0); +} + bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { if(!mesh || !invitation) { meshlink_errno = MESHLINK_EINVAL; @@ -2015,7 +2175,7 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { char copy[strlen(invitation) + 1]; strcpy(copy, invitation); - // Split the invitation URL into hostname, port, key hash and cookie. + // Split the invitation URL into a list of hostname/port tuples, a key hash and a cookie. char *slash = strchr(copy, '/'); @@ -2030,13 +2190,7 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { } char *address = copy; - char *port = strrchr(address, ':'); - - if(!port) { - goto invalid; - } - - *port++ = 0; + char *port = NULL; if(!b64decode(slash, mesh->hash, 18) || !b64decode(slash + 24, mesh->cookie, 18)) { goto invalid; @@ -2063,6 +2217,15 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { *comma++ = 0; } + // Split of the port + port = strrchr(address, ':'); + + if(!port) { + goto invalid; + } + + *port++ = 0; + // IPv6 address are enclosed in brackets, per RFC 3986 if(*address == '[') { address++; @@ -2074,7 +2237,7 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { *bracket++ = 0; - if(comma && bracket != comma) { + if(*bracket) { goto invalid; } } @@ -2126,7 +2289,7 @@ bool meshlink_join(meshlink_handle_t *mesh, const char *invitation) { mesh->blen = 0; - if(!sendline(mesh->sock, "0 ?%s %d.%d", b64key, PROT_MAJOR, 1)) { + if(!sendline(mesh->sock, "0 ?%s %d.%d %s", b64key, PROT_MAJOR, 1, 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; @@ -2361,8 +2524,14 @@ void meshlink_blacklist(meshlink_handle_t *mesh, meshlink_node_t *node) { //Make blacklisting persistent in the config file append_config_file(mesh, n->name, "blacklisted", "yes"); + //Immediately terminate any connections we have with the blacklisted node + for list_each(connection_t, c, mesh->connections) { + if(c->node == n) { + terminate_connection(mesh, c, c->status.active); + } + } + pthread_mutex_unlock(&(mesh->mesh_mutex)); - return; } void meshlink_whitelist(meshlink_handle_t *mesh, meshlink_node_t *node) { @@ -2652,7 +2821,18 @@ void update_node_status(meshlink_handle_t *mesh, node_t *n) { } } +void handle_duplicate_node(meshlink_handle_t *mesh, node_t *n) { + if(!mesh->node_duplicate_cb || n->status.duplicate) { + return; + } + + n->status.duplicate = true; + mesh->node_duplicate_cb(mesh, (meshlink_node_t *)n); +} + void meshlink_enable_discovery(meshlink_handle_t *mesh, bool enable) { +#if HAVE_CATTA + if(!mesh) { meshlink_errno = MESHLINK_EINVAL; return; @@ -2676,6 +2856,11 @@ void meshlink_enable_discovery(meshlink_handle_t *mesh, bool enable) { end: pthread_mutex_unlock(&mesh->mesh_mutex); +#else + (void)mesh; + (void)enable; + meshlink_errno = MESHLINK_ENOTSUP; +#endif } static void __attribute__((constructor)) meshlink_init(void) {