Browse Source

net: add server support.

Creating a server for IPv4 and IPv6 has similar issues to clients,
with some novel twists.  Slightly different arguments need to be given
to getaddrinfo(), but worse, some platforms (Linux without
/proc/sys/net/ipv6/bindv6only set) automatically bind IPv6 sockets to IPv4
ports as well.

Thus we need a function which can bind (and listen) to one or two fds.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Rusty Russell 13 years ago
parent
commit
81f2204cea
4 changed files with 355 additions and 2 deletions
  1. 4 2
      ccan/net/_info
  2. 88 0
      ccan/net/net.c
  3. 51 0
      ccan/net/net.h
  4. 212 0
      ccan/net/test/run-bind.c

+ 4 - 2
ccan/net/_info

@@ -2,9 +2,11 @@
 #include "config.h"
 
 /**
- * net - simple IPv4/IPv6 client library
+ * net - simple IPv4/IPv6 socket library
  *
- * This code makes it simple to support IPv4 and IPv6 without speed penalty.
+ * This code makes it simple to support IPv4 and IPv6 without speed penalty
+ * in clients by using non-blocking simultaneous connect, and using
+ * a convenience function to create both IPv4 and IPv6 sockets for servers.
  *
  * License: MIT
  *

+ 88 - 0
ccan/net/net.c

@@ -150,3 +150,91 @@ out:
 	errno = saved_errno;
 	return sockfd;
 }
+
+struct addrinfo *net_server_lookup(const char *service,
+				   int family,
+				   int socktype)
+{
+	struct addrinfo *res, hints;
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = family;
+	hints.ai_socktype = socktype;
+	hints.ai_flags = AI_PASSIVE;
+	hints.ai_protocol = 0;
+
+	if (getaddrinfo(NULL, service, &hints, &res) != 0)
+		return NULL;
+
+	return res;
+}
+
+static bool should_listen(const struct addrinfo *addrinfo)
+{
+#ifdef SOCK_SEQPACKET
+	if (addrinfo->ai_socktype == SOCK_SEQPACKET)
+		return true;
+#endif
+	return (addrinfo->ai_socktype == SOCK_STREAM);
+}
+
+static int make_listen_fd(const struct addrinfo *addrinfo)
+{
+	int saved_errno, fd, on = 1;
+
+	fd = socket(addrinfo->ai_family, addrinfo->ai_socktype,
+		    addrinfo->ai_protocol);
+	if (fd < 0)
+		return -1;
+
+	setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+	if (bind(fd, addrinfo->ai_addr, addrinfo->ai_addrlen) != 0)
+		goto fail;
+
+	if (should_listen(addrinfo) && listen(fd, 5) != 0)
+		goto fail;
+	return fd;
+
+fail:
+	saved_errno = errno;
+	close(fd);
+	errno = saved_errno;
+	return -1;
+}
+
+int net_bind(const struct addrinfo *addrinfo, int fds[2])
+{
+	const struct addrinfo *ipv6, *ipv4;
+	unsigned int num;
+
+	if (addrinfo->ai_family == AF_INET)
+		ipv4 = addrinfo;
+	else if (addrinfo->ai_family == AF_INET6)
+		ipv6 = addrinfo;
+
+	if (addrinfo->ai_next) {
+		if (addrinfo->ai_next->ai_family == AF_INET)
+			ipv4 = addrinfo->ai_next;
+		else if (addrinfo->ai_next->ai_family == AF_INET6)
+			ipv6 = addrinfo->ai_next;
+	}
+
+	num = 0;
+	/* Take IPv6 first, since it might bind to IPv4 port too. */
+	if (ipv6) {
+		if ((fds[num] = make_listen_fd(ipv6)) >= 0)
+			num++;
+		else
+			ipv6 = NULL;
+	}
+	if (ipv4) {
+		if ((fds[num] = make_listen_fd(ipv4)) >= 0)
+			num++;
+		else
+			ipv4 = NULL;
+	}
+	if (num == 0)
+		return -1;
+
+	return num;
+}

+ 51 - 0
ccan/net/net.h

@@ -47,4 +47,55 @@ struct addrinfo *net_client_lookup(const char *hostname,
  *	freeaddrinfo(addr);
  */
 int net_connect(const struct addrinfo *addrinfo);
+
+/**
+ * net_server_lookup - look up a service name to bind to.
+ * @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 address(es) to bind for our service.
+ *	addr = net_server_lookup("8888", AF_UNSPEC, SOCK_STREAM);
+ *	if (!addr)
+ *		errx(1, "Failed to look up 8888 to bind to");
+ */
+struct addrinfo *net_server_lookup(const char *service,
+				   int family,
+				   int socktype);
+
+/**
+ * net_bind - create listening socket(s)
+ * @addrinfo: the address(es) to bind to.
+ * @fds: array of two fds.
+ *
+ * This will create one (or if necessary) two sockets, mark them
+ * SO_REUSEADDR, bind them to the given address(es), and make them
+ * listen() (if the socket type is SOCK_STREAM or SOCK_SEQPACKET).
+ *
+ * Returns -1 (and sets errno) on error, or 1 or 2 depending on how many
+ * @fds are valid.
+ *
+ * Example:
+ *	int fds[2], i, num_fds;
+ *
+ *	num_fds = net_bind(addr, fds);
+ *	if (num_fds < 0)
+ *		err(1, "Failed to listen on port 8888");
+ *
+ *	for (i = 0; i < num_fds; i++)
+ *		printf(" Got fd %u/%u: %i\n", i, num_fds, fds[i]);
+ */
+int net_bind(const struct addrinfo *addrinfo, int fds[2]);
 #endif /* CCAN_NET_H */

+ 212 - 0
ccan/net/test/run-bind.c

@@ -0,0 +1,212 @@
+#include <ccan/net/net.h>
+#include <ccan/tap/tap.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <err.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+static int ipv6_only;
+
+#ifdef IPV6_V6ONLY
+static int my_setsockopt(int sockfd, int level, int optname,
+			 const void *optval, socklen_t optlen)
+{
+	int ret;
+	setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6_only,
+		   sizeof(ipv6_only));
+	ret = setsockopt(sockfd, level, optname, optval, optlen);
+	return ret;
+}
+#define setsockopt my_setsockopt
+#endif
+
+#include <ccan/net/net.c>
+
+#define TEST_PORT "65001"
+
+static void do_connect(int family, int type)
+{
+	int fd, ret;
+	struct addrinfo *addr;
+	char buf[8];
+
+	/* Just in case... */
+	alarm(5);
+	addr = net_client_lookup(NULL, TEST_PORT, family, type);
+	fd = net_connect(addr);
+	if (fd < 0)
+		err(1, "Failed net_connect");
+	freeaddrinfo(addr);
+
+	ret = write(fd, "Yay!", strlen("Yay!"));
+	if (ret != strlen("Yay!"))
+		err(1, "Write returned %i", ret);
+	ret = read(fd, buf, sizeof(buf));
+	if (ret != 5)
+		err(1, "Read returned %i", ret);
+	if (memcmp(buf, "metoo", ret) != 0)
+		err(1, "Read returned '%.*s'", ret, buf);
+	close(fd);
+}
+
+static int wait_for_readable(int fds[2], int num_fds)
+{
+	int i, max_fd = -1;
+	fd_set set;
+
+	FD_ZERO(&set);
+	for (i = 0; i < num_fds; i++) {
+		if (fds[i] > max_fd)
+			max_fd = fds[i];
+		FD_SET(fds[i], &set);
+	}
+
+	select(max_fd+1, &set, NULL, NULL, NULL);
+	for (i = 0; i < num_fds; i++) {
+		if (FD_ISSET(fds[i], &set))
+			return i;
+	}
+	return num_fds+1;
+}
+
+int main(void)
+{
+	struct addrinfo *addr;
+	int fds[2], num_fds, i, fd, status, ret;
+	char buf[20];
+	union {
+		struct sockaddr addr;
+		struct sockaddr_in ipv4;
+		struct sockaddr_in6 ipv6;
+	} remote_addr;
+	socklen_t addlen = sizeof(remote_addr);
+
+	plan_tests(35);
+
+	/* Simple TCP test. */
+	addr = net_server_lookup(TEST_PORT, AF_UNSPEC, SOCK_STREAM);
+	ok1(addr);
+	num_fds = net_bind(addr, fds);
+	ok1(num_fds == 1 || num_fds == 2);
+
+	if (!fork()) {
+		for (i = 0; i < num_fds; i++)
+			close(fds[i]);
+		do_connect(AF_UNSPEC, SOCK_STREAM);
+		exit(0);
+	}
+
+	i = wait_for_readable(fds, num_fds);
+	ok1(i < num_fds);
+	fd = accept(fds[i], NULL, NULL);
+	ok1(fd >= 0);
+
+	ret = read(fd, buf, strlen("Yay!"));
+	ok1(ret == strlen("Yay!"));
+	ok1(memcmp(buf, "Yay!", ret) == 0);
+	ret = write(fd, "metoo", strlen("metoo"));
+	ok1(ret == strlen("metoo"));
+	ok1(wait(&status) != -1);
+	ok1(WIFEXITED(status));
+	ok1(WEXITSTATUS(status) == 0);
+	close(fd);
+	for (i = 0; i < num_fds; i++)
+		close(fds[i]);
+
+	/* Simple UDP test. */
+	addr = net_server_lookup(TEST_PORT, AF_UNSPEC, SOCK_DGRAM);
+	ok1(addr);
+	num_fds = net_bind(addr, fds);
+	ok1(num_fds == 1 || num_fds == 2);
+
+	if (!fork()) {
+		for (i = 0; i < num_fds; i++)
+			close(fds[i]);
+		do_connect(AF_UNSPEC, SOCK_DGRAM);
+		exit(0);
+	}
+
+	i = wait_for_readable(fds, num_fds);
+	ok1(i < num_fds);
+	fd = fds[i];
+
+	ret = recvfrom(fd, buf, strlen("Yay!"), 0,
+		       (void *)&remote_addr, &addlen);
+	ok1(ret == strlen("Yay!"));
+	ok1(memcmp(buf, "Yay!", ret) == 0);
+	ret = sendto(fd, "metoo", strlen("metoo"), 0,
+		     (void *)&remote_addr, addlen);
+	ok1(ret == strlen("metoo"));
+	ok1(wait(&status) >= 0);
+	ok1(WIFEXITED(status));
+	ok1(WEXITSTATUS(status) == 0);
+	close(fd);
+	for (i = 0; i < num_fds; i++)
+		close(fds[i]);
+
+/* This seems like a Linux-only extension */
+#ifdef IPV6_V6ONLY
+	/* Try to force separate sockets for IPv4/IPv6, if we can. */
+	if (addr->ai_next)
+		ipv6_only = true;
+#endif
+
+	if (ipv6_only) {
+		int j;
+
+		addr = net_server_lookup(TEST_PORT, AF_UNSPEC, SOCK_STREAM);
+		ok1(addr);
+		num_fds = net_bind(addr, fds);
+		ok1(num_fds == 2);
+		freeaddrinfo(addr);
+
+		if (!fork()) {
+			for (i = 0; i < num_fds; i++)
+				close(fds[i]);
+			do_connect(AF_INET, SOCK_STREAM);
+			do_connect(AF_INET6, SOCK_STREAM);
+			exit(0);
+		}
+
+		i = wait_for_readable(fds, num_fds);
+		ok1(i < num_fds);
+		fd = accept(fds[i], NULL, NULL);
+		ok1(fd >= 0);
+
+		ret = read(fd, buf, strlen("Yay!"));
+		ok1(ret == strlen("Yay!"));
+		ok1(memcmp(buf, "Yay!", ret) == 0);
+		ret = write(fd, "metoo", strlen("metoo"));
+		ok1(ret == strlen("metoo"));
+		close(fd);
+
+		j = wait_for_readable(fds, num_fds);
+		ok1(j < num_fds);
+		ok1(j != i);
+		fd = accept(fds[j], NULL, NULL);
+		ok1(fd >= 0);
+
+		ret = read(fd, buf, strlen("Yay!"));
+		ok1(ret == strlen("Yay!"));
+		ok1(memcmp(buf, "Yay!", ret) == 0);
+		ret = write(fd, "metoo", strlen("metoo"));
+		ok1(ret == strlen("metoo"));
+
+		ok1(wait(&status) >= 0);
+		ok1(WIFEXITED(status));
+		ok1(WEXITSTATUS(status) == 0);
+		close(fd);
+	} else
+		skip(16, "No support for IPv6-only binding");
+
+	for (i = 0; i < num_fds; i++)
+		close(fds[i]);
+
+	/* This exits depending on whether all tests passed */
+	return exit_status();
+}