2009-06-15 78 views
7

私はWinFormsアプリケーションを持っており、フォームに表示されているIPのリストに対して逆DNSエントリを取得しようとしています。GetHostEntryが非常に遅い

私が実行した主な問題は、System.Net.Dns.GetHostEntryが非常に遅く、逆DNSエントリが見つからない場合です。ストレートDNSでは、DNSサーバーがNXDOMAINを返すので、これは高速でなければなりません。内部的には、「名前解決はDNS(Domain Name System)、ローカルホストファイル、または他の名前付けメカニズムで行うことができます」というws2_32.dll getnameinfo()が呼び出されています。そのため、「他の命名メカニズム」が原因ですとても遅いですが、誰もそのメカニズムが何であるか知っていますか?

逆引きが見つからない限り、通常これはIPごとに5秒かかるため、ほとんど直ちに処理されます。私は部分的にスレッドを使ってこの問題を解決しましたが、大きなリストを作成しているので、一度にたくさんのスレッドしか実行できません。

より高速になる逆DNSエントリを見つける良い方法はありますか?

答えて

5

残念ながら、クライアント側のWindows APIでこのタイムアウトを変更する方法はありません。レジストリを編集して、DNSクエリのタイムアウトの長さを変更することが最善の方法です。詳細については、this technet articleを参照してください。私の知る限り、これを行うと試行1,2、& 3が実行されるため、5秒の遅延が発生します。

これ以外の唯一のオプションは、逆DNSルックアップのasynchronous versionなど、何らかのバックグラウンド処理を使用することです。しかし、これはスレッディングを使用する予定ですので、最終的にはタイムアウトになるでしょう(多くの待機中のスレッドにもかかわらず完璧ではないため、より良いでしょう)。個人的には、膨大な数を処理しようとしている場合は、両方の方法を混在させます。一意的に逆引きを行い、レジストリを変更してタイムアウトを短くします。


コメントの後に編集:

あなたはgetnameinfoに旗を見れば、flagsパラメータがあります。私はあなたがこれにP/Invokeすることができ、フラグNI_NAMEREQD | NI_NUMERICHOSTを設定して後の動作を得ることができると信じています。 DNSエントリがないとすぐにエラーが発生し、タイムアウトに役立ちます.2番目のオプションは逆引き参照を意味します。

+1

私は実際にそのバージョンを使い始めました。時間切れの問題を効果的に回避します。私の問題は、タイムアウトがなければならないということです。 nslookupを実行するか、いくつかのランダムなIPでコマンドラインを掘ります。通常、1秒以内に "*** server.pf.localが見つからない42.23.1.42:存在しないドメイン"(またはNXDOMAIN、 digの場合) - 私はなぜGetHostEntry()が同じように動作しないのだろうかと思います。 – gregmac

+0

私はあなたがP/Invokeを使って、getnameinfoのデフォルトとは異なるフラグを使って、あなたが望むものを達成できると信じています。私の編集を参照してください。 –

0

誰かがGoogleでこれを見つけた場合に主にコメントを追加します。 ..

OSのバージョンによって動作が異なる場合があります。これらの注意事項はServer 2008 R2に適用されます。

NI_NUMERICHOSTフラグは、あなたが望むことをしません。この場合、ホスト名ではなく、ホストID(すなわち:IPアドレス)の数値バージョンを返すAPIがあります。

NI_NAMEREQDを使用しても、情報が見つからない場合(デフォルトでは5秒)にタイムアウトが発生します。私はこれがカスケードな検索のタイムアウトやその他の何らかの原因によるものかどうかは分かりませんが、このフラグはタイムアウトを防ぎません(他のフラグも表示できます)。

これは、WSALookupService APIを内部的に呼び出しているようですが、渡されるフラグは不明です。また、返される情報が正しくない可能性があります。私のテストケースの1つでは、nslookupは結果を返しませんでしたが、getnameinfoは不正確で修飾されていない名前として返されました。だから...いい答えはまだありませんが、うまくいけば、この情報は役に立ちます。

8

これはおそらく役に立ちますか?dead link.

(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

死んだリンク... :( –

4

あなたはin-addr.arpaドメインを照会することによって、かなり失敗したルックアップの速度を向上させることができます。たとえば、IPアドレスA.B.C.Dの逆IPルックアップを実行するには、DNSにドメインD.C.B.A.in-addr.arpaを照会する必要があります。逆引きが可能な場合は、ホスト名の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; 
} 
+0

解決できるアドレスではうまくいきましたが、解決不可能なアドレスを与えたときにハングアップしました。 – JimSTAT

0

誰もこれに当たる場合には...

私の代わりに時代遅れDns.GetHostByNameを呼び出すれるtcpClientのコンストラクタを使用してから切り替えます。

何らかの理由で、パフォーマンスがはるかに優れています。

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

これは、ホスト名を持っていて、IPアドレスを持っていて、元の質問 –