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!