There’s a really good DNS client library on Code Project. It does have a few issues (like a lack of TXT record support, but that was a piece of cake to add, really), but the big one (in my book) is that it doesn’t automatically detect the configured DNS server (which means you have to manually pass it in).
Now, that’s no good for my needs, so I wrote this small wrapper around a P/Invoke call to GetNetworkParams, which is an API available in Windows 2000 or newer (also Windows 98 or newer).
Now, the call itself is quite simple. You just need to set up the structures properly. It’s fairly simple as they’re all basically flat data structures (almost, but I’ll get onto that in a second). Here’s a definition which works for me:
const int MAX_HOSTNAME_LEN = 128;
const int MAX_DOMAIN_NAME_LEN = 128;
const int MAX_SCOPE_ID_LEN = 256;
[StructLayout(LayoutKind.Sequential, Pack=1)]
struct IP_ADDR_STRING
{
public IntPtr Next;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=16)]
public string IpAddress;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=16)]
public string IpMask;
public uint Context;
}
[StructLayout(LayoutKind.Sequential, Pack=1)]
struct FIXED_INFO
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_HOSTNAME_LEN + 4)]
public string HostName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_DOMAIN_NAME_LEN + 4)]
public string DomainName;
public IntPtr CurrentDnsServer;
public IP_ADDR_STRING DnsServerList;
public uint NodeType;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_SCOPE_ID_LEN + 4)]
public string ScopeId;
public uint EnableRouting;
public uint EnableProxy;
public uint EnableDns;
}
Then the P/Invoke method definition:
[DllImport("iphlpapi.dll", CharSet=CharSet.Ansi)]
static extern int GetNetworkParams(IntPtr pFixedInfo, ref uintpBufOutLen);
Note, we have to keep the pFixedInfo
parameter left as an IntPtr
because we’ll need to dynamically allocate the memory for it in our code. Below is the code for returning all the DNS servers configured for the local machine:
IPAddress[] GetNameServers()
{
uint bufLength = 0;
// Call it once to get the size of the buffer
GetNetworkParams(IntPtr.Zero, ref bufLength);
// Allocate the buffer in unmanaged memory.
IntPtr ptr = Marshal.AllocHGlobal((int) bufLength);
try
{
// Call it again with the newly-allocated buffer
// to get the actual data.
int ret = GetNetworkParams(ptr, ref bufLength);
if (ret != 0)
{
throw new ApplicationException("Could not query network params: " + ret);
}
// Now unmarshal the FIXED_INFO structure
FIXED_INFO fi = (FIXED_INFO) Marshal.PtrToStructure(ptr,
typeof(FIXED_INFO));
// We'll add the IPAddresses to an ArrayList because we don't
// know the final number of addresses yet.
ArrayList addrs = new ArrayList();
IP_ADDR_STRING addr = fi.DnsServerList;
for ( ; ; )
{
addrs.Add(IPAddress.Parse(addr.IpAddress));
if (addr.Next == IntPtr.Zero)
break;
// We'll have to unmarshal the next IP_ADDR_STRING structure as well
addr = (IP_ADDR_STRING) Marshal.PtrToStructure(addr.Next,
typeof(IP_ADDR_STRING));
}
return (IPAddress[]) addrs.ToArray(typeof(IPAddress));
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}
Now, a few things to note about this code. Note the list of DNS servers are returned as a linked list, so we just need to use Marshel.PtrToStructure
to unmarshal each instance of the structure.
Also, on a singly-homed machine (that is, one with only one network interface) this will return the DNS servers configured for the machine (or the DNS servers returned when the machine downloaded it’s DCHP parameters). I haven’t done a whole lot of testing (yet) on a multi-homed machine (that is, one with more than one network interface), but it seems to return all the DNS servers configured for all network adapters (and while each adapter’s DNS servers are returned in order, each adapter is apparently not returned in any particular order).