Resolving host names, that is, finding a connection-level address from their human-readable representation, is a standard task in client applications which need to connect to a server. This is quite a complex task: Information about such mappings might be spread, and new additions like IPv6 or internationalized domain names (IDN) are appearing once in a while, although admittedly not often.
These properties make it desirable to use a ready-made library to perform hostname lookups. But of course, there’s plenty of choice, so which possibilities exist out there? Let’s look at the most common ones: System library functions (libc, resolv, anl), adns, firedns and Qt.
resolv:
A fairly simple interface to DNS servers is provided by the resolver function res_query(), which has its root in the BIND sources and shipped with BSD 4.3 initially. The usage takes a name, constants for the class (always ns_c_in for Internet resources) and type (always ns_t_a for hostname lookups), as well as a string buffer provided by the application. For IPv6 however, ns_t_aaaa would have to be used additionally.
Before using the call, res_init() should be called, although this is not a requirement. The call to res_query() might block the application, which is a big drawback. Another issue for the real-world usage is that this library really only supports DNS queries, therefore not taking the contents of /etc/hosts into account, which severely limits its usability for network-less development machines, for instance.
Note that the glibc version is modified a bit so that no global state is kept within the library, thus enabling resolv to be used with threads. Note also that the returned IP address is really a string, which might or might not have to be converted to a network address structure using inet_pton(), which again has the drawback of not being IPv6-transparent, albeit it can support this address family.
libc:
Probably the most well-known “classic” function is gethostbyname(). It appeared in BSD 4.3 and has also been a POSIX standard function. The call is synchronous, and supports looking up IPv6 addresses. It queries the /etc/hosts file, DNS and other name servers like NIS, depending on the global configuration in /etc/host.conf. This function is reasonably easy to use (except for a few OS incompatibilities which might occur), but is not acceptable for use with interactive programs since it will block the execution thread. To overcome this limitation, a second thread could be created manually, but gethostbyname() is not threadsafe since it uses a library-internal static buffer, so gethostbyname_r had to be introduced to let the application programmer specify a buffer.
Note that IPv6 support was not present initially, for which the substitute function getipnodebyname() has been introduced, according to RFC 2553. This function was obsoleted soon, both by adding address family support to a variant called gethostbyname2(), which is glibc-only, and other functions.
Finally, gethostbyname() has been superseeded by getaddrinfo(), or GAI for short. This function does a little more than resolving hostnames, for instance it can resolve service names as well. Otherwise, it is similar to the gethostbyname2_r() call with IPv6 support and application-provided threadsafe buffers, but also including the undesirable blocking behaviour. This function has also been named in RFC 2553.
anl:
This is a not-yet-standardised extension to GAI, and provides a function called getaddrinfo_a() and several other support functions. As the name suggests, it supports asynchronous hostname lookups, and is present so far in recent versions of the glibc library, where it relies on the AIO layer. The features are similar to getaddrinfo(). Of note is that the notification might happen in two flavours, either by thread creation or by being signalled. Both are not really optimal for all kinds of applications, although it’s not terribly hard to handle the notifications. These are the drawback costs one pays for letting the system library do the polling for the results.
adns:
One of the oldest resolver libraries which natively support asynchronous operation, in addition to synchronous mode. The adns library is initialized with a global state variable, while for each query or status check a structure is initialized by allocating memory. The main functions to use are adns_init(), adns_submit(), adns_processany() and adns_check(). A few things are noteworthy. First, it embeds well with applications since the internally used file descriptor is made available, which means notification can happen within the regular execution thread, for instance using select(). Second, a lot of information is provided, in many cases in form of a struct or union so that the relevant parts can be accessed easily, like the resolved hostname as a string and as a network address structure, depending on the further processing needs.
One drawback is shared with the resolv library, however: The file /etc/hosts is not used, an accessible DNS server is required for the library to work. Another one is that IPv6 is not supported yet.
firedns:
Looking at the big picture, firedns is similar to the adns library, although there are a few differences. For example, IPv6 is supported, although no transparent support is given - similar to the resolv library, both IPv4 and IPv6 queries have to be done separately. Integration happens similarly using a file descriptor which must be polled. A drawback shared with adns is that only real DNS queries are performed.
Qt:
A special contender in this comparison is the Qt library, which is a library for creating C++ applications, providing both GUI and non-GUI classes and a fairly complete platform-independent programming platform. In Qt3, the QDns class was available, which took the hostname and optionally the wanted resource type, and then processed the query asynchronously until a result was available. Then, a Qt signal was sent, which basically means calling back a C++ method (or Slot in qt speak), in which a QValueList contained entries of type QHostAddress, which in turn could be passed further along to Qt networking functions or reveal the IP address in string format. IPv6 addresses and the /etc/hosts file are supported by Qt. In Qt4, QDns was renamed to QHostInfo, which provides a simplyfied interface, abstracting away from DNS terminology such as resource types. Also, support for IDN (via Punycode) was introduced.
Conclusion:
It is difficult to really find the “best of breed” when all methods of resolving have their drawbacks. For Qt-based applications, using the Qt resolver is the most compelling and most sensible way of getting the work done. For all others, especially graphical clients to network services, it gets really difficult.
Let’s do a simple score table:
libc/resolv: hosts -1, ipv6 +/-0, idn -1, nonblocking -1, score = -3
libc/gethostbyname: hosts +1, ipv6 +1, idn -1, nonblocking -1, score = 0
libc/getaddrinfo: hosts +1, ipv6 +1, idn +1, nonblocking -1, score = 2
libc/anl: hosts +1, ipv6 +1, idn +1, nonblocking +1, score = 4
adns: hosts -1, ipv6 -1, idn -1, nonblocking +1, score = -2
firedns: hosts -1, ipv6 +/-0, idn -1, nonblocking +1, score = -1
Qt: hosts +1, ipv6 +1, idn +1, nonblocking +1, score = 4
There is a clear distinction between the favorites and the others, but depending on the usage, this might not tell much - for instance, C programmers will not be able to use Qt!
For this reason, the GGZ library for networking and other programming aids, libggz, has taken a twofold approach. It provides a function named ggz_resolvename() and a function to set a result callback. If libggz has been configured with support for the anl library (providing gethostyname_a()), this callback is called whenever the results are available, or otherwise directly after a blocking call, transparently to the programmer.
Since libggz has a convenience method for creating sockets, called ggz_make_socket(), the developer is even relieved from resolving the socket first by setting up a callback for ggz_make_socket(), which if activated already holds the file descriptor, ready to be used on either IPv4 or IPv6 connections. The only remaining drawback would be missing IDN support, although even this is covered for all getaddrinfo() calls with some IDN flags when using recent glibc versions (>= 2.3.4) which are compiled with IDN support.
Otherwise this is really the recommended way to go for application developers: No blocking calls (when configured correctly), inclusion of /etc/hosts, IPv6 support and convenient API.