From 158de63fda4e79ce3a632f25d444a1ec66f45d74 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 2 Oct 2013 17:53:37 +0930 Subject: [PATCH 1/1] net: add async operation helpers. Signed-off-by: Rusty Russell --- ccan/net/_info | 1 + ccan/net/net.c | 194 +++++++++++++++++++++++++++++-------------------- ccan/net/net.h | 66 +++++++++++++++++ 3 files changed, 184 insertions(+), 77 deletions(-) diff --git a/ccan/net/_info b/ccan/net/_info index 9ce0e150..b4b219fd 100644 --- a/ccan/net/_info +++ b/ccan/net/_info @@ -66,6 +66,7 @@ int main(int argc, char *argv[]) return 1; if (strcmp(argv[1], "depends") == 0) { + printf("ccan/noerr\n"); return 0; } diff --git a/ccan/net/net.c b/ccan/net/net.c index e84380ff..d2eaa9ec 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,145 @@ 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; } +} + +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); - for (i = 0; i < num; i++) { - if (!set_nonblock(pfd[i].fd, true)) { - remove_fd(pfd, addr, slen, &num, i--); + if (pfds[i].fd == -1) continue; + if (getsockopt(pfds[i].fd, SOL_SOCKET, SO_ERROR, &err, + &errlen) != 0) { + net_connect_abort(pfds); + return -1; } - /* 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 (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; } - 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--); - } - } + /* Still going... */ + errno = EINPROGRESS; + return -1; +} -got_one: - /* We don't want to hand them a non-blocking socket! */ - if (set_nonblock(pfd[i].fd, false)) - sockfd = pfd[i].fd; +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; + } -out: - saved_errno = errno; - for (i = 0; i < num; i++) - if (pfd[i].fd != sockfd) - close(pfd[i].fd); - errno = saved_errno; - return sockfd; + net_connect_abort(pfds); + return -1; } struct addrinfo *net_server_lookup(const char *service, diff --git a/ccan/net/net.h b/ccan/net/net.h index 9ad6de56..5e02fc36 100644 --- a/ccan/net/net.h +++ b/ccan/net/net.h @@ -1,6 +1,10 @@ /* Licensed under BSD-MIT - see LICENSE file for details */ #ifndef CCAN_NET_H #define CCAN_NET_H +#include + +struct pollfd; + /** * net_client_lookup - look up a network name to connect to. * @hostname: the name to look up @@ -16,6 +20,7 @@ * #include * #include * #include + * #include * #include * ... * struct addrinfo *addr; @@ -48,6 +53,67 @@ struct addrinfo *net_client_lookup(const char *hostname, */ int net_connect(const struct addrinfo *addrinfo); +/** + * net_connect_async - initiate connect to a server + * @addrinfo: linked list struct addrinfo (usually from net_client_lookup). + * @pfds: array of two struct pollfd. + * + * This begins connecting to a server described by @addrinfo, + * and places the one or two file descriptors into pfds[0] and pfds[1]. + * It returns a valid file descriptor if connect() returned immediately. + * + * Otherwise it returns -1 and sets errno, most likely EINPROGRESS. + * In this case, poll() on pfds and call net_connect_complete(). + * + * Example: + * struct pollfd pfds[2]; + * ... + * fd = net_connect_async(addr, pfds); + * if (fd < 0 && errno != EINPROGRESS) + * err(1, "Failed to connect to ccan.ozlabs.org"); + */ +int net_connect_async(const struct addrinfo *addrinfo, struct pollfd *pfds); + +/** + * net_connect_complete - complete net_connect_async call. + * @pfds: array of two struct pollfd handed to net_connect_async. + * + * When poll() reports some activity, this determines whether a connection + * has completed. If so, it cleans up and returns the connected fd. + * Otherwise, it returns -1, and sets errno (usually EINPROGRESS). + * + * Example: + * // After net_connect_async. + * while (fd < 0 && errno == EINPROGRESS) { + * // Wait for activity... + * poll(pfds, 2, -1); + * fd = net_connect_complete(pfds); + * } + * if (fd < 0) + * err(1, "connecting"); + * printf("Connected on fd %i!\n", fd); + */ +int net_connect_complete(struct pollfd *pfds); + +/** + * net_connect_abort - abort a net_connect_async call. + * @pfds: array of two struct pollfd handed to net_connect_async. + * + * Closes the file descriptors. + * + * Example: + * // After net_connect_async. + * if (poll(pfds, 2, 1000) == 0) { // Timeout. + * net_connect_abort(pfds); + * fd = -1; + * } else { + * fd = net_connect_complete(pfds); + * if (fd < 0) + * err(1, "connecting"); + * } + */ +void net_connect_abort(struct pollfd *pfds); + /** * net_server_lookup - look up a service name to bind to. * @service: the service to look up -- 2.39.2