From 1a7277c64d74ae5ae18c48ebe09a522ffe7c696b Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Tue, 9 Oct 2018 23:27:05 +0200 Subject: [PATCH] Really add both local and external address to the invitation URL. Also make sure we add both IPv4 and IPv6 if possible, use numeric addresses unless hostnames resolve back to the same numeric address. Add meshlink_invite_ex() that takes a flag that makes URL generation more configurable. --- src/meshlink++.h | 27 +++++- src/meshlink.c | 232 ++++++++++++++++++++++++++++++++++------------- src/meshlink.h | 52 ++++++++++- 3 files changed, 243 insertions(+), 68 deletions(-) diff --git a/src/meshlink++.h b/src/meshlink++.h index 4179a9ec..16a6e366 100644 --- a/src/meshlink++.h +++ b/src/meshlink++.h @@ -446,6 +446,28 @@ public: return meshlink_get_external_address_for_family(handle, family); } + /** This function performs tries to discover the address of the local interface used for outgoing connection. + * + * Please note that this is function only returns a single address, + * even if the local node might have more than one external address. + * In that case, there is no control over which address will be selected. + * Also note that if you have a dynamic IP address, or are behind carrier-grade NAT, + * there is no guarantee that the external address will be valid for an extended period of time. + * + * This function will fail if it couldn't find a local address for the given address family. + * If hostname resolving is requested, this function may block for a few seconds. + * + * @param family The address family to check, for example AF_INET or AF_INET6. If AF_UNSPEC is given, + * this might return the external address for any working address family. + * + * @return This function returns a pointer to a C string containing the discovered external address, + * or NULL if there was an error looking up the address. + * After get_external_address() returns, the application is free to overwrite or free this string. + */ + bool get_local_address(int family = AF_UNSPEC) { + return meshlink_get_local_address_for_family(handle, family); + } + /// Try to discover the external address for the local node, and add it to its list of addresses. /** This function is equivalent to: * @@ -505,12 +527,13 @@ public: * The URL can only be used once, after the user has joined the mesh the URL is no longer valid. * * @param name The name that the invitee will use in the mesh. + * @param flags A bitwise-or'd combination of flags that controls how the URL is generated. * * @return This function returns a string that contains the invitation URL. * The application should call free() after it has finished using the URL. */ - char *invite(const char *name) { - return meshlink_invite(handle, name); + char *invite(const char *name, uint32_t flags = 0) { + return meshlink_invite_ex(handle, name, flags); } /// Use an invitation to join a mesh. diff --git a/src/meshlink.c b/src/meshlink.c index b407e5b5..8da56922 100644 --- a/src/meshlink.c +++ b/src/meshlink.c @@ -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; } } @@ -324,89 +310,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); } -// String comparison which handles NULL arguments -static bool safe_streq(const char *a, const char *b) { - if(!a || !b) { - return a == b; - } else { - return !strcmp(a, b); +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[3] = {NULL}; - char *port[3] = {NULL}; +static char *get_my_hostname(meshlink_handle_t *mesh, uint32_t flags) { + char *hostname[4] = {NULL}; + char *port[4] = {NULL}; char *hostport = NULL; - // Use the best Address statement in our own host config file - char filename[PATH_MAX] = ""; - snprintf(filename, sizeof(filename), "%s" SLASH "hosts" SLASH "%s", mesh->confbase, mesh->self->name); - scan_for_hostname(filename, &hostname[0], &port[0]); + if(!(flags & (MESHLINK_INVITE_LOCAL | MESHLINK_INVITE_PUBLIC))) { + flags |= MESHLINK_INVITE_LOCAL | MESHLINK_INVITE_PUBLIC; + } - hostname[1] = meshlink_get_external_address_for_family(mesh, AF_INET); - hostname[2] = meshlink_get_external_address_for_family(mesh, AF_INET6); + if(!(flags & (MESHLINK_INVITE_IPV4 | MESHLINK_INVITE_IPV6))) { + flags |= MESHLINK_INVITE_IPV4 | MESHLINK_INVITE_IPV6; + } - // Concatenate all unique address to the hostport string - for(int i = 0; i < 3; i++) { - if(!hostname[i]) { - continue; + fprintf(stderr, "flags = %u\n", flags); + + // 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); } - // Ignore duplicate hostnames - bool found = false; + if(flags & MESHLINK_INVITE_IPV6) { + hostname[1] = meshlink_get_local_address_for_family(mesh, AF_INET6); + } + } - for(int j = 0; i < j; j++) { - if(safe_streq(hostname[i], hostname[j]) && safe_streq(port[i], port[j])) { - found = true; - break; + // 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(found) { + for(int i = 0; i < 4; i++) { + // Ensure we always have a port number + if(hostname[i] && !port[i]) { + port[i] = 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); + + 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]); - free(port[i]); - hostname[i] = NULL; - port[i] = NULL; + hostname[i] = xstrdup(resolved_host); + + freeaddrinfo(ai_in); + freeaddrinfo(ai_out); + } + } + + // Remove duplicates again, since IPv4 and IPv6 addresses might map to the same hostname + remove_duplicate_hostnames(hostname, port, 4); + + // Concatenate all unique address to the hostport string + for(int i = 0; i < 4; i++) { + if(!hostname[i]) { continue; } // Ensure we have the same addresses in our own host config file. char *tmphostport; - xasprintf(&tmphostport, "%s %s", hostname[i], port[i] ? port[i] : mesh->myport); + xasprintf(&tmphostport, "%s %s", hostname[i], port[i]); append_config_file(mesh, mesh->self->name, "Address", tmphostport); free(tmphostport); // Append the address to the hostport string char *newhostport; - xasprintf(&newhostport, (strchr(hostname[i], ':') ? "%s%s[%s]:%s" : "%s%s%s:%s"), hostport ? hostport : "", hostport ? "," : "", hostname[i], port[i] ? port[i] : mesh->myport); - free(hostname[i]); - free(port[i]); + 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; @@ -1911,7 +2009,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; @@ -1947,7 +2045,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"); @@ -2049,6 +2147,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; diff --git a/src/meshlink.h b/src/meshlink.h index 2f2e7f84..74ebae5d 100644 --- a/src/meshlink.h +++ b/src/meshlink.h @@ -76,6 +76,13 @@ typedef enum { _DEV_CLASS_MAX = 3 } dev_class_t; +/// Invitation flags +static const uint32_t MESHLINK_INVITE_LOCAL = 1; // Only use local addresses in the URL +static const uint32_t MESHLINK_INVITE_PUBLIC = 2; // Only use public or canonical addresses in the URL +static const uint32_t MESHLINK_INVITE_IPV4 = 4; // Only use IPv4 addresses in the URL +static const uint32_t MESHLINK_INVITE_IPV6 = 8; // Only use IPv6 addresses in the URL +static const uint32_t MESHLINK_INVITE_NUMERIC = 16; // Don't look up hostnames + /// Channel flags static const uint32_t MESHLINK_CHANNEL_RELIABLE = 1; // Data is retransmitted when packets are lost. static const uint32_t MESHLINK_CHANNEL_ORDERED = 2; // Data is delivered in-order to the application. @@ -498,10 +505,32 @@ extern char *meshlink_get_external_address(meshlink_handle_t *mesh); * * @return This function returns a pointer to a C string containing the discovered external address, * or NULL if there was an error looking up the address. - * After meshlink_get_external_address() returns, the application is free to overwrite or free this string. + * After meshlink_get_external_address_for_family() returns, the application is free to overwrite or free this string. */ extern char *meshlink_get_external_address_for_family(meshlink_handle_t *mesh, int address_family); +/// Try to discover the local address for the local node. +/** This function performs tries to discover the address of the local interface used for outgoing connection. + * + * Please note that this is function only returns a single address, + * even if the interface might have more than one address. + * In that case, there is no control over which address will be selected. + * Also note that if you have a dynamic IP address, + * there is no guarantee that the local address will be valid for an extended period of time. + * + * This function will fail if it couldn't find a local address for the given address family. + * If hostname resolving is requested, this function may block for a few seconds. + * + * @param mesh A handle which represents an instance of MeshLink. + * @param family The address family to check, for example AF_INET or AF_INET6. If AF_UNSPEC is given, + * this might return the local address for any working address family. + * + * @return This function returns a pointer to a C string containing the discovered local address, + * or NULL if there was an error looking up the address. + * After meshlink_get_local_address_for_family() returns, the application is free to overwrite or free this string. + */ +extern char *meshlink_get_local_address_for_family(meshlink_handle_t *mesh, int address_family); + /// Try to discover the external address for the local node, and add it to its list of addresses. /** This function is equivalent to: * @@ -561,6 +590,24 @@ extern void meshlink_set_invitation_timeout(meshlink_handle_t *mesh, int timeout * @param mesh A handle which represents an instance of MeshLink. * @param name A nul-terminated C string containing the name that the invitee will be allowed to use in the mesh. * After this function returns, the application is free to overwrite or free @a name @a. + * @param flags A bitwise-or'd combination of flags that controls how the URL is generated. + * + * @return This function returns a nul-terminated C string that contains the invitation URL, or NULL in case of an error. + * The application should call free() after it has finished using the URL. + */ +extern char *meshlink_invite_ex(meshlink_handle_t *mesh, const char *name, uint32_t flags); + +/// Invite another node into the mesh. +/** This function generates an invitation that can be used by another node to join the same mesh as the local node. + * The generated invitation is a string containing a URL. + * This URL should be passed by the application to the invitee in a way that no eavesdroppers can see the URL. + * The URL can only be used once, after the user has joined the mesh the URL is no longer valid. + * + * Calling this function is equal to callen meshlink_invite_ex() with flags set to 0. + * + * @param mesh A handle which represents an instance of MeshLink. + * @param name A nul-terminated C string containing the name that the invitee will be allowed to use in the mesh. + * After this function returns, the application is free to overwrite or free @a name @a. * * @return This function returns a nul-terminated C string that contains the invitation URL, or NULL in case of an error. * The application should call free() after it has finished using the URL. @@ -774,6 +821,9 @@ extern meshlink_channel_t *meshlink_channel_open_ex(meshlink_handle_t *mesh, mes * When the application does no longer need to use this channel, it must call meshlink_close() * to free its resources. * + * Calling this function is equivalent to calling meshlink_channel_open_ex() + * with the flags set to MESHLINK_CHANNEL_TCP. + * * @param mesh A handle which represents an instance of MeshLink. * @param node The node to which this channel is being initiated. * @param port The port number the peer wishes to connect to. -- 2.39.5