Browse Source

net: add async operation helpers.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Rusty Russell 12 years ago
parent
commit
158de63fda
3 changed files with 184 additions and 77 deletions
  1. 1 0
      ccan/net/_info
  2. 117 77
      ccan/net/net.c
  3. 66 0
      ccan/net/net.h

+ 1 - 0
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;
 	}
 

+ 117 - 77
ccan/net/net.c

@@ -1,5 +1,6 @@
 /* Licensed under BSD-MIT - see LICENSE file for details */
 #include <ccan/net/net.h>
+#include <ccan/noerr/noerr.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <poll.h>
@@ -11,6 +12,7 @@
 #include <errno.h>
 #include <stdbool.h>
 #include <netinet/in.h>
+#include <assert.h>
 
 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,

+ 66 - 0
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 <stdbool.h>
+
+struct pollfd;
+
 /**
  * net_client_lookup - look up a network name to connect to.
  * @hostname: the name to look up
@@ -16,6 +20,7 @@
  *	#include <sys/socket.h>
  *	#include <stdio.h>
  *	#include <netdb.h>
+ *	#include <poll.h>
  *	#include <err.h>
  *	...
  *	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