getaddrinfo
cross-platform edge case behavior
An often-needed piece of functionality in network programming is to resolve human-readable host or port names to their numerical equivalent, for example in order to pass the latter to operating system socket APIs. The getaddrinfo
function fills this role on POSIX and Windows. Apart from some flags, it accepts two string parameters for host and service (port) names and returns a list of corresponding IP addresses and port numbers, superseding the older gethostbyname
and getservbyname
functions.
Either of its string parameters is allowed to be null
, representing the local host/all interfaces (depending on whether AI_PASSIVE
is specified) and an automatically assigned port, respectively. Both parameters being null
at the same time, however, is disallowed by the specification, and leads to a EAI_NONAME
error on Posix or WSAHOST_NOT_FOUND
on Windows. What happens if the strings are empty ("\0"
) instead of null
, however, is left open by RFC 2553, and not really mentioned in the operating system API documentation either.
It turns out that there are quite a few differences there between the various operating systems, which is obviously likely to cause issues for Wine (an implementation of the Windows API on Posix/X systems). To get a clear understanding of how the different cases are handled, I put together a little D program which tests a few combinations of host name, port, and flag parameters (see end of post). The snippet could be written in C just the same, as getAddressInfo
directly maps to getaddrinfo
, I just chose D to avoid platform dependencies and writing an unduly large amount of more boilerplate code.
The results are summarized in the following table, where »loopback« means that the IP addresses returned were 127.0.0.1
and ::1
, »catchall« refers to 0.0.0.0
and ::
, »public« means that the actual IP addresses of all available network interfaces were returned, and NONAME
refers to a lookup error. »hostname« means that the actual fully qualified name of the host that ran the test was used (care: the host part of the FQDN only does usually not resolve on OS X).
Host | Port | Flags | Windows | Linux | OS X |
---|---|---|---|---|---|
null | null | - | NONAME | NONAME | NONAME |
AI_PASSIVE | NONAME | NONAME | NONAME | ||
"" | - | loopback | loopback | NONAME | |
AI_PASSIVE | catchall | catchall | NONAME | ||
"0" | - | loopback | loopback | loopback | |
AI_PASSIVE | catchall | catchall | catchall | ||
"80" | - | loopback | loopback | loopback | |
AI_PASSIVE | catchall | catchall | catchall | ||
"" | null | - | public | NONAME | NONAME |
AI_PASSIVE | public | NONAME | NONAME | ||
"" | - | public | NONAME | NONAME | |
AI_PASSIVE | public | NONAME | NONAME | ||
"0" | - | public | NONAME | loopback | |
AI_PASSIVE | public | NONAME | catchall | ||
"80" | - | public | NONAME | loopback | |
AI_PASSIVE | public | NONAME | catchall | ||
"localhost" | null | - | loopback | loopback (v4) | loopback |
AI_PASSIVE | loopback | loopback (v4) | loopback | ||
"" | - | loopback | loopback (v4) | loopback | |
AI_PASSIVE | loopback | loopback (v4) | loopback | ||
"0" | - | loopback | loopback (v4) | loopback | |
AI_PASSIVE | loopback | loopback (v4) | loopback | ||
"80" | - | loopback | loopback (v4) | loopback | |
AI_PASSIVE | loopback | loopback (v4) | loopback | ||
hostname | null | - | public | loopback (v4) | public |
AI_PASSIVE | public | loopback (v4) | public | ||
"" | - | public | loopback (v4) | public | |
AI_PASSIVE | public | loopback (v4) | public | ||
"0" | - | public | loopback (v4) | public | |
AI_PASSIVE | public | loopback (v4) | public | ||
"80" | - | public | loopback (v4) | public | |
AI_PASSIVE | public | loopback (v4) | public |
getaddrinfo()
behavior on Windows Server 2008 R2, Arch Linux (Kernel 3.1.4, glibc 2.14.1), and OS X 10.7.2 (Lion).What caused me to investigate the issue in the first place is the behavior when given an empty, non-null host string: Windows returns the public addresses of the present interfaces, while OS X resolves them to ::1
/::
, but only if a port is given, and Linux doesn’t resolve them at all! Windows generally accepts the most combinations, returning an error only for the explicitly disallowed combination, which is relied on by some applications (e.g. the game League of Legends).
There were also some less significant differences in behavior which are mostly not listed in the table. First, in both of the Linux VMs I tried (an up-to-date Arch box and Ubuntu Oneric), only the IPv4 address of the loopback interface was returned. Second, as in the test no address family, socket type or protocol hints were passed to getaddrinfo()
, each address was returned twice on OS X, once with SOCK_STREAM
/IPPROTO_TCP
and once with SOCK_DGRAM
/IPPROTO_UDP
set. Linux returned three copies of each address, for STREAM
, DGRAM
and RAW
, with the according protocol types set, whereas Windows only returned a single copy with protocol type IPPROTO_IP
and socket type set to 0.
In any case, as a result I have prepared a patch for Wine to emulate at least the succeeding/failing behavior of the Winsock incarnation of getaddrinfo
on Linux and OS X, which should solve the bigger part of the related problems. There ideally shouldn’t be any Windows software relying on details beyond that (such as the actual number/layout of addresses returned), but who knows…