From db8e6e4221c974314fd8b05d79dbc94152f01108 Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Sat, 7 Mar 2020 00:19:57 +0100 Subject: [PATCH] Handle not being able to bind to the configured port at startup. When starting a MeshLink node that has already been configured to run on a certain port, but that port is in use (for one or more of the supported address families), it would either ignore some address families, or would try to bind to port 0 if all address families failed. However, this is problematic, because it makes discovery and invitation URL generation much harder. Fix this by checking if any port binding fails for a supported address family, and if so, try to find another port that does support binding on all address families. If it fails to find any available port, it will fall back to binding to port 0, so that outgoing connections are still possible. --- src/meshlink.c | 2 +- src/meshlink_internal.h | 1 - src/net.h | 5 +- src/net_setup.c | 93 ++++++++++++++++++++++-------------- src/net_socket.c | 102 +++++----------------------------------- 5 files changed, 75 insertions(+), 128 deletions(-) diff --git a/src/meshlink.c b/src/meshlink.c index 0090d54d..9f9b771d 100644 --- a/src/meshlink.c +++ b/src/meshlink.c @@ -639,7 +639,7 @@ static bool try_bind(int port) { 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); diff --git a/src/meshlink_internal.h b/src/meshlink_internal.h index 42df8b89..eaa89c5d 100644 --- a/src/meshlink_internal.h +++ b/src/meshlink_internal.h @@ -55,7 +55,6 @@ typedef struct listen_socket_t { struct io_t udp; sockaddr_t sa; sockaddr_t broadcast_sa; - bool bindto; } listen_socket_t; typedef enum proxytype_t { diff --git a/src/net.h b/src/net.h index 7cddebde..63fb922f 100644 --- a/src/net.h +++ b/src/net.h @@ -84,8 +84,8 @@ extern void handle_incoming_vpn_data(struct event_loop_t *loop, void *, int); extern void finish_connecting(struct meshlink_handle *mesh, struct connection_t *); extern void do_outgoing_connection(struct meshlink_handle *mesh, struct outgoing_t *); extern void handle_new_meta_connection(struct event_loop_t *loop, void *, int); -extern int setup_listen_socket(const sockaddr_t *) __attribute__((__warn_unused_result__)); -extern int setup_vpn_in_socket(struct meshlink_handle *mesh, const sockaddr_t *) __attribute__((__warn_unused_result__)); +extern bool setup_listen_socket(struct meshlink_handle *mesh, int fd, int sa_family) __attribute__((__warn_unused_result__)); +extern bool setup_vpn_in_socket(struct meshlink_handle *mesh, int fd, int sa_family) __attribute__((__warn_unused_result__)); extern bool send_sptps_data(void *handle, uint8_t type, const void *data, size_t len); extern bool receive_sptps_record(void *handle, uint8_t type, const void *data, uint16_t len) __attribute__((__warn_unused_result__)); extern void send_packet(struct meshlink_handle *mesh, struct node_t *, struct vpn_packet_t *); @@ -106,6 +106,7 @@ extern bool node_write_config(struct meshlink_handle *mesh, struct node_t *) __a extern void send_mtu_probe(struct meshlink_handle *mesh, struct node_t *); extern void handle_meta_connection_data(struct meshlink_handle *mesh, struct connection_t *); extern void retry(struct meshlink_handle *mesh); +extern int check_port(struct meshlink_handle *mesh); #ifndef HAVE_MINGW #define closesocket(s) close(s) diff --git a/src/net_setup.c b/src/net_setup.c index 1b091db0..b9cddeb0 100644 --- a/src/net_setup.c +++ b/src/net_setup.c @@ -304,22 +304,7 @@ static bool load_node(meshlink_handle_t *mesh, const char *name, void *priv) { /* Add listening sockets. */ -static bool add_listen_address(meshlink_handle_t *mesh, char *address, bool bindto) { - char *port = mesh->myport; - - if(address) { - char *space = strchr(address, ' '); - - if(space) { - *space++ = 0; - port = space; - } - - if(!strcmp(address, "*")) { - *address = 0; - } - } - +static bool add_listen_sockets(meshlink_handle_t *mesh) { struct addrinfo *ai; struct addrinfo hint = { @@ -329,9 +314,7 @@ static bool add_listen_address(meshlink_handle_t *mesh, char *address, bool bind .ai_flags = AI_PASSIVE, }; - int err = getaddrinfo(address && *address ? address : NULL, port, &hint, &ai); - - free(address); + int err = getaddrinfo(NULL, mesh->myport, &hint, &ai); if(err || !ai) { logger(mesh, MESHLINK_ERROR, "System call `%s' failed: %s", "getaddrinfo", err == EAI_SYSTEM ? strerror(err) : gai_strerror(err)); @@ -344,11 +327,12 @@ static bool add_listen_address(meshlink_handle_t *mesh, char *address, bool bind // Ignore duplicate addresses bool found = false; - for(int i = 0; i < mesh->listen_sockets; i++) + for(int i = 0; i < mesh->listen_sockets; i++) { if(!memcmp(&mesh->listen_socket[i].sa, aip->ai_addr, aip->ai_addrlen)) { found = true; break; } + } if(found) { continue; @@ -359,17 +343,47 @@ static bool add_listen_address(meshlink_handle_t *mesh, char *address, bool bind return false; } - int tcp_fd = setup_listen_socket((sockaddr_t *) aip->ai_addr); + /* Try to bind to TCP */ + + int tcp_fd = socket(aip->ai_family, SOCK_STREAM, IPPROTO_TCP); - if(tcp_fd < 0) { + if(tcp_fd == -1) { continue; } - int udp_fd = setup_vpn_in_socket(mesh, (sockaddr_t *) aip->ai_addr); + if(bind(tcp_fd, aip->ai_addr, aip->ai_addrlen)) { + closesocket(tcp_fd); - if(udp_fd < 0) { - close(tcp_fd); - continue; + if(errno == EADDRINUSE) { + /* If this port is in use for any address family, avoid it. */ + success = false; + break; + } else { + continue; + } + } + + if(!setup_listen_socket(mesh, tcp_fd, aip->ai_family)) { + closesocket(tcp_fd); + success = false; + break; + } + + /* If TCP worked, then we require that UDP works as well. */ + + int udp_fd = socket(aip->ai_family, SOCK_DGRAM, IPPROTO_UDP); + + if(udp_fd == -1) { + closesocket(tcp_fd); + success = false; + break; + } + + if(bind(udp_fd, aip->ai_addr, aip->ai_addrlen) || !setup_vpn_in_socket(mesh, udp_fd, aip->ai_family)) { + closesocket(tcp_fd); + closesocket(udp_fd); + success = false; + break; } io_add(&mesh->loop, &mesh->listen_socket[mesh->listen_sockets].tcp, handle_new_meta_connection, &mesh->listen_socket[mesh->listen_sockets], tcp_fd, IO_READ); @@ -381,7 +395,6 @@ static bool add_listen_address(meshlink_handle_t *mesh, char *address, bool bind free(hostname); } - mesh->listen_socket[mesh->listen_sockets].bindto = bindto; memcpy(&mesh->listen_socket[mesh->listen_sockets].sa, aip->ai_addr, aip->ai_addrlen); memcpy(&mesh->listen_socket[mesh->listen_sockets].broadcast_sa, aip->ai_addr, aip->ai_addrlen); @@ -398,6 +411,18 @@ static bool add_listen_address(meshlink_handle_t *mesh, char *address, bool bind } freeaddrinfo(ai); + + if(!success) { + for(int i = 0; i < mesh->listen_sockets; i++) { + io_del(&mesh->loop, &mesh->listen_socket[i].tcp); + io_del(&mesh->loop, &mesh->listen_socket[i].udp); + close(mesh->listen_socket[i].tcp.fd); + close(mesh->listen_socket[i].udp.fd); + } + + mesh->listen_sockets = 0; + } + return success; } @@ -423,17 +448,17 @@ bool setup_myself(meshlink_handle_t *mesh) { mesh->listen_sockets = 0; - if(!add_listen_address(mesh, NULL, NULL)) { + if(!add_listen_sockets(mesh)) { if(strcmp(mesh->myport, "0")) { - logger(mesh, MESHLINK_INFO, "Could not bind to port %s, asking OS to choose one for us", mesh->myport); - free(mesh->myport); - mesh->myport = strdup("0"); + logger(mesh, MESHLINK_WARNING, "Could not bind to port %s, trying to find an alternative port", mesh->myport); - if(!mesh->myport) { - return false; + if(!check_port(mesh)) { + logger(mesh, MESHLINK_WARNING, "Could not bind to any port, trying to bind to port 0"); + free(mesh->myport); + mesh->myport = xstrdup("0"); } - if(!add_listen_address(mesh, NULL, NULL)) { + if(!add_listen_sockets(mesh)) { return false; } } else { diff --git a/src/net_socket.c b/src/net_socket.c index 7e05ab93..931f21d1 100644 --- a/src/net_socket.c +++ b/src/net_socket.c @@ -72,65 +72,17 @@ static void configure_tcp(connection_t *c) { #endif } -static bool bind_to_address(meshlink_handle_t *mesh, connection_t *c) { - int s = -1; - - for(int i = 0; i < mesh->listen_sockets && mesh->listen_socket[i].bindto; i++) { - if(mesh->listen_socket[i].sa.sa.sa_family != c->address.sa.sa_family) { - continue; - } - - if(s >= 0) { - return false; - } - - s = i; - } - - if(s < 0) { - return false; - } - - sockaddr_t sa = mesh->listen_socket[s].sa; - - if(sa.sa.sa_family == AF_INET) { - sa.in.sin_port = 0; - } else if(sa.sa.sa_family == AF_INET6) { - sa.in6.sin6_port = 0; - } - - if(bind(c->socket, &sa.sa, SALEN(sa.sa))) { - logger(mesh, MESHLINK_WARNING, "Can't bind outgoing socket: %s", strerror(errno)); - return false; - } - - return true; -} - -int setup_listen_socket(const sockaddr_t *sa) { - int nfd; - char *addrstr; - int option; - - nfd = socket(sa->sa.sa_family, SOCK_STREAM, IPPROTO_TCP); - - if(nfd < 0) { - logger(NULL, MESHLINK_ERROR, "Creating metasocket failed: %s", sockstrerror(sockerrno)); - return -1; - } - +bool setup_listen_socket(meshlink_handle_t *mesh, int nfd, int sa_family) { #ifdef FD_CLOEXEC fcntl(nfd, F_SETFD, FD_CLOEXEC); #endif - /* Optimize TCP settings */ - - option = 1; + int option = 1; setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, (void *)&option, sizeof(option)); #if defined(IPV6_V6ONLY) - if(sa->sa.sa_family == AF_INET6) { + if(sa_family == AF_INET6) { setsockopt(nfd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&option, sizeof(option)); } @@ -138,35 +90,15 @@ int setup_listen_socket(const sockaddr_t *sa) { #warning IPV6_V6ONLY not defined #endif - if(bind(nfd, &sa->sa, SALEN(sa->sa))) { - closesocket(nfd); - addrstr = sockaddr2hostname(sa); - logger(NULL, MESHLINK_ERROR, "Can't bind to %s/tcp: %s", addrstr, sockstrerror(sockerrno)); - free(addrstr); - return -1; - } - if(listen(nfd, 3)) { - closesocket(nfd); - logger(NULL, MESHLINK_ERROR, "System call `%s' failed: %s", "listen", sockstrerror(sockerrno)); - return -1; + logger(mesh, MESHLINK_ERROR, "System call `%s' failed: %s", "listen", sockstrerror(sockerrno)); + return false; } - return nfd; + return true; } -int setup_vpn_in_socket(meshlink_handle_t *mesh, const sockaddr_t *sa) { - int nfd; - char *addrstr; - int option; - - nfd = socket(sa->sa.sa_family, SOCK_DGRAM, IPPROTO_UDP); - - if(nfd < 0) { - logger(mesh, MESHLINK_ERROR, "Creating UDP socket failed: %s", sockstrerror(sockerrno)); - return -1; - } - +bool setup_vpn_in_socket(meshlink_handle_t *mesh, int nfd, int sa_family) { #ifdef FD_CLOEXEC fcntl(nfd, F_SETFD, FD_CLOEXEC); #endif @@ -179,7 +111,7 @@ int setup_vpn_in_socket(meshlink_handle_t *mesh, const sockaddr_t *sa) { closesocket(nfd); logger(mesh, MESHLINK_ERROR, "System call `%s' failed: %s", "fcntl", strerror(errno)); - return -1; + return false; } } #elif defined(WIN32) @@ -189,18 +121,18 @@ int setup_vpn_in_socket(meshlink_handle_t *mesh, const sockaddr_t *sa) { if(ioctlsocket(nfd, FIONBIO, &arg) != 0) { closesocket(nfd); logger(mesh, MESHLINK_ERROR, "Call to `%s' failed: %s", "ioctlsocket", sockstrerror(sockerrno)); - return -1; + return false; } } #endif - option = 1; + int option = 1; setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, (void *)&option, sizeof(option)); setsockopt(nfd, SOL_SOCKET, SO_BROADCAST, (void *)&option, sizeof(option)); #if defined(IPV6_V6ONLY) - if(sa->sa.sa_family == AF_INET6) { + if(sa_family == AF_INET6) { setsockopt(nfd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&option, sizeof(option)); } @@ -226,15 +158,7 @@ int setup_vpn_in_socket(meshlink_handle_t *mesh, const sockaddr_t *sa) { setsockopt(nfd, IPPROTO_IPV6, IPV6_DONTFRAG, (void *)&option, sizeof(option)); #endif - if(bind(nfd, &sa->sa, SALEN(sa->sa))) { - closesocket(nfd); - addrstr = sockaddr2hostname(sa); - logger(mesh, MESHLINK_ERROR, "Can't bind to %s/udp: %s", addrstr, sockstrerror(sockerrno)); - free(addrstr); - return -1; - } - - return nfd; + return true; } /* int setup_vpn_in_socket */ static void retry_outgoing_handler(event_loop_t *loop, void *data) { @@ -562,8 +486,6 @@ begin: #endif - bind_to_address(mesh, c); - /* Connect */ if(!mesh->proxytype) { -- 2.39.2