From 5c7be85686db219955e1af592b32d0d4108625cb Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Tue, 26 Jun 2018 16:12:28 +0200 Subject: [PATCH] Add meshlink_set_canonical_address(). This introduces the concept of a canonical address for a node, which is its "official" address/port. It is only set manually, and never updated or removed when other addresses have been discovered for this node. Also, this changed meshlink_add_address(mesh, address) to be equivalent to meshlink_set_canonical_address(mesh, meshlink_get_self(mesh), address, NULL). While not strictly equivalent to the old behaviour, it matches the documented intent of this function. --- src/meshlink++.h | 36 ++++++++++ src/meshlink.c | 75 +++++++++++++++---- src/meshlink.h | 19 +++++ src/net.h | 13 +++- src/net_socket.c | 182 +++++++++++++++++++++++++++++++++-------------- test/trio.c | 6 +- 6 files changed, 260 insertions(+), 71 deletions(-) diff --git a/src/meshlink++.h b/src/meshlink++.h index 83eafdfa..4cea8cfa 100644 --- a/src/meshlink++.h +++ b/src/meshlink++.h @@ -360,6 +360,42 @@ public: return meshlink_verify(handle, source, data, len, signature, siglen); } + /// Set the canonical Address for a node. + /** This function sets the canonical Address for a node. + * This address is stored permanently until it is changed by another call to this function, + * unlike other addresses associated with a node, + * such as those added with meshlink_hint_address() or addresses discovered at runtime. + * + * If a canonical Address is set for the local node, + * it will be used for the hostname part of generated invitation URLs. + * + * @param node A pointer to a meshlink_node_t describing the node. + * @param address A nul-terminated C string containing the address, which can be either in numeric format or a hostname. + * @param port A nul-terminated C string containing the port, which can be either in numeric or symbolic format. + * If it is NULL, the listening port's number will be used. + * + * @return This function returns true if the address was added, false otherwise. + */ + bool set_canonical_address(node *node, const char *address, const char *port = NULL) { + return meshlink_set_canonical_address(handle, node, address, port); + } + + /// Set the canonical Address for the local node. + /** This function sets the canonical Address for the local node. + * This address is stored permanently until it is changed by another call to this function, + * unlike other addresses associated with a node, + * such as those added with meshlink_hint_address() or addresses discovered at runtime. + * + * @param address A nul-terminated C string containing the address, which can be either in numeric format or a hostname. + * @param port A nul-terminated C string containing the port, which can be either in numeric or symbolic format. + * If it is NULL, the listening port's number will be used. + * + * @return This function returns true if the address was added, false otherwise. + */ + bool set_canonical_address(const char *address, const char *port = NULL) { + return meshlink_set_canonical_address(handle, get_self(), address, port); + } + /// Add an Address for the local node. /** This function adds an Address for the local node, which will be used for invitation URLs. * diff --git a/src/meshlink.c b/src/meshlink.c index 1de54e91..f6121a1e 100644 --- a/src/meshlink.c +++ b/src/meshlink.c @@ -59,6 +59,7 @@ const var_t variables[] = { {"ConnectTo", VAR_SERVER | VAR_MULTIPLE | VAR_SAFE}, {"Name", VAR_SERVER}, /* Host configuration */ + {"CanonicalAddress", VAR_HOST}, {"Address", VAR_HOST | VAR_MULTIPLE}, {"ECDSAPublicKey", VAR_HOST}, {"Port", VAR_HOST}, @@ -96,6 +97,7 @@ static int rstrip(char *value) { static void scan_for_hostname(const char *filename, char **hostname, char **port) { char line[4096]; + bool canonical = false; if(!filename || (*hostname && *port)) { return; @@ -135,25 +137,33 @@ static void scan_for_hostname(const char *filename, char **hostname, char **port p += strspn(p, "\t "); p[strcspn(p, "\t ")] = 0; - // p is now pointing to the port - - // 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; - } + // p is now pointing to the port, if present if(!*port && !strcasecmp(line, "Port")) { *port = xstrdup(q); - } else if(!*hostname && !strcasecmp(line, "Address")) { + } 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); *port = xstrdup(p); } } - if(*hostname && *port) { + if(canonical && *hostname && *port) { break; } } @@ -162,6 +172,10 @@ static void scan_for_hostname(const char *filename, char **hostname, char **port } static bool is_valid_hostname(const char *hostname) { + if(!*hostname) { + return false; + } + for(const char *p = hostname; *p; p++) { if(!(isalnum(*p) || *p == '-' || *p == '.' || *p == ':')) { return false; @@ -171,6 +185,26 @@ static bool is_valid_hostname(const char *hostname) { return true; } +static bool is_valid_port(const char *port) { + if(!*port) { + return false; + } + + if(isdigit(*port)) { + char *end; + unsigned long int result = strtoul(port, &end, 10); + return result && result < 65536 && !*end; + } + + for(const char *p = port; *p; p++) { + if(!(isalnum(*p) || *p == '-')) { + return false; + } + } + + return true; +} + static void set_timeout(int sock, int timeout) { #ifdef _WIN32 DWORD tv = timeout; @@ -1675,8 +1709,8 @@ static bool refresh_invitation_key(meshlink_handle_t *mesh) { return mesh->invitation_key; } -bool meshlink_add_address(meshlink_handle_t *mesh, const char *address) { - if(!mesh || !address) { +bool meshlink_set_canonical_address(meshlink_handle_t *mesh, meshlink_node_t *node, const char *address, const char *port) { + if(!mesh || !node || !address) { meshlink_errno = MESHLINK_EINVAL; return false; } @@ -1687,15 +1721,32 @@ bool meshlink_add_address(meshlink_handle_t *mesh, const char *address) { return false; } - bool rval = 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 *canonical_address; + + if(port) { + xasprintf(&canonical_address, "%s %s", address, port); + } else { + canonical_address = xstrdup(address); + } pthread_mutex_lock(&(mesh->mesh_mutex)); - rval = append_config_file(mesh, mesh->self->name, "Address", address); + bool rval = modify_config_file(mesh, node->name, "CanonicalAddress", canonical_address, 1); pthread_mutex_unlock(&(mesh->mesh_mutex)); + free(canonical_address); return rval; } +bool meshlink_add_address(meshlink_handle_t *mesh, const char *address) { + return meshlink_set_canonical_address(mesh, mesh->self, address, NULL); +} + bool meshlink_add_external_address(meshlink_handle_t *mesh) { if(!mesh) { meshlink_errno = MESHLINK_EINVAL; diff --git a/src/meshlink.h b/src/meshlink.h index c8b9870d..26d0dfce 100644 --- a/src/meshlink.h +++ b/src/meshlink.h @@ -403,6 +403,25 @@ extern bool meshlink_sign(meshlink_handle_t *mesh, const void *data, size_t len, */ extern bool meshlink_verify(meshlink_handle_t *mesh, meshlink_node_t *source, const void *data, size_t len, const void *signature, size_t siglen); +/// Set the canonical Address for a node. +/** This function sets the canonical Address for a node. + * This address is stored permanently until it is changed by another call to this function, + * unlike other addresses associated with a node, + * such as those added with meshlink_hint_address() or addresses discovered at runtime. + * + * If a canonical Address is set for the local node, + * it will be used for the hostname part of generated invitation URLs. + * + * @param mesh A handle which represents an instance of MeshLink. + * @param node A pointer to a meshlink_node_t describing the node. + * @param address A nul-terminated C string containing the address, which can be either in numeric format or a hostname. + * @param port A nul-terminated C string containing the port, which can be either in numeric or symbolic format. + * If it is NULL, the listening port's number will be used. + * + * @return This function returns true if the address was added, false otherwise. + */ +extern bool meshlink_set_canonical_address(meshlink_handle_t *mesh, meshlink_node_t *node, const char *address, const char *port); + /// Add an Address for the local node. /** This function adds an Address for the local node, which will be used for invitation URLs. * diff --git a/src/net.h b/src/net.h index 0ff313d0..04bd55a5 100644 --- a/src/net.h +++ b/src/net.h @@ -60,12 +60,19 @@ typedef enum packet_type_t { typedef struct outgoing_t { char *name; - int timeout; struct splay_tree_t *config_tree; + int timeout; + enum { + OUTGOING_START, + OUTGOING_CANONICAL, + OUTGOING_RECENT, + OUTGOING_KNOWN, + OUTGOING_END, + OUTGOING_NO_KNOWN_ADDRESSES, + } state; struct config_t *cfg; - struct addrinfo *ai; // addresses from config files + struct addrinfo *ai; struct addrinfo *aip; - struct addrinfo *nai; // addresses known via other online nodes (use free_known_addresses()) timeout_t ev; struct meshlink_handle *mesh; } outgoing_t; diff --git a/src/net_socket.c b/src/net_socket.c index 68bf98f4..01ef9429 100644 --- a/src/net_socket.c +++ b/src/net_socket.c @@ -389,65 +389,139 @@ static void free_known_addresses(struct addrinfo *ai) { } } -bool do_outgoing_connection(meshlink_handle_t *mesh, outgoing_t *outgoing) { - char *address, *port, *space; - struct addrinfo *proxyai = NULL; - int result; +static bool get_recent(meshlink_handle_t *mesh, outgoing_t *outgoing) { + node_t *n = lookup_node(mesh, outgoing->name); -begin: + if(!n) { + return false; + } - if(!outgoing->ai && !outgoing->nai) { - if(!outgoing->cfg) { - logger(mesh, MESHLINK_ERROR, "Could not set up a meta connection to %s", outgoing->name); - retry_outgoing(mesh, outgoing); - return false; + outgoing->ai = get_known_addresses(n); + outgoing->aip = outgoing->ai; + return outgoing->aip; +} + +static bool get_next_ai(meshlink_handle_t *mesh, outgoing_t *outgoing) { + if(!outgoing->ai) { + char *address = NULL; + + if(get_config_string(outgoing->cfg, &address)) { + char *port; + char *space = strchr(address, ' '); + + if(space) { + port = xstrdup(space + 1); + *space = 0; + } else { + if(!get_config_string(lookup_config(outgoing->config_tree, "Port"), &port)) { + logger(mesh, MESHLINK_ERROR, "No Port known for %s", outgoing->name); + return false; + } + } + + outgoing->ai = str2addrinfo(address, port, SOCK_STREAM); + free(port); + free(address); } - get_config_string(outgoing->cfg, &address); + outgoing->aip = outgoing->ai; + } else { + outgoing->aip = outgoing->aip->ai_next; + } + + return outgoing->aip; +} - space = strchr(address, ' '); +static bool get_next_cfg(meshlink_handle_t *mesh, outgoing_t *outgoing, char *variable) { + (void)mesh; - if(space) { - port = xstrdup(space + 1); - *space = 0; - } else { - // TODO: Only allow Address statements? - if(!get_config_string(lookup_config(outgoing->config_tree, "Port"), &port)) { - logger(mesh, MESHLINK_ERROR, "No Port known for %s", outgoing->name); - retry_outgoing(mesh, outgoing); - return false; + if(!outgoing->cfg) { + outgoing->cfg = lookup_config(outgoing->config_tree, variable); + } else { + outgoing->cfg = lookup_config_next(outgoing->config_tree, outgoing->cfg); + } + + return outgoing->cfg; +} + +static bool get_next_outgoing_address(meshlink_handle_t *mesh, outgoing_t *outgoing) { + bool start = false; + + if(outgoing->state == OUTGOING_START) { + start = true; + outgoing->state = OUTGOING_CANONICAL; + } + + if(outgoing->state == OUTGOING_CANONICAL) { + if(outgoing->aip || get_next_cfg(mesh, outgoing, "CanonicalAddress")) { + if(get_next_ai(mesh, outgoing)) { + return true; + } else { + freeaddrinfo(outgoing->ai); + outgoing->ai = NULL; + outgoing->aip = NULL; } } - outgoing->ai = str2addrinfo(address, port, SOCK_STREAM); - free(address); - free(port); - - outgoing->aip = outgoing->ai; - outgoing->cfg = lookup_config_next(outgoing->config_tree, outgoing->cfg); + outgoing->state = OUTGOING_RECENT; } - if(!outgoing->aip) { - if(outgoing->ai) { - freeaddrinfo(outgoing->ai); + if(outgoing->state == OUTGOING_RECENT) { + if(outgoing->aip || get_next_cfg(mesh, outgoing, "Address")) { + if(get_next_ai(mesh, outgoing)) { + return true; + } else { + freeaddrinfo(outgoing->ai); + outgoing->ai = NULL; + outgoing->aip = NULL; + } } - outgoing->ai = NULL; + outgoing->state = OUTGOING_KNOWN; + } - if(outgoing->nai) { - free_known_addresses(outgoing->nai); + if(outgoing->state == OUTGOING_KNOWN) { + if(outgoing->aip || get_recent(mesh, outgoing)) { + if(get_next_ai(mesh, outgoing)) { + return true; + } else { + free_known_addresses(outgoing->ai); + outgoing->ai = NULL; + outgoing->aip = NULL; + } } - outgoing->nai = NULL; + outgoing->state = OUTGOING_END; + } - goto begin; + if(start) { + outgoing->state = OUTGOING_NO_KNOWN_ADDRESSES; + } + + return false; +} + +bool do_outgoing_connection(meshlink_handle_t *mesh, outgoing_t *outgoing) { + struct addrinfo *proxyai = NULL; + int result; + +begin: + + if(!get_next_outgoing_address(mesh, outgoing)) { + if(outgoing->state == OUTGOING_NO_KNOWN_ADDRESSES) { + logger(mesh, MESHLINK_ERROR, "No known addresses for %s", outgoing->name); + } else { + logger(mesh, MESHLINK_ERROR, "Could not set up a meta connection to %s", outgoing->name); + retry_outgoing(mesh, outgoing); + } + + return false; } connection_t *c = new_connection(); c->outgoing = outgoing; memcpy(&c->address, outgoing->aip->ai_addr, outgoing->aip->ai_addrlen); - outgoing->aip = outgoing->aip->ai_next; char *hostname = sockaddr2hostname(&c->address); @@ -537,28 +611,28 @@ void setup_outgoing_connection(meshlink_handle_t *mesh, outgoing_t *outgoing) { return; } + + if(outgoing->ai) { + if(outgoing->state == OUTGOING_KNOWN) { + free_known_addresses(outgoing->ai); + } else { + freeaddrinfo(outgoing->ai); + } + } + + outgoing->cfg = NULL; + exit_configuration(&outgoing->config_tree); // discard old configuration if present init_configuration(&outgoing->config_tree); read_host_config(mesh, outgoing->config_tree, outgoing->name); - outgoing->cfg = lookup_config(outgoing->config_tree, "Address"); - get_config_bool(lookup_config(outgoing->config_tree, "blacklisted"), &blacklisted); + outgoing->state = OUTGOING_START; + if(blacklisted) { return; } - if(!outgoing->cfg) { - if(n) { - outgoing->aip = outgoing->nai = get_known_addresses(n); - } - - if(!outgoing->nai) { - logger(mesh, MESHLINK_ERROR, "No address known for %s", outgoing->name); - return; - } - } - do_outgoing_connection(mesh, outgoing); } @@ -670,11 +744,11 @@ static void free_outgoing(outgoing_t *outgoing) { timeout_del(&mesh->loop, &outgoing->ev); if(outgoing->ai) { - freeaddrinfo(outgoing->ai); - } - - if(outgoing->nai) { - free_known_addresses(outgoing->nai); + if(outgoing->state == OUTGOING_KNOWN) { + free_known_addresses(outgoing->ai); + } else { + freeaddrinfo(outgoing->ai); + } } if(outgoing->config_tree) { diff --git a/test/trio.c b/test/trio.c index 2cb14bd3..c82821f1 100644 --- a/test/trio.c +++ b/test/trio.c @@ -59,6 +59,8 @@ int main() { mesh[i] = meshlink_open(path, name[i], "trio", DEV_CLASS_BACKBONE); assert(mesh[i]); + meshlink_add_address(mesh[i], "localhost"); + data[i] = meshlink_export(mesh[i]); assert(data[i]); } @@ -81,7 +83,7 @@ int main() { // start the nodes for(int i = 0; i < 3; i++) { - meshlink_start(mesh[i]); + assert(meshlink_start(mesh[i])); } // the nodes should now learn about each other @@ -122,7 +124,7 @@ int main() { meshlink_set_log_cb(mesh[1], MESHLINK_DEBUG, log_cb); for(int i = 1; i < 3; i++) { - meshlink_start(mesh[i]); + assert(meshlink_start(mesh[i])); } assert(meshlink_get_node(mesh[1], name[2])); -- 2.39.2