GetHostEntry is very slow

前端 未结 5 985
情书的邮戳
情书的邮戳 2020-12-05 08:25

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.GetHostEn

相关标签:
5条回答
  • 2020-12-05 08:49

    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;
    }
    
    0 讨论(0)
  • 2020-12-05 08:55

    Mainly adding a comment in case someone finds this via google, as I did...

    The behavior may be OS version specific; these notes apply for Server 2008 R2.

    The NI_NUMERICHOST flag doesn't do what you want; this cases the API to return the numeric version of the host identifier (ie: IP address), rather than the host name.

    Even with NI_NAMEREQD, there is still a timeout if the information is not found (5 seconds by default). I'm not sure if this is due to a cascading lookup timeout, or something else, but this flag does not prevent the timeout (nor does any other flag, as far as I can tell).

    It appears this calls the WSALookupService API's internally, although it's unclear what flags are being passed. Also, note that the information returned can be incorrect; in one of my test cases, nslookup returned no result, but getnameinfo returned as inaccurate and unqualified name. So... yeah, no good answer yet, but hopefully this information is helpful.

    0 讨论(0)
  • 2020-12-05 09:05

    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.)

    0 讨论(0)
  • 2020-12-05 09:07

    Maybe this can help? The WayBack Machine version of the dead link.

    (Dead link: http://www.chapleau.info/blog/2008/09/09/reverse-dns-lookup-with-timeout-in-c.html)

    The code, for posterity:

    private delegate IPHostEntry GetHostEntryHandler(string ip);
    
    public string GetReverseDNS(string ip, int timeout)
    {
        try
        {
            GetHostEntryHandler callback = new GetHostEntryHandler(Dns.GetHostEntry);
            IAsyncResult result = callback.BeginInvoke(ip,null,null);
            if (result.AsyncWaitHandle.WaitOne(timeout, false))
            {
                return callback.EndInvoke(result).HostName;
            }
            else
            {
                return ip;
            }
        }
        catch (Exception)
        {
            return ip;
        }
    }
    
    0 讨论(0)
  • 2020-12-05 09:13

    In case anyone hits this...

    I switched from using the TcpClient constructor to calling the obsolete Dns.GetHostByName instead.

    For whatever reason it performs much better.

    public TcpClientIP(string hostname, int port) : base()
    {
        try
        {
            if (_legacyDnsEnabled)
            {
                var host = Dns.GetHostByName(hostname);
                var ips = host.AddressList.Select(o => new IPAddress(o.GetAddressBytes())).ToArray();
                Connect(ips, port);
                return;
            }
        }
        catch(SocketException e)
        { }
    
        Connect(hostname, port);
    }
    
    0 讨论(0)
提交回复
热议问题