X-Git-Url: https://git.ozlabs.org/?p=ccan;a=blobdiff_plain;f=ccan%2Fnet%2Fnet.c;h=394c4e49f2bb16024ea576b0aa68ed1376c25bcc;hp=e84380fff96f27a885d05618a46b6d1d2d46cc2c;hb=fc8acdbbe2484794aa3c3bea8c00f0de80e48811;hpb=659efe73b8aa2cf479a8e13f81752b47d1154afc diff --git a/ccan/net/net.c b/ccan/net/net.c index e84380ff..394c4e49 100644 --- a/ccan/net/net.c +++ b/ccan/net/net.c @@ -1,5 +1,6 @@ /* Licensed under BSD-MIT - see LICENSE file for details */ #include +#include #include #include #include @@ -11,6 +12,7 @@ #include #include #include +#include struct addrinfo *net_client_lookup(const char *hostname, const char *service, @@ -48,107 +50,156 @@ static bool set_nonblock(int fd, bool nonblock) return (fcntl(fd, F_SETFL, flags) == 0); } -/* We only handle IPv4 and IPv6 */ -#define MAX_PROTOS 2 - -static void remove_fd(struct pollfd pfd[], - const struct addrinfo *addr[], - socklen_t slen[], - unsigned int *num, - unsigned int i) +static int start_connect(const struct addrinfo *addr, bool *immediate) { - memmove(pfd + i, pfd + i + 1, (*num - i - 1) * sizeof(pfd[0])); - memmove(addr + i, addr + i + 1, (*num - i - 1) * sizeof(addr[0])); - memmove(slen + i, slen + i + 1, (*num - i - 1) * sizeof(slen[0])); - (*num)--; + int fd; + + *immediate = false; + + fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + if (fd == -1) + return fd; + + if (!set_nonblock(fd, true)) + goto close; + + if (connect(fd, addr->ai_addr, addr->ai_addrlen) == 0) { + /* Immediate connect. */ + *immediate = true; + return fd; + } + + if (errno == EINPROGRESS) + return fd; + +close: + close_noerr(fd); + return -1; } -int net_connect(const struct addrinfo *addrinfo) + +int net_connect_async(const struct addrinfo *addrinfo, struct pollfd pfds[2]) { - int sockfd = -1, saved_errno; - unsigned int i, num; - const struct addrinfo *ipv4 = NULL, *ipv6 = NULL; - const struct addrinfo *addr[MAX_PROTOS]; - socklen_t slen[MAX_PROTOS]; - struct pollfd pfd[MAX_PROTOS]; + const struct addrinfo *addr[2] = { NULL, NULL }; + unsigned int i; + + pfds[0].fd = pfds[1].fd = -1; + pfds[0].events = pfds[1].events = POLLOUT; + /* Give IPv6 a slight advantage, by trying it first. */ for (; addrinfo; addrinfo = addrinfo->ai_next) { switch (addrinfo->ai_family) { case AF_INET: - if (!ipv4) - ipv4 = addrinfo; + addr[1] = addrinfo; break; case AF_INET6: - if (!ipv6) - ipv6 = addrinfo; + addr[0] = addrinfo; break; + default: + continue; } } - num = 0; - /* We give IPv6 a slight edge by connecting it first. */ - if (ipv6) { - addr[num] = ipv6; - slen[num] = sizeof(struct sockaddr_in6); - pfd[num].fd = socket(AF_INET6, ipv6->ai_socktype, - ipv6->ai_protocol); - if (pfd[num].fd != -1) - num++; + /* In case we found nothing. */ + errno = ENOENT; + for (i = 0; i < 2; i++) { + bool immediate; + + if (!addr[i]) + continue; + + pfds[i].fd = start_connect(addr[i], &immediate); + if (immediate) { + if (pfds[!i].fd != -1) + close(pfds[!i].fd); + if (!set_nonblock(pfds[i].fd, false)) { + close_noerr(pfds[i].fd); + return -1; + } + return pfds[0].fd; + } } - if (ipv4) { - addr[num] = ipv4; - slen[num] = sizeof(struct sockaddr_in); - pfd[num].fd = socket(AF_INET, ipv4->ai_socktype, - ipv4->ai_protocol); - if (pfd[num].fd != -1) - num++; + + if (pfds[0].fd != -1 || pfds[1].fd != -1) + errno = EINPROGRESS; + return -1; +} + +void net_connect_abort(struct pollfd pfds[2]) +{ + unsigned int i; + + for (i = 0; i < 2; i++) { + if (pfds[i].fd != -1) + close_noerr(pfds[i].fd); + pfds[i].fd = -1; } +} - for (i = 0; i < num; i++) { - if (!set_nonblock(pfd[i].fd, true)) { - remove_fd(pfd, addr, slen, &num, i--); +int net_connect_complete(struct pollfd pfds[2]) +{ + unsigned int i; + + assert(pfds[0].fd != -1 || pfds[1].fd != -1); + + for (i = 0; i < 2; i++) { + int err; + socklen_t errlen = sizeof(err); + + if (pfds[i].fd == -1) + continue; + if (pfds[i].revents & POLLHUP) { + /* Linux gives this if connecting to local + * non-listening port */ + close(pfds[i].fd); + pfds[i].fd = -1; + if (pfds[!i].fd == -1) { + errno = ECONNREFUSED; + return -1; + } continue; } - /* Connect *can* be instant. */ - if (connect(pfd[i].fd, addr[i]->ai_addr, slen[i]) == 0) - goto got_one; - if (errno != EINPROGRESS) { - /* Remove dead one. */ - remove_fd(pfd, addr, slen, &num, i--); + if (getsockopt(pfds[i].fd, SOL_SOCKET, SO_ERROR, &err, + &errlen) != 0) { + net_connect_abort(pfds); + return -1; } - pfd[i].events = POLLOUT; - } - - while (num && poll(pfd, num, -1) != -1) { - for (i = 0; i < num; i++) { - int err; - socklen_t errlen = sizeof(err); - if (!pfd[i].revents) - continue; - if (getsockopt(pfd[i].fd, SOL_SOCKET, SO_ERROR, &err, - &errlen) != 0) - goto out; - if (err == 0) - goto got_one; - - /* Remove dead one. */ - errno = err; - remove_fd(pfd, addr, slen, &num, i--); + if (err == 0) { + /* Don't hand them non-blocking fd! */ + if (!set_nonblock(pfds[i].fd, false)) { + net_connect_abort(pfds); + return -1; + } + /* Close other one. */ + if (pfds[!i].fd != -1) + close(pfds[!i].fd); + return pfds[i].fd; } } -got_one: - /* We don't want to hand them a non-blocking socket! */ - if (set_nonblock(pfd[i].fd, false)) - sockfd = pfd[i].fd; + /* Still going... */ + errno = EINPROGRESS; + return -1; +} -out: - saved_errno = errno; - for (i = 0; i < num; i++) - if (pfd[i].fd != sockfd) - close(pfd[i].fd); - errno = saved_errno; - return sockfd; +int net_connect(const struct addrinfo *addrinfo) +{ + struct pollfd pfds[2]; + int sockfd; + + sockfd = net_connect_async(addrinfo, pfds); + /* Immediate connect or error is easy. */ + if (sockfd >= 0 || errno != EINPROGRESS) + return sockfd; + + while (poll(pfds, 2, -1) != -1) { + sockfd = net_connect_complete(pfds); + if (sockfd >= 0 || errno != EINPROGRESS) + return sockfd; + } + + net_connect_abort(pfds); + return -1; } struct addrinfo *net_server_lookup(const char *service,