Commit 04a42d50 authored by Rusty Russell's avatar Rusty Russell

net: new module to help IPv4/IPv6 transition.

parent 02098331
../../licenses/BSD-MIT
\ No newline at end of file
#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;
}
#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;
}
#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 */
#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();
}
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) $<
#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;
}
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