public
Created

Verify sanity with regards to bind() on sockets when receiving info from getaddrinfo(). AF_INET vs AF_INET6.

  • Download Gist
getaddrinfo.c
C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
/*
* 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;
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.