From 366a0515c96d11ae23b06eca938bee0286e8785c Mon Sep 17 00:00:00 2001 From: Paul Marks Date: Tue, 22 Nov 2016 15:00:09 -0800 Subject: [PATCH] port_posix: use IPv4+IPv6 sockets to check for a free port. This allows the port picker to function across dual-stack and IPv4/IPv6-only environments. For a port to be considered available, the kernel must support at least one of (IPv6, IPv4), and the port must be available on each supported family. Also use the bool type where feasible. This is based on http://cl/97155408 (for those who can read it.) --- test/core/util/port_posix.c | 100 +++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 42 deletions(-) diff --git a/test/core/util/port_posix.c b/test/core/util/port_posix.c index 60537b49469..1cb4a8c05fd 100644 --- a/test/core/util/port_posix.c +++ b/test/core/util/port_posix.c @@ -39,6 +39,7 @@ #include #include +#include #include #include #include @@ -50,6 +51,8 @@ #include #include "src/core/lib/http/httpcli.h" +#include "src/core/lib/iomgr/resolve_address.h" +#include "src/core/lib/iomgr/sockaddr_utils.h" #include "src/core/lib/support/env.h" #include "test/core/util/port_server_client.h" @@ -115,55 +118,68 @@ static void chose_port(int port) { chosen_ports[num_chosen_ports - 1] = port; } -static int is_port_available(int *port, int is_tcp) { - const int proto = is_tcp ? IPPROTO_TCP : 0; - const int fd = socket(AF_INET, is_tcp ? SOCK_STREAM : SOCK_DGRAM, proto); - int one = 1; - struct sockaddr_in addr; - socklen_t alen = sizeof(addr); - int actual_port; - +static bool is_port_available(int *port, bool is_tcp) { GPR_ASSERT(*port >= 0); GPR_ASSERT(*port <= 65535); - if (fd < 0) { - gpr_log(GPR_ERROR, "socket() failed: %s", strerror(errno)); - return 0; - } - /* Reuseaddr lets us start up a server immediately after it exits */ - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) { - gpr_log(GPR_ERROR, "setsockopt() failed: %s", strerror(errno)); - close(fd); - return 0; - } + /* For a port to be considered available, the kernel must support + at least one of (IPv6, IPv4), and the port must be available + on each supported family. */ + bool got_socket = false; + for (int is_ipv6 = 1; is_ipv6 >= 0; is_ipv6--) { + const int fd = socket(is_ipv6 ? AF_INET6 : AF_INET, + is_tcp ? SOCK_STREAM : SOCK_DGRAM, + is_tcp ? IPPROTO_TCP : 0); + if (fd >= 0) { + got_socket = true; + } else { + continue; + } - /* Try binding to port */ - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = INADDR_ANY; - addr.sin_port = htons((uint16_t)*port); - if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - gpr_log(GPR_DEBUG, "bind(port=%d) failed: %s", *port, strerror(errno)); - close(fd); - return 0; - } + /* Reuseaddr lets us start up a server immediately after it exits */ + const int one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) { + gpr_log(GPR_ERROR, "setsockopt() failed: %s", strerror(errno)); + close(fd); + return false; + } + + /* Try binding to port */ + grpc_resolved_address addr; + if (is_ipv6) { + grpc_sockaddr_make_wildcard6(*port, &addr); /* [::]:port */ + } else { + grpc_sockaddr_make_wildcard4(*port, &addr); /* 0.0.0.0:port */ + } + if (bind(fd, (struct sockaddr *)addr.addr, (socklen_t)addr.len) < 0) { + gpr_log(GPR_DEBUG, "bind(port=%d) failed: %s", *port, strerror(errno)); + close(fd); + return false; + } + + /* Get the bound port number */ + if (getsockname(fd, (struct sockaddr *)addr.addr, + (socklen_t *)&addr.len) < 0) { + gpr_log(GPR_ERROR, "getsockname() failed: %s", strerror(errno)); + close(fd); + return false; + } + GPR_ASSERT(addr.len <= sizeof(addr.addr)); + const int actual_port = grpc_sockaddr_get_port(&addr); + GPR_ASSERT(actual_port > 0); + if (*port == 0) { + *port = actual_port; + } else { + GPR_ASSERT(*port == actual_port); + } - /* Get the bound port number */ - if (getsockname(fd, (struct sockaddr *)&addr, &alen) < 0) { - gpr_log(GPR_ERROR, "getsockname() failed: %s", strerror(errno)); close(fd); - return 0; } - GPR_ASSERT(alen <= sizeof(addr)); - actual_port = ntohs(addr.sin_port); - GPR_ASSERT(actual_port > 0); - if (*port == 0) { - *port = actual_port; - } else { - GPR_ASSERT(*port == actual_port); + if (!got_socket) { + gpr_log(GPR_ERROR, "socket() failed: %s", strerror(errno)); + return false; } - - close(fd); - return 1; + return true; } int grpc_pick_unused_port(void) { @@ -180,7 +196,7 @@ int grpc_pick_unused_port(void) { UDP ports and they are scarcer. */ /* Type of port to first pick in next iteration */ - int is_tcp = 1; + bool is_tcp = true; int trial = 0; char *env = gpr_getenv("GRPC_TEST_PORT_SERVER");