How does gcc/cygwin get the DNS server?

7.4k views Asked by At

I have some code I'm writing under cygwin (using GCC) that successfully uses gethostbyname(); however when I try to use the resolver directly to retrieve the IP address of the DNS server it fails (all entries in nsaddr_list[] are null and nscount is -1). If gethostbyname() is working, then obviously it is able to connect to the DNS server.
This code...

    if (res_init() == -1) {
        fprintf(stderr,"res_init() failed\n");
        exit(1);
    }

    if (_res.nscount <= 0) {
        fprintf(stderr,"nscount = %d\n",_res.nscount);
    }
    else {
        for(i=0;i<_res.nscount;i++) {
            fprintf(stderr, "dnssrvr: %d.%d.%d.%d\n",
                (_res.nsaddr_list[i].sin_addr.s_addr & 0xff) >> 0,
                (_res.nsaddr_list[i].sin_addr.s_addr & 0xff00) >> 8,
                (_res.nsaddr_list[i].sin_addr.s_addr & 0xff0000) >> 16,
                (_res.nsaddr_list[i].sin_addr.s_addr & 0xff000000) >> 24);
        }
    }

works on unix/linux, but returns nscount=-1 on cygwin. Is there some trick to getting the DNS server when using cygwin/gcc?

2

There are 2 answers

1
caf On BEST ANSWER

As n.m. says, on Cygwin res_init() does not populate _res.nsaddr_list if it is using the Windows resolver. It uses the Windows resolver if either /etc/resolv.conf does not exist, or /etc/resolv.conf contains options osquery.

In my opinion this is a Cygwin bug - returning a negative nscount is bogus - but nonetheless we are stuck with working around it.

The solution is to call GetNetworkParams() just as Cygwin does itself - here's what I'm doing as a fallback:

#include <windows.h>
#include <iphlpapi.h>
#include <netinet/in.h>
#include <arpa/inet.h>

if (_res.nscount < 0)
{
    ULONG buflen = 0;
    FIXED_INFO *buf = NULL;

    if (GetNetworkParams(NULL, &buflen) == ERROR_BUFFER_OVERFLOW)
        buf = malloc(buflen);

    if (buf && GetNetworkParams(buf, &buflen) == NO_ERROR)
    {
        _res.nscount = 1;
        _res.nsaddr_list[0].sin_family = AF_INET;
        _res.nsaddr_list[0].sin_addr.s_addr = inet_addr(buf->DnsServerList.IpAddress.String);
        _res.nsaddr_list[0].sin_port = htons(53);
    }

    free(buf);
}

You need to link against -liphlpapi for the GetNetworkParams() function.

This only takes the first Windows DNS address, but if you want the rest of them you can follow the linked list that GetNetworkParams() returns. GetNetworkParams() only returns IPv4 addresses, I'm not sure what you're supposed to do if the machine has an IPv6 DNS server address configured.

6
n. m. could be an AI On

res_init does not necessarily populate _res.nsaddr_list. Instead, on Windows it directs the resolver to use DnsQuery_A, unless you have the resolv.conf file, in which case DNS servers from that file are used.

See the source here, files minires.c and minires-os-if.c.

If you need to know your DNS servers, you probably have to use DnsQueryConfig or GetNetworkParams.

NB: _res is undocumented and should not be used.

UPDATE Apparently the "newer" (ca 2010 and later) versions of cygwin do populate _res.nsaddr_list, via a call to get_dns_info and then get_registry_dns. You may want to make sure that you have the newest cygwin, and if the problem persists, try to use a debug version and trace calls to the mentioned functions.

UPDATE 2 No, _res is not populated, my mistake.