net: new module to help IPv4/IPv6 transition.
authorRusty Russell <rusty@rustcorp.com.au>
Fri, 28 Jan 2011 06:40:23 +0000 (17:10 +1030)
committerRusty Russell <rusty@rustcorp.com.au>
Fri, 28 Jan 2011 06:40:23 +0000 (17:10 +1030)
ccan/net/LICENSE [new symlink]
ccan/net/_info [new file with mode: 0644]
ccan/net/net.c [new file with mode: 0644]
ccan/net/net.h [new file with mode: 0644]
ccan/net/test/run.c [new file with mode: 0644]
ccan/net/tools/Makefile [new file with mode: 0644]
ccan/net/tools/test-net.c [new file with mode: 0644]

diff --git a/ccan/net/LICENSE b/ccan/net/LICENSE
new file mode 120000 (symlink)
index 0000000..2354d12
--- /dev/null
@@ -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 (file)
index 0000000..0b38c44
--- /dev/null
@@ -0,0 +1,67 @@
+#include <string.h>
+#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 <rusty@rustcorp.com.au>
+ *
+ * Example:
+ *     #include <ccan/net/net.h>
+ *     #include <sys/types.h>
+ *     #include <sys/socket.h>
+ *     #include <stdio.h>
+ *     #include <err.h>
+ *
+ *     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 <target> [<port>]");
+ *     
+ *             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 (file)
index 0000000..82d6c4e
--- /dev/null
@@ -0,0 +1,149 @@
+#include <ccan/net/net.h>
+#include <ccan/noerr/noerr.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <poll.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdbool.h>
+
+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 (file)
index 0000000..456ca9d
--- /dev/null
@@ -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 <sys/types.h>
+ *     #include <sys/socket.h>
+ *     #include <stdio.h>
+ *     #include <netdb.h>
+ *     #include <err.h>
+ *     ...
+ *     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 (file)
index 0000000..2e241cb
--- /dev/null
@@ -0,0 +1,111 @@
+#include <ccan/net/net.h>
+#include <ccan/net/net.c>
+#include <ccan/tap/tap.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <err.h>
+
+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 (file)
index 0000000..3a22043
--- /dev/null
@@ -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 (file)
index 0000000..07989f8
--- /dev/null
@@ -0,0 +1,56 @@
+#include <ccan/net/net.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <err.h>
+
+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 <target> [<port>]");
+
+       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;
+}