问题描述:

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 getaddrinfo returns 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 bind() and 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 0::0.

网友答案:

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.

Computer configuration solution

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

then add:

label ::ffff:7f00:1/128 8

Then your code should open IPv6 if supported, and will also accept IPv4 connections.

Code solution

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:

  • Do three passes through the getaddrinfo() results.
  • The first pass, prefer IPv6. Attempt to clear the IPV6_V6ONLY socket option to support both IPv6 and IPv4.
  • The second pass, prefer IPv4.
  • The third pass, take whatever is available.
相关阅读:
Top