]> git.meshlink.io Git - meshlink/commitdiff
Really add both local and external address to the invitation URL.
authorGuus Sliepen <guus@meshlink.io>
Tue, 9 Oct 2018 21:27:05 +0000 (23:27 +0200)
committerGuus Sliepen <guus@meshlink.io>
Tue, 9 Oct 2018 21:33:59 +0000 (23:33 +0200)
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
src/meshlink.c
src/meshlink.h

index 4179a9ec248b274ec0681917436c9beb434486d0..16a6e36624ec7c6fa772ffdc73ad4331bdddb3d9 100644 (file)
@@ -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.
index b407e5b53dca8e7a263be76d08c6a1e8511ac661..8da56922aa5735828f0e598773c613fb3b13d645 100644 (file)
@@ -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;
index 2f2e7f841054e2ea3d21622c09906bb881d95970..74ebae5d71e4eb5771312f57bbe5a08e663a2130 100644 (file)
@@ -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.