我有一个WinForms应用程序,我正在尝试获取表单上显示的IP列表的反向DNS条目。
我遇到的主要问题是System.Net.Dns.GetHostEntry非常慢,特别是在没有找到反向DNS条目的情况下。使用直接DNS,这应该很快,因为DNS服务器将返回NXDOMAIN。在内部,它调用ws2_32.dll getnameinfo(),其中指出“名称解析可以通过域名系统(DNS),本地主机文件或其他命名机制” - 所以我假设它是那些“其他命名机制”这导致它如此缓慢,但有谁知道这些机制是什么?
一般情况下,每个IP需要5秒钟,除非它找到一个反向条目,然后它几乎是立即的。我使用线程部分解决了这个问题,但由于我正在做一个大型列表而且我只能同时运行这么多线程,所以仍然需要一段时间才能完成它们。
有没有更好的方法来查找速度更快的反向DNS条目?
答案 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作为不准确且不合格的名称返回。所以...是的,还没有好的答案,但希望这些信息有用。