Search code examples
cnetwork-programmingcygwindnslibresolv

How does gcc/cygwin get the DNS server?


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?


Solution

  • 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.