Usage of getaddrinfo() with AI_PASSIVE

glglgl picture glglgl · Nov 13, 2011 · Viewed 7.9k times · Source

The getaddrinfo() function not only allows for client programs to efficiently find the correct data for creating a socket to a given host, it also allows for servers to bind to the correct socket - in theory.

I just learned about that and started to play around with it via Python:

from socket import *
for i in getaddrinfo(None, 22, AF_UNSPEC, SOCK_STREAM, IPPROTO_IP, AI_PASSIVE): i

yields

(2, 1, 6, '', ('0.0.0.0', 22))
(10, 1, 6, '', ('::', 22, 0, 0))

what makes me wonder about if there is something wrong.

What exactly am I supposed to do with these answers? Should I

  • make a listen()ing socket of all of these answers, or should I
  • just pick the first one which really works?

The example in the manpage suggests me to only take the first one and be happy with it if it is error-free, but then I only get a connection via IPv4 n my example.

But if I try all of them, I have to worry with 2 server sockets, which is unnecessary due to the fact that IPv6 server sockets also listen to IPv4 if certain conditions are met (OS, socket flags etc.).

Where am I thinking wrong?


EDIT: Obviously, I'm not thinking wrong, but my PC does the wrong thing. I use the default /etc/gai.conf shipped with OpenSUSE. It would be nice if anyone could point me towards the right direction.

EDIT 2: In the given case, strace gives the following calls made internally after reading /etc/gai.conf (now with port 54321, as I thought that using port 22 might have some bad influence, which was not the case):

socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET6, sin6_port=htons(54321), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = 0
getsockname(3, {sa_family=AF_INET6, sin6_port=htons(38289), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [28]) = 0
connect(3, {sa_family=AF_UNSPEC, sa_data="\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, 16) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(54321), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
getsockname(3, {sa_family=AF_INET6, sin6_port=htons(60866), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [28]) = 0
close(3)                                = 0

Obviously, the decision is intended to take place according to the results of the getsockname() calls...

BTW: https://bugs.launchpad.net/ubuntu/+source/eglibc/+bug/673708 and the other bug reports mentionned there confirm my observations. Several people there claim that the new behaviour is correct, so I'm obviously stuck to using AF_INET6... :-(

Answer

Per Johansson picture Per Johansson · Nov 13, 2011

Your getaddrinfo is returning the wrong result for some reason. It's supposed to return the IPv6 socket first. The only thing I can think of is if your OS detects that your system has a low prio IPv6 (6to4 or Teredo) and avoids them, IMO wrongly so in that case. Edit: Just noticed my own computer does the same thing, I use 6to4.

However, you can either listen to both of them, or use AF_INET6 instead of AF_UNSPEC. Then you can do setsockopt to disable IPV6_V6ONLY.

getaddrinfo does the reasonable thing here and returns all applicable results (though in the wrong order, as I mentioned). Both one and two listen sockets are valid approaches, depending on your application.