X-Git-Url: http://git.archive.openwrt.org/?p=project%2Flibubox.git;a=blobdiff_plain;f=usock.c;h=0ce5390434d2ccae31bd0d9ec49c7e74e2e47c23;hp=64eab9e12cb3f3edcb3a5584b4c586ea0da551a8;hb=7a1057604ed2ad83434ed3be03728065b14a5e9c;hpb=fa73496098893e90330040b8f43f2773b42eed12 diff --git a/usock.c b/usock.c index 64eab9e..0ce5390 100644 --- a/usock.c +++ b/usock.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -27,8 +28,10 @@ #include #include #include +#include #include "usock.h" +#include "utils.h" static void usock_set_flags(int sock, unsigned int type) { @@ -39,7 +42,7 @@ static void usock_set_flags(int sock, unsigned int type) fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK); } -static int usock_connect(struct sockaddr *sa, int sa_len, int family, int socktype, bool server) +static int usock_connect(int type, struct sockaddr *sa, int sa_len, int family, int socktype, bool server) { int sock; @@ -47,6 +50,8 @@ static int usock_connect(struct sockaddr *sa, int sa_len, int family, int sockty if (sock < 0) return -1; + usock_set_flags(sock, type); + if (server) { const int one = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); @@ -63,9 +68,11 @@ static int usock_connect(struct sockaddr *sa, int sa_len, int family, int sockty return -1; } -static int usock_unix(const char *host, int socktype, bool server) +static int usock_unix(int type, const char *host) { struct sockaddr_un sun = {.sun_family = AF_UNIX}; + bool server = !!(type & USOCK_SERVER); + int socktype = ((type & 0xff) == USOCK_TCP) ? SOCK_STREAM : SOCK_DGRAM; if (strlen(host) >= sizeof(sun.sun_path)) { errno = EINVAL; @@ -73,11 +80,65 @@ static int usock_unix(const char *host, int socktype, bool server) } strcpy(sun.sun_path, host); - return usock_connect((struct sockaddr*)&sun, sizeof(sun), AF_UNIX, socktype, server); + return usock_connect(type, (struct sockaddr*)&sun, sizeof(sun), AF_UNIX, socktype, server); +} + +static int +usock_inet_notimeout(int type, struct addrinfo *result, void *addr) +{ + struct addrinfo *rp; + int socktype = ((type & 0xff) == USOCK_TCP) ? SOCK_STREAM : SOCK_DGRAM; + bool server = !!(type & USOCK_SERVER); + int sock; + + for (rp = result; rp != NULL; rp = rp->ai_next) { + sock = usock_connect(type, rp->ai_addr, rp->ai_addrlen, rp->ai_family, socktype, server); + if (sock >= 0) { + if (addr) + memcpy(addr, rp->ai_addr, rp->ai_addrlen); + return sock; + } + } + + return -1; +} + +static int poll_restart(struct pollfd *fds, int nfds, int timeout) +{ + struct timespec ts, cur; + int msec = timeout % 1000; + int ret; + + clock_gettime(CLOCK_MONOTONIC, &ts); + + ts.tv_nsec += msec * 1000000; + if (ts.tv_nsec > 1000000000) { + ts.tv_sec++; + ts.tv_nsec -= 1000000000; + } + ts.tv_sec += timeout / 1000; + + while (1) { + ret = poll(fds, nfds, timeout); + if (ret == EAGAIN) + continue; + + if (ret != EINTR) + return ret; + + clock_gettime(CLOCK_MONOTONIC, &cur); + timeout = (ts.tv_sec - cur.tv_sec) * 1000; + timeout += (ts.tv_nsec - cur.tv_nsec) / 1000000; + if (timeout <= 0) + return 0; + } } -static int usock_inet(int type, const char *host, const char *service, int socktype, bool server) +int usock_inet_timeout(int type, const char *host, const char *service, + void *addr, int timeout) { + int socktype = ((type & 0xff) == USOCK_TCP) ? SOCK_STREAM : SOCK_DGRAM; + bool server = !!(type & USOCK_SERVER); struct addrinfo *result, *rp; struct addrinfo hints = { .ai_family = (type & USOCK_IPV6ONLY) ? AF_INET6 : @@ -87,17 +148,94 @@ static int usock_inet(int type, const char *host, const char *service, int sockt | ((type & USOCK_SERVER) ? AI_PASSIVE : 0) | ((type & USOCK_NUMERIC) ? AI_NUMERICHOST : 0), }; + struct addrinfo *rp_v6 = NULL; + struct addrinfo *rp_v4 = NULL; + struct pollfd pfds[2] = { + { .fd = -1, .events = POLLOUT }, + { .fd = -1, .events = POLLOUT }, + }; int sock = -1; + int i; if (getaddrinfo(host, service, &hints, &result)) return -1; + if (timeout <= 0 || server) { + sock = usock_inet_notimeout(type, result, addr); + goto free_addrinfo; + } + for (rp = result; rp != NULL; rp = rp->ai_next) { - sock = usock_connect(rp->ai_addr, rp->ai_addrlen, rp->ai_family, socktype, server); - if (sock >= 0) - break; + if (rp->ai_family == AF_INET6 && !rp_v6) + rp_v6 = rp; + if (rp->ai_family == AF_INET && !rp_v4) + rp_v4 = rp; + } + + if (!rp_v6 && !rp_v4) + goto out; + + if (rp_v6) { + rp = rp_v6; + pfds[0].fd = usock_connect(type | USOCK_NONBLOCK, rp->ai_addr, + rp->ai_addrlen, rp->ai_family, + socktype, server); + if (pfds[0].fd < 0) { + rp_v6 = NULL; + goto try_v4; + } + + if (timeout > 300) { + if (poll_restart(pfds, 1, 300) == 1) { + rp = rp_v6; + sock = pfds[0].fd; + goto out; + } + } + timeout -= 300; } +try_v4: + if (rp_v4) { + rp = rp_v4; + pfds[1].fd = usock_connect(type | USOCK_NONBLOCK, rp->ai_addr, + rp->ai_addrlen, rp->ai_family, + socktype, server); + if (pfds[1].fd < 0) { + rp_v4 = NULL; + if (!rp_v6) + goto out; + goto wait; + } + } + +wait: + poll_restart(pfds + !rp_v6, !!rp_v6 + !!rp_v4, timeout); + if (pfds[0].revents & POLLOUT) { + rp = rp_v6; + sock = pfds[0].fd; + goto out; + } + + if (pfds[1].revents & POLLOUT) { + rp = rp_v4; + sock = pfds[1].fd; + goto out; + } + +out: + for (i = 0; i < 2; i++) { + int fd = pfds[i].fd; + if (fd >= 0 && fd != sock) + close(fd); + } + + if (!(type & USOCK_NONBLOCK)) + fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) & ~O_NONBLOCK); + + if (addr && sock >= 0) + memcpy(addr, rp->ai_addr, rp->ai_addrlen); +free_addrinfo: freeaddrinfo(result); return sock; } @@ -115,18 +253,41 @@ const char *usock_port(int port) } int usock(int type, const char *host, const char *service) { - int socktype = ((type & 0xff) == USOCK_TCP) ? SOCK_STREAM : SOCK_DGRAM; - bool server = !!(type & USOCK_SERVER); int sock; if (type & USOCK_UNIX) - sock = usock_unix(host, socktype, server); + sock = usock_unix(type, host); else - sock = usock_inet(type, host, service, socktype, server); + sock = usock_inet(type, host, service, NULL); if (sock < 0) return -1; - usock_set_flags(sock, type); return sock; } + +int usock_wait_ready(int fd, int msecs) { + struct pollfd fds[1]; + int res; + + fds[0].fd = fd; + fds[0].events = POLLOUT; + + res = poll(fds, 1, msecs); + if (res < 0) { + return errno; + } else if (res == 0) { + return -ETIMEDOUT; + } else { + int err = 0; + socklen_t optlen = sizeof(err); + + res = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &optlen); + if (res) + return errno; + if (err) + return err; + } + + return 0; +}