The Dark Hole of GetAddrInfo() – Part 2

In Part 1 I brought up all the weirdness of GetAddrInfo(). Things like the fact that it’s way more complex than I anticipated and that there are bunch of input parameters that I couldn’t decipher. I also mentioned that behavior of itself is a bit weird, to say at least. And that’s what I’m going to talk about now.

Let me set the stage by pasting just the relevant part of code:

dwRetval = GetAddrInfoW(L"", nullptr, nullptr, &result);

if (dwRetval != 0) {
    printf("getaddrinfo failed with error: %d\n", dwRetval);
    return 1;

printf("getaddrinfo returned success\n");

// Iterate through the linked list of addresses
for (auto ptr = result; ptr != nullptr; ptr = ptr->ai_next) {
    if (ptr->ai_family == AF_INET) {
        // IPv4 address
        sockaddr_in* ipv4 = reinterpret_cast<sockaddr_in*>(ptr->ai_addr);
        char ipstr[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &(ipv4->sin_addr), ipstr, INET_ADDRSTRLEN);
        printf("IPv4 Address: %s\n", ipstr);
    else if (ptr->ai_family == AF_INET6) {
        // IPv6 address
        sockaddr_in6* ipv6 = reinterpret_cast<sockaddr_in6*>(ptr->ai_addr);
        char ipstr[INET6_ADDRSTRLEN];
        inet_ntop(AF_INET6, &(ipv6->sin6_addr), ipstr, INET6_ADDRSTRLEN);
        printf("IPv6 Address: %s\n", ipstr);

And here’s the output:

getaddrinfo returned success
IPv4 Address:
IPv4 Address:

Nothing spectacular, really. Now let me ask you – what would you presume happened in the background? Give it a moment or two and think it through.

Here’s what I’d expect:

  1. Process would first check the hosts file to see if hostname is there. If yes – it’d return that.
  2. Next, if it wasn’t in hosts file, it’d probably issue one or two DNS queries to query against A and AAAA records.
  3. That’d be about it, I guess.

Now let’s see what really happens:

Exactly ZERO network requests were made. Interesting. And trust me – I cleaned the cache upfront so it’s not that. Next, I set the filter to “hosts” and explored Opened files:

Well, that’s a bummer. A weird one, indeed. So my process made ZERO network requests and made no attempt at reading the hosts file. That’s weird, to say at least. Amusingly though, there WERE UDP requests sent to DNS server:

Interesting. So all the DNS queries seem to have been sent by svchost.exe with PID 3228. Who and what is that? Task Manager provides the answer:

GetAddrInfo() relies on DNS Client service to do all the querying

This was a massive aha! moment for me. I always presumed that the process itself makes the actualy DNS queries, but it turns out it just delegates them to DNS Client service, which, as we will see later, will actually query the hosts file and will issue network requests.

My next logical question was – how the hell does my process talk to DNS Client service? And through a series of highly sophisticated reverse-engineering steps (i.e. I set billion breakpoints in WinDbg and observed in Wireshark when the actual DNS query is made) I observed that it communicates through RPC. Here’s a call stack in case you want to repro for yourself:

GetAddrInfo!main <--- this is the main method of my app

What’s interesting to observe here is that there seems to be a way to execute DNS query directly – by invoking DnsQueryEx directly.

To go back to my point, I guess my biggest learning here was that every time you want to resolve a hostname, your process will issue an RPC call towards DNS Client Service, which, in turn, will do the resolution for you. This is important if you want to log the Network calls, because you simply won’t see them being executed from your process, but rather from svchost.exe that is executing DNS Client service.

DNS Client service and etc\hosts file

I’m quite sure most everyone knows about C:\Windows\System32\drivers\etc\hosts. It pretty much allows you to hardcode hostnames to specified IPs. Here’s content my from my system:

# Copyright (c) 1993-2009 Microsoft Corp.
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
# For example:
#          # source server
#              # x client host

# localhost name resolution is handled within DNS itself.
#       localhost
#	::1             localhost
# Added by Docker Desktop host.docker.internal gateway.docker.internal
# To allow the same kube context to work on the host and the container: kubernetes.docker.internal
# End of section

Now if I were to append "" at the end of this file, what would happen is:

Pretty much as expected – it loaded the IP addess from hosts file and never made a DNS query. Now, the part that I found rather interesting is that neither my process nor DNS Client actually ever read this file! I could show you a screenshot of empty ProcMon showing that no process interacted with hosts file during the execution of my app. Which bears another question — how the hell does DNS Client know that contents of hosts file has changed?! Turns out that this is yet another aha! moment.

By playing around with ProcMon, what I observed is that DNS Client actually monitors (due to lack of better word) when hosts file changes, and when it does, it loads and caches its content. Amusingly though, DNS Client doesn’t seem to be the only one doing this. Here are all processes that interacted with hosts file after I made some changes to it:

Amusingly enough, as can be observed, there are multiple processes interested in changes to “hosts” file – svchost.exe (i.e. DNS Client Service), MS Edge and Chrome. This makes me think that Chrome and Edge are likely doing their own DNS resolution and completely avoid the OS built one, but that’s a story for another time. The important thing is that hosts file is parsed only when it’s changed and not with every process execution. This seems to be different than how it read it works on Linux, so I guess that’s worth knowing 🙂

That’d be about it for today. Some of the things I plan to explore in future articles are “could I somehow interact with DNS Client Service directly?”, “do WinINet and WinHttp also rely on DNS Client?” and “is there a way to force specific process to use different DNS Client Service?”. But that’s a story for another time 😉

Hope you liked this one!

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top