I am trying to write a server application that listens to both IPv6 and IPv4 connections. The proper way to accomplish this seems to be listening on IPv6 address, which will also accept IPv4 connections.
The relevant piece of code is:
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
getaddrinfo(NULL, MYPORT, &hints, &res);
(pretty much copypasted from Beej's Guide)
The problem is that at least on my system,
getaddrinfo returns an entry with
AF_INET first and
AF_INET6 second -- whereas client's
AF_INET6 first, as per spec. With my naive approach, server picks IPv4, client picks IPv6 and the connection fails.
I tried to fix this by setting
hints.ai_family = AF_INET6, but that fails on systems where IPv6 is not available.
I see two obvious solutions:
a) try asking for IPv6 first and fall back to IPv4 if that fails, or
b) walk through results of
getaddrinfo, look for IPv6, and if is not present, pick the first entry
but i don't like either one too much ;) I feel that there should be a way to convince
getaddrinfo to do the right thing, or perhaps a different way to accomplish my goal.
The order of the address that
getaddrinfo() returns is unspecified, so you have to be prepared to handle either case. That probably means traversing the list, keeping track of the "best address seen so far".
Alternatively, you could try to
listen() on all the addresses returned by
getaddrinfo(). This is probably the best option, since some OSes do not accept IPv4 connections to IPv6 sockets listening on
Your code should work the way you described. Unfortunately, there's a bug in glibc as described in launchpad bug #673708, which causes it to choose IPv4 first.
There is a work-around which can be done on each Linux computer on which you're running your server program: edit
/etc/gai.conf, enable all the default rules (uncomment them):
label ::1/128 0 label ::/0 1 label 2002::/16 2 label ::/96 3 label ::ffff:0:0/96 4 label fec0::/10 5 label fc00::/7 6 label 2001:0::/32 7
label ::ffff:7f00:1/128 8
Then your code should open IPv6 if supported, and will also accept IPv4 connections.
If the above is not practical (it's only practical if you're willing to change the configuration on every computer you run on), then modify your code to prefer IPv6. E.g. I've done this:
IPV6_V6ONLYsocket option to support both IPv6 and IPv4.