Name resolution

Name resolution methods

Connecting to services using getaddrinfo()

The getaddrinfo() function is a dualstack-friendly API to name
resolution. It is used by applications to translate host and
service names to a linked list of struct addrinfo
objects. It has its own manual page getaddrinfo(3) in the Linux
Programmer's Manual.

The input of getaddrinfo() consists of node specification,
service specification and further hints.

node: literal IPv4 or IPv6 address, or a hostname to be resolved

service: numeric port number or a symbolic service name

hints.ai_family: enable dualprotocol, IPv4-only or IPv6-only queries

hints.ai_socktype: select socket type

hints.ai_protocol: select transport protocol

Socktype and protocol are somewhat duplicate for TCP/IP stack with just TCP
and UDP. getaddrinfo() can be futher tweaked with the hints.ai_flags.
Other attributes are not supposed to be set in hints (ai_canonname, ai_addr and ai_next).

On success, the error variable is assigned to 0 and result is pointed to
a linked list of one or more struct addrinfo objects.

Never assume that getaddrinfo() returns only one result or that the first result actually works!

Using getaddrinfo() results

It is necesary to try all results until one successfully
connects. This works perfectly for TCP connections as they
can fail gracefully at this stage.

For UDP, connect() succeeds without contacting the other side (if you
are using connect() with udp at all). Therefore you might want to
perform additional actions (such as sending a message and recieving a reply)
before crying out „success!“.

Freeing getaddrinfo() results

When we're done with the results, we'll free the linked list.

freeaddrinfo(result);

Using getaddrinfo() in Python

Python's socket.getaddrinfo() API tries to be
a little bit more sane than the C API.

AI_V4MAPPED: I don't see any real use for this, only returns mapped IPv4 if there are no IPv6 addresses

Connecting to multiple transport channels

Some applications need to open several TCP or UDP channels to the same host. The
classic usage of getaddrinfo() returns a linked list of addrinfo
objects for just one channel.

Solutions:

1) Run getaddrinfo() once per channel. This may for example cause
multiple DNS requests for the same information, which is suboptimal

2) Run getaddrinfo() only for the main (or first) channel. When
connection succeeds, reuse the addrinfo structure for the other channels. It
is usually safe to assume that when one channel succeeds, the machine is
available for the other channels, too.

An example of such an application is Spice. Thanks to David Jaša for information
on this subject.

Binding to addresses using getaddrinfo()

According to the manual page, you should use AI_PASSIVE flag when
you use getaddrinfo() to retrieve addresses to
bind() to. Those are usually stored as user configuration
with NULL being the default value.

getaddrinfo() returns a linked list of addrinfo structures.
Developers should not generally assume that it only returns one address
nor that the first address is the best and only one to bind() to.

The general idea is that one would loop through getaddrinfo()
structure and bind() one socket to each of them. But this doesn't work
in general. Read on.

Binding to the INADDR_ANY and/or in6addr_any addresses

This is the most common option that doesn't limit the service to
a particular set of local addresses.

getaddrinfo(NULL, ...) with AI_PASSIVE returns two addresses,
0.0.0.0 and ::, in this order. If you use
the general rule above, the resulting actions would look like:

This won't work. The first bind() will successfully bind
to 0.0.0.0, while the second tries to bind() in a dualstack
manner, taking both :: and 0.0.0.0 (unless
sysctl net.ipv6.bindv6only is enabled) and therefore it will fail
as 0.0.0.0 is already taken.

The addresses are obviously returned in different order than usual
(IPv4 first) and that should probably be fixed in glibc. But even
if it's fixed, it only changes the order of the actions:

After writing this, I found a resource from Bert JW Regeer on the same subject.

Binding to specific addresses by number

This works well because in this case only one address is ever returned
(if socktype and protocol are specified). It is possible
to limit getaddrinfo() to this case using the
AI_NUMERICHOST flag. AI_PASSIVE flag is not used in this case.

Binding to a list of specific addresses by hostname

This is just getaddrinfo() resolving. The AI_PASSIVE
flag is not used in this case. Always remember that getaddrinfo()
returns a linked list of addresses. If you support binding by name,
you should always create one socket for each address and bind()
it.

Proposed solutions

1) Only support listening on all addresses. Always bind()
to the IPv6 address in dualstack mode.

2) Use only numeric addresses and NULL setting. Special case NULL
setting and solve them separately with #1. Only then, if you use
AI_NUMERICHOST and properly
specify the socktype and/or protocol fields, you can assume
that getaddrinfo() only returns one result.

If result order is fixed in glibc, you wouldn't actually have to special
case the NULL host. But it's more of a hack.

3) Support full name resolution. Always create one socket per
resulting address. Set IPV6_V6ONLY on IPv6 sockets to suppres the
transparent dualstack. This works for all cases.

There are many other possibilities that seem to work at first glance
but don't behave as expected in many situations.

You can clearly see the reversed order for NULL host and multiple results
for hostnames. You can further improve the code with creation and
binding of the sockets.

Using getaddrinfo() for accesslists

Some software uses addresses where you can put addresses, hostnames or more
sophisticated filters based on them. Names can be resolved at configuration
time or at check time.

Various hosts are connecting to the hostname using IPv4 and IPv6 addresses. Whenever
the administrator uses a hostname in the accesslist, the hostname must point to
all addresses that can be used for the connection.

On the other hand, when the administrator puts addresses to the accesslist, he must
put all possible addresses there, otherwise an unpleasant surprise awaits him
when the host connects using an address that is not in the ACL.

localhost is a special case that, on most current distributions resolves to ::1
and 127.0.0.1, in this order. This is usually only driven by the /etc/hosts configuration file, except the list is reordered by getaddrinfo().

For example, when a service has 127.0.0.1 in the accesslist, it is
illegal to try to connect to it over ::1. But that's the first address
getaddrinfo("localhost", ...) returns. This is a configuration problem
but even many administrators skilled in IP networking won't realize that
localhost is not identical to 127.0.0.1.

Proposed solutions

1) Make administrators fix their configurations. Tell them about the breakage
of identity between "localhost" and "127.0.0.1".

2) Workaround: Change client behavior on access denied error from server and treat
it as a network error. Always try other addresses from the list returned by
getaddrinfo().

3) Workaround: Prefer 127.0.0.1 over ::1. This assumes that
anyone putting ::1 in an access list knows what he's doing.

Flag AI_ADDRCONFIG considered harmful

Current implementation of AI_ADDRCONFIG flag is a source problems and confusion. Detailed description of the problem was moved to a separate article: Flag AI_ADDRCONFIG considered harmful.

Comments and discussion

Please send any remarks and questions to psimerda-at-redhat-dot-com or use Talk:Networking. Edit with care.