Skip to content

Instantly share code, notes, and snippets.

@digitalresistor
Created August 2, 2012 00:30
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save digitalresistor/3231892 to your computer and use it in GitHub Desktop.
Save digitalresistor/3231892 to your computer and use it in GitHub Desktop.
Verify sanity with regards to bind() on sockets when receiving info from getaddrinfo(). AF_INET vs AF_INET6.
/*
* 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