System.Net.Dns Flush Cache Issues

I was attempting to use the System.Net.Dns class query a host and check for an expected value. I quickly realized that DNS caching was causing issues. This lead me down an interesting path. The original code I was writing looked something like this:

public bool IsDnsConfigured(string host, string desiredHost)  
{
   var aliasRecord = Dns.GetHostEntry(host);
   return aliasRecord.HostName == desiredHost;  
}

After executing this code, the result would be cached for 10-15 minutes. Subsequent calls would load the original record, even after the records TTL had expired. Clearly this was a caching issue. So then the question of how to programmatically force a DNS query became the issue.

I began my journey by looking at additional DNS querying techniques for C#. Surprisingly, not much is in the framework. Figuring that perhaps there is some setting I can configuring for System.Net that would make fresh connections I figured up the trusty .NET Reflector and took a peak.

Internally, you can see that the Dns class doesn't do much special besides PInvoke the winsock API to resolve the address.

internal static IPHostEntry InternalGetHostByName(string hostName, bool includeIPv6)  
{
    if (Logging.On)
    {
        Logging.Enter(Logging.Sockets, "DNS", "GetHostByName", hostName);
    }
    IPHostEntry retObject = null;
    if ((hostName.Length > 0xff) || ((hostName.Length == 0xff) && (hostName[0xfe] != '.')))
    {
        object[] args = new object[] { "hostName", 0xff.ToString(NumberFormatInfo.CurrentInfo) };
        throw new ArgumentOutOfRangeException("hostName", SR.GetString("net_toolong", args));
    }
    if (Socket.LegacySupportsIPv6 || (includeIPv6 && ComNetOS.IsPostWin2K))
    {
        retObject = GetAddrInfo(hostName);
    }
    else
    {
        IntPtr nativePointer = UnsafeNclNativeMethods.OSSOCK.gethostbyname(hostName);
        if (nativePointer == IntPtr.Zero)
        {
            IPAddress address;
            SocketException exception = new SocketException();
            if (!IPAddress.TryParse(hostName, out address))
            {
                throw exception;
            }
            retObject = GetUnresolveAnswer(address);
            if (Logging.On)
            {
                Logging.Exit(Logging.Sockets, "DNS", "GetHostByName", retObject);
            }
            return retObject;
        }
        retObject = NativeToHostEntry(nativePointer);
    }
    if (Logging.On)
    {
        Logging.Exit(Logging.Sockets, "DNS", "GetHostByName", retObject);
    }
    return retObject;
}

[DllImport("ws2_32.dll", CharSet=CharSet.Ansi, SetLastError=true)]
internal static extern IntPtr gethostbyname([In] string host);  

The reference for the winsock API didn't reveal any ways to achieve my goal, so I started looking elsewhere. Instead of making direct calls, I now started searching for ways to invalidate the DNS cache. Doing some digging allowed me to find a few methods for flushing the DNS from PInvoke calls to dnsapi.dll.

Unfortunately, no API documentation exists beyond the methods:

dnsapi.dll
DnsFlushResolverCache
DnsFlushResolverCacheEntry_A
DnsFlushResolverCacheEntry_UTF8
DnsFlushResolverCacheEntry_W

However, some brave souls were able to provide info on StackExchange after disassembling the DLL. I was ultimately able to get my code to work with the following helper utilities:

public class DnsUtils  
{        
    [DllImport("dnsapi.dll", EntryPoint="DnsFlushResolverCache")]
    static extern UInt32 DnsFlushResolverCache();

    [DllImport("dnsapi.dll", EntryPoint = "DnsFlushResolverCacheEntry_A")]
    public static extern int DnsFlushResolverCacheEntry(string hostName);

    public static void FlushCache()
    {
        DnsFlushResolverCache();
    }

    public static void FlushCache(string hostName)
    {
        DnsFlushResolverCacheEntry(hostName);
    }
}

The first method will flush the entire cache. This is ok, but not ideal. The seond method will invalidate cache for a single record. The end result is this code:

public bool IsDnsConfigured(string host, string desiredHost)  
{
   DnsUtil.FlushCache(host);
   var aliasRecord = Dns.GetHostEntry(host);
   return aliasRecord.HostName == desiredHost;  
}

If you happen to know other techniques or have more information on the dnsapi.dll please let me know!

comments powered by Disqus