Linux – How to disable AAAA lookups

domain-name-systemglibcipv6linux

… to compensate for broken DNS servers that are outside our control.

Our problem: We deploy embedded devices that collect sensor data at various, mostly IPv4-only sites. Some sites have poorly maintained networks, e.g. misconfigured or otherwise broken DNS caches and/or firewalls that either ignore AAAA queries altogether, or respond to them with broken replies (e.g. wrong source IP!). As an external supplier to the facilities department, we have next to no influence on the (sometimes reluctant) IT departments. The chances of them fixing their DNS servers/firewalls any time soon are minuscule.

The effect on our device is that with each gethostbyname(), the processes have to wait until the AAAA queries time out, at which point some processes have already timed out their connection attempts altogether.

I am looking for solutions that are …

  • system-wide. I cannot reconfigure dozens of application individually
  • non-permanent and configurable. We need to (re-)enable IPv6 where/when it gets fixed/rolled out. Reboot is OK.
  • If a solution requires a core library like glibc to be replaced, the replacement library package should be available from a known-to-be-well-maintained repository (e.g. Debian Testing, Ubuntu universe, EPEL). Self-building is not an option for so many reasons that I don't even know where to begin with, so I just don't list them at all…

The most obvious solution would be to configure the resolver library e.g. via /etc/{resolv,nsswitch,gai}.conf to not query AAAA records. A resolv.conf option no-inet6 as suggested here would be exactly what i am looking for. Unfortunately it is not implemented, at least not on our systems (libc6-2.13-38+deb7u4 on Debian 7; libc6-2.19-0ubuntu6.3 on Ubuntu 14.04)

So how then? One finds the following methods suggested on SF and elsewhere, but non of them work:

  • Disabling IPv6 altogether, e.g. by blacklisting the ipv6 LKM in /etc/modprobe.d/, or sysctl -w net.ipv6.conf.all.disable_ipv6=1. (Out of curiosity: Why is the resolver asking for AAAA where IPv6 is disabled?)
  • Removing options inet6 from /etc/resolv.conf. It wasn't there in the first place, inet6 is simply enabled by default these days.
  • Setting options single-request in /etc/resolv.conf. This only ensures that the A and the AAAA queries are done sequentially rather than in parallel
  • Changing precedence in /etc/gai.conf. That does not affect the DNS queries, only how multiple replies are processed.
  • Using external resolvers (or running a local resolver daemon that circumvents the broken DNS servers) would help, but is usually disallowed by the company's firewall policies. And it can make internal resources inaccessible.

Alternative ugly ideas:

  • Run a DNS cache on localhost. Configure it to forward all non-AAAA queries, but to respond to AAAA queries with either NOERROR or NXDOMAIN (depending on the result of the corresponding A-query). I am not aware of a DNS cache able to do this though.
  • Use some clever iptables u32 match, or Ondrej Caletka's iptables DNS module to match AAAA queries, in order to either icmp-reject them (how would the resolver lib react to that?), or to redirect them to a local DNS server that responds to everything with an empty NOERROR.

Note that there are similar, related questions on SE. My question differs insofar as it elaborates the actual problem i am trying to solve, as it lists explicit requirements, as it blacklists some often-suggested non-working solutions, and as it is not specific to a single application. Following this discussion, I posted my question.

Best Answer

Stop using gethostbyname(). You should be using getaddrinfo() instead, and should have been for years now. The man page even warns you of this.

The gethostbyname*(), gethostbyaddr*(), herror(), and hstrerror() functions are obsolete. Applications should use getaddrinfo(3), getnameinfo(3), and gai_strerror(3) instead.

Here is a quick sample program in C which demonstrates looking up only A records for a name, and a Wireshark capture showing that only A record lookups went over the network.

In particular, you need to set ai_family to AF_INET if you only want A record lookups done. This sample program only prints the returned IP addresses. See the getaddrinfo() man page for a more complete example of how to make outgoing connections.

In the Wireshark capture, 172.25.50.3 is the local DNS resolver; the capture was taken there, so you also see its outgoing queries and responses. Note that only an A record was requested. No AAAA lookup was ever done.

#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netdb.h>
#include <stdio.h>

int main(void) {
    struct addrinfo hints;
    struct addrinfo *result, *rp;
    int s;
    char host[256];

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = 0;

    s = getaddrinfo("www.facebook.com", NULL, &hints, &result);
    if (s != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
        exit(EXIT_FAILURE);
    }

    for (rp = result; rp != NULL; rp = rp->ai_next) {
        getnameinfo(rp->ai_addr, rp->ai_addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST);
        printf("%s\n", host);
    }
    freeaddrinfo(result);
}
Related Topic