From 04a42d50a02645aca83d7dc02d8e9838540e818d Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 28 Jan 2011 17:10:23 +1030 Subject: [PATCH] net: new module to help IPv4/IPv6 transition. --- ccan/net/LICENSE | 1 + ccan/net/_info | 67 +++++++++++++++++ ccan/net/net.c | 149 ++++++++++++++++++++++++++++++++++++++ ccan/net/net.h | 49 +++++++++++++ ccan/net/test/run.c | 111 ++++++++++++++++++++++++++++ ccan/net/tools/Makefile | 9 +++ ccan/net/tools/test-net.c | 56 ++++++++++++++ 7 files changed, 442 insertions(+) create mode 120000 ccan/net/LICENSE create mode 100644 ccan/net/_info create mode 100644 ccan/net/net.c create mode 100644 ccan/net/net.h create mode 100644 ccan/net/test/run.c create mode 100644 ccan/net/tools/Makefile create mode 100644 ccan/net/tools/test-net.c diff --git a/ccan/net/LICENSE b/ccan/net/LICENSE new file mode 120000 index 00000000..2354d129 --- /dev/null +++ b/ccan/net/LICENSE @@ -0,0 +1 @@ +../../licenses/BSD-MIT \ No newline at end of file diff --git a/ccan/net/_info b/ccan/net/_info new file mode 100644 index 00000000..0b38c440 --- /dev/null +++ b/ccan/net/_info @@ -0,0 +1,67 @@ +#include +#include "config.h" + +/** + * net - simple IPv4/IPv6 client library + * + * This code makes it simple to support IPv4 and IPv6 without speed penalty. + * + * License: MIT + * + * Author: Rusty Russell + * + * Example: + * #include + * #include + * #include + * #include + * #include + * + * int main(int argc, char *argv[]) + * { + * struct addrinfo *addr; + * const char *dest, *port; + * int fd; + * struct sockaddr saddr; + * socklen_t slen = sizeof(saddr); + * + * if (argc == 2) { + * dest = argv[1]; + * port = "http"; + * } else if (argc == 3) { + * dest = argv[1]; + * port = argv[2]; + * } else + * errx(1, "Usage: test-net []"); + * + * addr = net_client_lookup(dest, port, AF_UNSPEC, SOCK_STREAM); + * if (!addr) + * err(1, "Failed to look up %s", dest); + * + * fd = net_connect(addr); + * if (fd < 0) + * err(1, "Failed to connect to %s", dest); + * + * if (getsockname(fd, &saddr, &slen) == 0) + * printf("Connected via %s\n", + * saddr.sa_family == AF_INET6 ? "IPv6" + * : saddr.sa_family == AF_INET ? "IPv4" + * : "UNKNOWN??"); + * else + * err(1, "Failed to get socket type for connection"); + * return 0; + * } + */ +int main(int argc, char *argv[]) +{ + /* Expect exactly one argument */ + if (argc != 2) + return 1; + + if (strcmp(argv[1], "depends") == 0) { + printf("ccan/noerr\n"); + return 0; + } + + return 1; +} diff --git a/ccan/net/net.c b/ccan/net/net.c new file mode 100644 index 00000000..82d6c4e5 --- /dev/null +++ b/ccan/net/net.c @@ -0,0 +1,149 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct addrinfo *net_client_lookup(const char *hostname, + const char *service, + int family, + int socktype) +{ + struct addrinfo hints; + struct addrinfo *res; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = socktype; + hints.ai_flags = 0; + hints.ai_protocol = 0; + + if (getaddrinfo(hostname, service, &hints, &res) != 0) + return NULL; + + return res; +} + +static bool set_nonblock(int fd, bool nonblock) +{ + long flags; + + flags = fcntl(fd, F_GETFL); + if (flags == -1) + return false; + + if (nonblock) + flags |= O_NONBLOCK; + else + flags &= ~(long)O_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) +{ + 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 net_connect(const struct addrinfo *addrinfo) +{ + int sockfd = -1; + 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]; + + for (; addrinfo; addrinfo = addrinfo->ai_next) { + switch (addrinfo->ai_family) { + case AF_INET: + if (!ipv4) + ipv4 = addrinfo; + break; + case AF_INET6: + if (!ipv6) + ipv6 = addrinfo; + break; + } + } + + 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++; + } + 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++; + } + + for (i = 0; i < num; i++) { + if (!set_nonblock(pfd[i].fd, true)) { + remove_fd(pfd, addr, slen, &num, i--); + 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--); + } + 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--); + } + } + +got_one: + /* We don't want to hand them a non-blocking socket! */ + if (set_nonblock(pfd[i].fd, false)) + sockfd = pfd[i].fd; + +out: + for (i = 0; i < num; i++) + if (pfd[i].fd != sockfd) + close_noerr(pfd[i].fd); + return sockfd; +} diff --git a/ccan/net/net.h b/ccan/net/net.h new file mode 100644 index 00000000..456ca9d6 --- /dev/null +++ b/ccan/net/net.h @@ -0,0 +1,49 @@ +#ifndef CCAN_NET_H +#define CCAN_NET_H +/** + * net_client_lookup - look up a network name to connect to. + * @hostname: the name to look up + * @service: the service to look up + * @family: Usually AF_UNSPEC, otherwise AF_INET or AF_INET6. + * @socktype: SOCK_DGRAM or SOCK_STREAM. + * + * This will do a synchronous lookup of a given name, returning a linked list + * of results, or NULL on error. You should use freeaddrinfo() to free it. + * + * Example: + * #include + * #include + * #include + * #include + * #include + * ... + * struct addrinfo *addr; + * + * // Get a TCP connection to ccan.ozlabs.org daytime port. + * addr = net_client_lookup("ccan.ozlabs.org", "daytime", + * AF_UNSPEC, SOCK_STREAM); + * if (!addr) + * errx(1, "Failed to look up daytime at ccan.ozlabs.org"); + */ +struct addrinfo *net_client_lookup(const char *hostname, + const char *service, + int family, + int socktype); + +/** + * net_connect - connect to a server + * @addrinfo: linked list struct addrinfo (usually from net_client_lookup). + * + * This synchronously connects to a server described by @addrinfo, or returns + * -1 on error (and sets errno). + * + * Example: + * int fd; + * ... + * fd = net_connect(addr); + * if (fd < 0) + * err(1, "Failed to connect to ccan.ozlabs.org"); + * freeaddrinfo(addr); + */ +int net_connect(const struct addrinfo *addrinfo); +#endif /* CCAN_NET_H */ diff --git a/ccan/net/test/run.c b/ccan/net/test/run.c new file mode 100644 index 00000000..2e241cbb --- /dev/null +++ b/ccan/net/test/run.c @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include +#include + +static unsigned int server(int protocol, int type) +{ + int sock; + union { + struct sockaddr addr; + struct sockaddr_in ipv4; + struct sockaddr_in6 ipv6; + } addr; + socklen_t addlen = sizeof(addr); + + sock = socket(protocol, type, 0); + + /* Bind to free port. */ + listen(sock, 0); + + /* Figure out what port it gave us. */ + getsockname(sock, &addr.addr, &addlen); + fflush(stdout); + + if (fork() == 0) { + int ret, fd; + + alarm(3); + fd = accept(sock, NULL, 0); + if (fd < 0) + err(1, "Accepting from socket %i", sock); + + ret = write(fd, "Yay!", strlen("Yay!")); + if (ret != strlen("Yay!")) + err(1, "Write returned %i", ret); + exit(0); + } + close(sock); + return ntohs(protocol == AF_INET + ? addr.ipv4.sin_port : addr.ipv6.sin6_port); +} + +int main(void) +{ + struct addrinfo *addr, *addr2; + int fd, status; + struct sockaddr saddr; + socklen_t slen = sizeof(saddr); + char buf[20]; + unsigned int port; + + plan_tests(16); + port = server(AF_INET, SOCK_STREAM); + sprintf(buf, "%u", port); + + addr = net_client_lookup("localhost", buf, AF_UNSPEC, SOCK_STREAM); + addr2 = net_client_lookup("ip6-localhost", buf, + AF_UNSPEC, SOCK_STREAM); + ok1(addr); + ok1(addr2); + /* Join them as if they were from one lookup. */ + addr->ai_next = addr2; + + fd = net_connect(addr); + ok1(fd >= 0); + + ok1(getsockname(fd, &saddr, &slen) == 0); + ok1(saddr.sa_family == AF_INET); + status = read(fd, buf, sizeof(buf)); + ok(status == strlen("Yay!"), + "Read returned %i (%s)", status, strerror(errno)); + ok1(strncmp(buf, "Yay!", strlen("Yay!")) == 0); + close(fd); + addr->ai_next = NULL; + freeaddrinfo(addr); + freeaddrinfo(addr2); + + port = server(AF_INET6, SOCK_STREAM); + sprintf(buf, "%u", port); + + addr = net_client_lookup("localhost", buf, AF_UNSPEC, SOCK_STREAM); + addr2 = net_client_lookup("ip6-localhost", buf, + AF_UNSPEC, SOCK_STREAM); + ok1(addr); + ok1(addr2); + /* Join them as if they were from one lookup. */ + addr->ai_next = addr2; + + fd = net_connect(addr); + ok1(fd >= 0); + + ok1(getsockname(fd, &saddr, &slen) == 0); + ok1(saddr.sa_family == AF_INET6); + status = read(fd, buf, sizeof(buf)); + ok(status == strlen("Yay!"), + "Read returned %i (%s)", status, strerror(errno)); + ok1(strncmp(buf, "Yay!", strlen("Yay!")) == 0); + close(fd); + addr->ai_next = NULL; + freeaddrinfo(addr); + freeaddrinfo(addr2); + + wait(&status); + ok1(WIFEXITED(status)); + ok1(WEXITSTATUS(status) == 0); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/net/tools/Makefile b/ccan/net/tools/Makefile new file mode 100644 index 00000000..3a220438 --- /dev/null +++ b/ccan/net/tools/Makefile @@ -0,0 +1,9 @@ +CFLAGS=-g -Wall -W -I../../.. + +test-net: test-net.o net.o noerr.o + +net.o: ../net.c + $(COMPILE.c) $(OUTPUT_OPTION) $< + +noerr.o: ../../noerr/noerr.c + $(COMPILE.c) $(OUTPUT_OPTION) $< diff --git a/ccan/net/tools/test-net.c b/ccan/net/tools/test-net.c new file mode 100644 index 00000000..07989f87 --- /dev/null +++ b/ccan/net/tools/test-net.c @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include +#include + +static unsigned int count_addrs(const struct addrinfo *addr, int family) +{ + unsigned int num = 0; + + while (addr) { + if (family == AF_UNSPEC || family == addr->ai_family) + num++; + addr = addr->ai_next; + } + return num; +} + +int main(int argc, char *argv[]) +{ + struct addrinfo *addr; + const char *dest, *port; + int fd; + struct sockaddr saddr; + socklen_t slen = sizeof(saddr); + + if (argc == 2) { + dest = argv[1]; + port = "http"; + } else if (argc == 3) { + dest = argv[1]; + port = argv[2]; + } else + errx(1, "Usage: test-net []"); + + addr = net_client_lookup(dest, port, AF_UNSPEC, SOCK_STREAM); + if (!addr) + err(1, "Failed to look up %s", dest); + + printf("Received %u IPv4 addresses, %u IPv6 addresses\n", + count_addrs(addr, AF_INET), count_addrs(addr, AF_INET6)); + + fd = net_connect(addr); + if (fd < 0) + err(1, "Failed to connect to %s", dest); + + if (getsockname(fd, &saddr, &slen) == 0) + printf("Connected via %s\n", + saddr.sa_family == AF_INET6 ? "IPv6" + : saddr.sa_family == AF_INET ? "IPv4" + : "UNKNOWN??"); + else + err(1, "Failed to get socket type for connection"); + return 0; +} -- 2.39.2