Commit 158de63f authored by Rusty Russell's avatar Rusty Russell

net: add async operation helpers.

Signed-off-by: default avatarRusty Russell <rusty@rustcorp.com.au>
parent 3e9d2361
...@@ -66,6 +66,7 @@ int main(int argc, char *argv[]) ...@@ -66,6 +66,7 @@ int main(int argc, char *argv[])
return 1; return 1;
if (strcmp(argv[1], "depends") == 0) { if (strcmp(argv[1], "depends") == 0) {
printf("ccan/noerr\n");
return 0; return 0;
} }
......
/* Licensed under BSD-MIT - see LICENSE file for details */ /* Licensed under BSD-MIT - see LICENSE file for details */
#include <ccan/net/net.h> #include <ccan/net/net.h>
#include <ccan/noerr/noerr.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <poll.h> #include <poll.h>
...@@ -11,6 +12,7 @@ ...@@ -11,6 +12,7 @@
#include <errno.h> #include <errno.h>
#include <stdbool.h> #include <stdbool.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <assert.h>
struct addrinfo *net_client_lookup(const char *hostname, struct addrinfo *net_client_lookup(const char *hostname,
const char *service, const char *service,
...@@ -48,107 +50,145 @@ static bool set_nonblock(int fd, bool nonblock) ...@@ -48,107 +50,145 @@ static bool set_nonblock(int fd, bool nonblock)
return (fcntl(fd, F_SETFL, flags) == 0); return (fcntl(fd, F_SETFL, flags) == 0);
} }
/* We only handle IPv4 and IPv6 */ static int start_connect(const struct addrinfo *addr, bool *immediate)
#define MAX_PROTOS 2
static void remove_fd(struct pollfd pfd[],
const struct addrinfo *addr[],
socklen_t slen[],
unsigned int *num,
unsigned int i)
{ {
memmove(pfd + i, pfd + i + 1, (*num - i - 1) * sizeof(pfd[0])); int fd;
memmove(addr + i, addr + i + 1, (*num - i - 1) * sizeof(addr[0]));
memmove(slen + i, slen + i + 1, (*num - i - 1) * sizeof(slen[0])); *immediate = false;
(*num)--;
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; const struct addrinfo *addr[2] = { NULL, NULL };
unsigned int i, num; unsigned int i;
const struct addrinfo *ipv4 = NULL, *ipv6 = NULL;
const struct addrinfo *addr[MAX_PROTOS];
socklen_t slen[MAX_PROTOS];
struct pollfd pfd[MAX_PROTOS];
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) { for (; addrinfo; addrinfo = addrinfo->ai_next) {
switch (addrinfo->ai_family) { switch (addrinfo->ai_family) {
case AF_INET: case AF_INET:
if (!ipv4) addr[1] = addrinfo;
ipv4 = addrinfo;
break; break;
case AF_INET6: case AF_INET6:
if (!ipv6) addr[0] = addrinfo;
ipv6 = addrinfo;
break; break;
default:
continue;
} }
} }
num = 0; /* In case we found nothing. */
/* We give IPv6 a slight edge by connecting it first. */ errno = ENOENT;
if (ipv6) { for (i = 0; i < 2; i++) {
addr[num] = ipv6; bool immediate;
slen[num] = sizeof(struct sockaddr_in6);
pfd[num].fd = socket(AF_INET6, ipv6->ai_socktype, if (!addr[i])
ipv6->ai_protocol); continue;
if (pfd[num].fd != -1)
num++; 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; if (pfds[0].fd != -1 || pfds[1].fd != -1)
slen[num] = sizeof(struct sockaddr_in); errno = EINPROGRESS;
pfd[num].fd = socket(AF_INET, ipv4->ai_socktype, return -1;
ipv4->ai_protocol); }
if (pfd[num].fd != -1)
num++; 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 (pfds[i].fd == -1)
if (!set_nonblock(pfd[i].fd, true)) {
remove_fd(pfd, addr, slen, &num, i--);
continue; continue;
if (getsockopt(pfds[i].fd, SOL_SOCKET, SO_ERROR, &err,
&errlen) != 0) {
net_connect_abort(pfds);
return -1;
} }
/* Connect *can* be instant. */ if (err == 0) {
if (connect(pfd[i].fd, addr[i]->ai_addr, slen[i]) == 0) /* Don't hand them non-blocking fd! */
goto got_one; if (!set_nonblock(pfds[i].fd, false)) {
if (errno != EINPROGRESS) { net_connect_abort(pfds);
/* Remove dead one. */ return -1;
remove_fd(pfd, addr, slen, &num, i--); }
/* 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) { /* Still going... */
for (i = 0; i < num; i++) { errno = EINPROGRESS;
int err; return -1;
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--);
}
}
got_one: int net_connect(const struct addrinfo *addrinfo)
/* We don't want to hand them a non-blocking socket! */ {
if (set_nonblock(pfd[i].fd, false)) struct pollfd pfds[2];
sockfd = pfd[i].fd; 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: net_connect_abort(pfds);
saved_errno = errno; return -1;
for (i = 0; i < num; i++)
if (pfd[i].fd != sockfd)
close(pfd[i].fd);
errno = saved_errno;
return sockfd;
} }
struct addrinfo *net_server_lookup(const char *service, struct addrinfo *net_server_lookup(const char *service,
......
/* Licensed under BSD-MIT - see LICENSE file for details */ /* Licensed under BSD-MIT - see LICENSE file for details */
#ifndef CCAN_NET_H #ifndef CCAN_NET_H
#define CCAN_NET_H #define CCAN_NET_H
#include <stdbool.h>
struct pollfd;
/** /**
* net_client_lookup - look up a network name to connect to. * net_client_lookup - look up a network name to connect to.
* @hostname: the name to look up * @hostname: the name to look up
...@@ -16,6 +20,7 @@ ...@@ -16,6 +20,7 @@
* #include <sys/socket.h> * #include <sys/socket.h>
* #include <stdio.h> * #include <stdio.h>
* #include <netdb.h> * #include <netdb.h>
* #include <poll.h>
* #include <err.h> * #include <err.h>
* ... * ...
* struct addrinfo *addr; * struct addrinfo *addr;
...@@ -48,6 +53,67 @@ struct addrinfo *net_client_lookup(const char *hostname, ...@@ -48,6 +53,67 @@ struct addrinfo *net_client_lookup(const char *hostname,
*/ */
int net_connect(const struct addrinfo *addrinfo); 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. * net_server_lookup - look up a service name to bind to.
* @service: the service to look up * @service: the service to look up
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment