Commit 81f2204c authored by Rusty Russell's avatar Rusty Russell

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: default avatarRusty Russell <rusty@rustcorp.com.au>
parent 8216fa0b
......@@ -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
*
......
......@@ -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;
}
......@@ -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 */
#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();
}
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