/* | |
* There is inconsistent behaviour regarding the way various operating | |
* systems use IPV6_V6ONLY by default and interactions with bind() to bind a | |
* wildcard socket (as in all available addresses) on an AF_INET6 (IPv6) | |
* socket. | |
* | |
* getaddrinfo() returns two results, the first one being AF_INET and the | |
* second being AF_INET6 (Unless you are on an OS that prefers IPv6, I'm | |
* looking at you OI). We create a socket, bind, and listen on AF_INET first. | |
* At that point we try to create a socket, bind and then listen on AF_INET6. | |
* This is where the inconsistencies become apparent. | |
* | |
* Linux if we ask for AF_UNSPEC we get handed back an IPv4 INADDR_ANY first, | |
* when we bind to that and then get an IPv6 IN6ADDR_ANY_INIT and try to bind | |
* we get a bind() failure because it is also attempting to bind on IPv4. This | |
* is rather unexpected to me, I created the socket on IPv6, it should let me | |
* bind. Even if that means that it needs to not bind on IPv4. This failure | |
* mode does not exist on any other operating systems tested. Linux is the only | |
* one that will return an error on an attempt to bind. | |
* | |
* Mac OS X will let you bind on IPv4, and then when you bind on IPv6 | |
* it will bind on both IPv6 AND IPv4. Netstat output: | |
* | |
* tcp46 0 0 *.7020 *.* LISTEN | |
* tcp4 0 0 *.7020 *.* LISTEN | |
* | |
* FreeBSD has disabled IPv4 mapped on IPv6 addresses for a while now, | |
* so this feature would have to be turned on by a system | |
* administrator. The default is sane and doesn't result in a failure. | |
* What should be determined is what happens when this IPv4 mapped on | |
* IPv6 addresses is turned on, unfortunately I don't have a test | |
* machine to attempt this experiment. | |
* | |
* OpenIndiana has the same sort of mode as Mac OS X, although its | |
* getaddrinfo() will actually return the IPv6 based address first, and | |
* the IPv4 based address second, but both bind's will complete without | |
* issues, and netstat output will look similar to this: | |
* | |
* TCP: IPv6 | |
* Local Address Remote Address Swind Send-Q Rwind Recv-Q State If | |
* -------------------- -------------------- ----- ------ ------ ------ ----------- ----- | |
* *.7020 *.* 0 0 128000 0 LISTEN | |
* | |
* | |
* TCP: IPv4 | |
* Local Address Remote Address Swind Send-Q Rwind Recv-Q State | |
* -------------------- -------------------- ----- ------ ------ ------ ----------- | |
* *.7020 *.* 0 0 128000 0 LISTEN | |
* *.7020 *.* 0 0 128000 0 LISTEN | |
* | |
* The question that still also needs to be answered is which one of | |
* the listening sockets can accept() a connection, whether it is the | |
* IPv4 mapped on IPv6 that gets the connection, or whether the OS | |
* prefers to use the IPv4 socket. | |
* | |
* Oh, and I am so glad that we (Linux, FreeBSD, Mac OS X, OpenIndiana) could | |
* all standardise our address family numbers/defines, same applies for socket | |
* type. Luckily protocol isn't defined by the OS creators. Having the same | |
* number across multiple OS's would make it much simpler to debug across | |
* multiple platforms. C'est la vie! | |
* | |
* Compile: | |
* | |
* clang getaddrinfo.c -o getaddrinfo | |
* | |
* Or if you are testing for failure modes: | |
* | |
* clang getaddrinfo.c -o getaddrinfo -DNOIPV6ONLY | |
* | |
* On OpenIndiana you may also need to link in the sockets library: | |
* | |
* cc getaddrinfo.c -o getaddrinfo -lsockets | |
* | |
* Run: | |
* | |
* ./getaddrinfo | |
* | |
* Sample output (OpenIndiana): | |
* | |
* Address family: AF_INET6 (26) Socket type: SOCK_STREAM (2) Protocol: tcp (6) | |
* Address family: AF_INET (2) Socket type: SOCK_STREAM (2) Protocol: tcp (6) | |
* Waiting for user input to close all sockets and end... <Enter> | |
* closing socket: 4 | |
* closing socket: 5 | |
*/ | |
#include <stdio.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <sys/types.h> | |
#include <sys/socket.h> | |
#include <netdb.h> | |
#include <errno.h> | |
#include <stdlib.h> | |
char * family_type(int af) { | |
switch (af) { | |
case AF_INET: | |
return "AF_INET"; | |
case AF_INET6: | |
return "AF_INET6"; | |
default: | |
return "UNKNOWN"; | |
} | |
} | |
struct socket_links { | |
struct socket_links *next; | |
int sockfd; | |
}; | |
int main() { | |
struct addrinfo hints, *addrlist, *addr; | |
struct socket_links *head, *current; | |
// Thanks great big OS for cleaning up after me ... | |
current = head = malloc(sizeof(struct socket_links)); | |
current->next = 0; | |
memset(&hints, 0, sizeof(hints)); | |
// Ask for TCP | |
hints.ai_socktype = SOCK_STREAM; | |
// Any family works for us ... | |
hints.ai_family = AF_UNSPEC; | |
// Set some hints | |
hints.ai_flags = | |
AI_PASSIVE | // We want to use this with bind | |
AI_NUMERICSERV | // We only pass in numeric port numbers | |
AI_NUMERICHOST; // We only pass in numeric IP addresses | |
int rv; | |
if ((rv = getaddrinfo(0, "7020", &hints, &addrlist)) != 0) { | |
fprintf(stderr, "getaddrinfo: %s", gai_strerror(rv)); | |
return 1; | |
} | |
for (addr = addrlist; addr != 0; addr = addr->ai_next) { | |
struct protoent *proto = getprotobynumber(addr->ai_protocol); | |
printf("Address family: %s (%d)\tSocket type: %s (%d)\tProtocol: %s (%d)\n", | |
family_type(addr->ai_family), | |
addr->ai_family, | |
addr->ai_socktype == SOCK_STREAM ? "SOCK_STREAM" : "other", | |
addr->ai_socktype, | |
proto->p_name, | |
addr->ai_protocol); | |
int sockfd; | |
if ((sockfd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol)) == -1) { | |
fprintf(stderr, "socket: %s\n", strerror(errno)); | |
continue; | |
} | |
int yes = 1; | |
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { | |
close(sockfd); | |
fprintf(stderr, "setsockopt: %s\n", strerror(errno)); | |
continue; | |
} | |
#ifndef NOIPV6ONLY | |
// If the socket is AF_INET6 | |
if (addr->ai_family == AF_INET6) { | |
// Set the socket to be IPv6 only and don't allow IPv4 traffic on this socket | |
// | |
// Now we can bind() without issues on Linux | |
if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(int)) == -1) { | |
close(sockfd); | |
fprintf(stderr, "setsockopt: %s IPV6_V6ONLY\n", strerror(errno)); | |
continue; | |
} | |
} | |
#endif | |
if (bind(sockfd, addr->ai_addr, addr->ai_addrlen) == -1) { | |
close(sockfd); | |
fprintf(stderr, "bind: %s\n", strerror(errno)); | |
continue; | |
} | |
if (listen(sockfd, 20) == -1) { | |
close(sockfd); | |
fprintf(stderr, "listen: %s\n", strerror(errno)); | |
continue; | |
} | |
current->sockfd = sockfd; | |
current->next = malloc(sizeof(struct socket_links)); | |
current = current->next; | |
current->next = 0; | |
current->sockfd = 0; | |
} | |
printf("Waiting for user input to close all sockets and end..."); | |
getchar(); | |
for (current = head; current->sockfd != 0; current = current->next) { | |
printf("closing socket: %d\n", current->sockfd); | |
close(current->sockfd); | |
} | |
freeaddrinfo(addrlist); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment