GetHostEntry非常慢

时间:2009-06-15 16:26:31

标签: .net dns

我有一个WinForms应用程序,我正在尝试获取表单上显示的IP列表的反向DNS条目。

我遇到的主要问题是System.Net.Dns.GetHostEntry非常慢,特别是在没有找到反向DNS条目的情况下。使用直接DNS,这应该很快,因为DNS服务器将返回NXDOMAIN。在内部,它调用ws2_32.dll getnameinfo(),其中指出“名称解析可以通过域名系统(DNS),本地主机文件或其他命名机制” - 所以我假设它是那些“其他命名机制”这导致它如此缓慢,但有谁知道这些机制是什么?

一般情况下,每个IP需要5秒钟,除非它找到一个反向条目,然后它几乎是立即的。我使用线程部分解决了这个问题,但由于我正在做一个大型列表而且我只能同时运行这么多线程,所以仍然需要一段时间才能完成它们。

有没有更好的方法来查找速度更快的反向DNS条目?

5 个答案:

答案 0 :(得分:10)

也许这有用吗? dead link.

的WayBack Machine版本

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

代码,为子孙后代:

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;
    }
}

答案 1 :(得分:5)

不幸的是,没有办法(我知道)在客户端的Windows API中更改此超时。您可以做的最好的事情是编辑注册表以更改DNS查询中超时的长度。有关详细信息,请参阅this technet article。据我所知,尝试1,2和&执行此操作时会运行3,因此延迟5秒。

唯一的另一种选择是使用某种形式的后台处理,例如此asynchronous version反向DNS查找。但是,这将使用线程,所以你最终会遇到超时(它会更好,因为它将跨越许多等待线程,但仍然不完美)。就个人而言,如果你要处理一个庞大的数字,我会混合两种方法 - 反复查找和修改注册表以缩短超时。


评论后编辑:

如果查看getnameinfo上的标志,则会有一个flags参数。我相信你可以P / Invoke到这个并设置标志NI_NAMEREQD | NI_NUMERICHOST来获得你所追求的行为。 (如果没有DNS条目,第一个说立即错误输出,这有助于超时 - 第二个说反向查找。)

答案 2 :(得分:4)

通过查询in-addr.arpa域,您可以大大提高查找失败的速度。例如,要对IP地址执行反向IP查找 A.B.C.D ,您应该查询域 D.C.B.A.in-addr.arpa 的DNS。如果可以进行反向查找,则返回具有主机名的PTR记录。

不幸的是,.NET没有用于查询DNS的通用API。但是通过使用P / Invoke,您可以调用DNS API来获得所需的结果(如果反向查找失败,函数将返回null。)

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;
}

答案 3 :(得分:1)

如果有人遇到这个......

我从使用TcpClient构造函数切换到调用过时的Dns.GetHostByName

无论出于何种原因,它表现得更好。

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);
}

答案 4 :(得分:0)

主要添加评论以防有人通过谷歌发现这一点,就像我做的那样......

行为可能是OS版本特定的;这些说明适用于Server 2008 R2。

NI_NUMERICHOST标志不能满足你的需要;这种情况下API返回主机标识符的数字版本(即:IP地址),而不是主机名。

即使使用NI_NAMEREQD,如果找不到信息仍然会超时(默认为5秒)。我不确定这是否是由于级联查找超时或其他原因造成的,但是这个标志不会阻止超时(就我所知,也没有任何其他标志)。

看来这会在内部调用WSALookupService API,虽然不清楚传递了什么标志。另请注意,返回的信息可能不正确;在我的一个测试用例中,nslookup没有返回任何结果,但是getnameinfo作为不准确且不合格的名称返回。所以...是的,还没有好的答案,但希望这些信息有用。