Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GetHostEntry is very slow

Tags:

.net

dns

I have a WinForms app, and I'm trying to get reverse DNS entries for a list of IPs displayed on the form.

The main issue I've run into is System.Net.Dns.GetHostEntry is ridiculously slow, particularly when no reverse DNS entry is found. With straight DNS, this should be fast, since the DNS server will return NXDOMAIN. Internally, it's calling ws2_32.dll getnameinfo(), which states "Name resolution can be by the Domain Name System (DNS), a local hosts file, or by other naming mechanisms" - so I'm assuming it's those "other naming mechanisms" that's causing it to be so slow, but does anyone know what those mechanisms are?

Generally this is taking 5 seconds per IP, unless it finds a reverse entry, and then it's almost immediate. I've partly worked around this using threads, but since I am doing a large list and I can only run so many threads at once, it still takes a while to get through them all.

Is there a better way to find reverse DNS entries that is going to be faster?

like image 443
gregmac Avatar asked Jun 15 '09 16:06

gregmac


2 Answers

Unfortunately, there is no way (of which I am aware) to change this timeout in the Windows API, on the client side. The best you can do is edit the registry to alter the length of the timeouts in DNS queries. See this technet article for details. To my knowledge, attempts 1, 2, & 3 are run when you do this, hence the 5 second delay.

The only other option is to use some form of background processing, such as this asynchronous version of reverse DNS lookups. This is going to use threading, though, so you'll eventually run into the timeouts (it'll be better, since it'll be across many waiting threads, but still not perfect). Personally, if you're going to process a huge number, I'd mix both approaches - do a reverse lookup ansyncrhonously AND modify the registry to make the timeout shorter.


Edit after comments:

If you look at the flags on getnameinfo, there is a flags parameter. I believe you can P/Invoke into this and set the flags NI_NAMEREQD | NI_NUMERICHOST to get the behavior you are after. (The first says to error out immediately if there is no DNS entry, which helps the timeout - the second says to do the reverse lookup.)

like image 142
Reed Copsey Avatar answered Oct 26 '22 17:10

Reed Copsey


You can improve the speed of a failed lookup considerably by querying the in-addr.arpa domain. E.g to perform a reverse IP lookup for IP address A.B.C.D you should query DNS for the domain D.C.B.A.in-addr.arpa. If reverse lookup is possible a PTR record with the host name is returned.

Unfortunately .NET does not have a general API for querying DNS. But by using P/Invoke you can call the DNS API to get the desired result (the function will return null if the reverse lookup fails).

using System;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;

public static String ReverseIPLookup(IPAddress ipAddress) {
  if (ipAddress.AddressFamily != AddressFamily.InterNetwork)
    throw new ArgumentException("IP address is not IPv4.", "ipAddress");
  var domain = String.Join(
    ".", ipAddress.GetAddressBytes().Reverse().Select(b => b.ToString())
  ) + ".in-addr.arpa";
  return DnsGetPtrRecord(domain);
}

static String DnsGetPtrRecord(String domain) {
  const Int16 DNS_TYPE_PTR = 0x000C;
  const Int32 DNS_QUERY_STANDARD = 0x00000000;
  const Int32 DNS_ERROR_RCODE_NAME_ERROR = 9003;
  IntPtr queryResultSet = IntPtr.Zero;
  try {
    var dnsStatus = DnsQuery(
      domain,
      DNS_TYPE_PTR,
      DNS_QUERY_STANDARD,
      IntPtr.Zero,
      ref queryResultSet,
      IntPtr.Zero
    );
    if (dnsStatus == DNS_ERROR_RCODE_NAME_ERROR)
      return null;
    if (dnsStatus != 0)
      throw new Win32Exception(dnsStatus);
    DnsRecordPtr dnsRecordPtr;
    for (var pointer = queryResultSet; pointer != IntPtr.Zero; pointer = dnsRecordPtr.pNext) {
      dnsRecordPtr = (DnsRecordPtr) Marshal.PtrToStructure(pointer, typeof(DnsRecordPtr));
      if (dnsRecordPtr.wType == DNS_TYPE_PTR)
        return Marshal.PtrToStringUni(dnsRecordPtr.pNameHost);
    }
    return null;
  }
  finally {
    const Int32 DnsFreeRecordList = 1;
    if (queryResultSet != IntPtr.Zero)
      DnsRecordListFree(queryResultSet, DnsFreeRecordList);
  }
}

[DllImport("Dnsapi.dll", EntryPoint = "DnsQuery_W", ExactSpelling=true, CharSet = CharSet.Unicode, SetLastError = true)]
static extern Int32 DnsQuery(String lpstrName, Int16 wType, Int32 options, IntPtr pExtra, ref IntPtr ppQueryResultsSet, IntPtr pReserved);

[DllImport("Dnsapi.dll", SetLastError = true)]
static extern void DnsRecordListFree(IntPtr pRecordList, Int32 freeType);

[StructLayout(LayoutKind.Sequential)]
struct DnsRecordPtr {
  public IntPtr pNext;
  public String pName;
  public Int16 wType;
  public Int16 wDataLength;
  public Int32 flags;
  public Int32 dwTtl;
  public Int32 dwReserved;
  public IntPtr pNameHost;
}
like image 33
Martin Liversage Avatar answered Oct 26 '22 17:10

Martin Liversage