2009-06-15 15 views
7

Mam aplikację WinForms i próbuję uzyskać odwrotne wpisy DNS dla listy adresów IP wyświetlanych w formularzu.GetHostEntry jest bardzo powolny

Główny problem Zabrakło mi na to System.Net.Dns.GetHostEntry jest śmiesznie powolna, szczególnie, gdy nie reverse DNS znajduje się wpis. W przypadku prostego systemu DNS powinno to być szybkie, ponieważ serwer DNS zwróci NXDOMAIN. Wewnętrznie nazywa się ws2_32.dll getnameinfo(), która stwierdza "Rozpoznawanie nazw może być przez system nazw domen (DNS), lokalny plik hosts lub przez inne mechanizmy nazewnictwa" - więc zakładam, że to "inne mechanizmy nazewnictwa" powodują, że być tak powolnym, ale czy ktoś wie, jakie są te mechanizmy?

Generalnie to bierze 5 sekund na IP, chyba że stwierdzi odwrotną pozycję, a potem to niemal natychmiastowe. Częściowo pracowałem nad tym przy użyciu wątków, ale ponieważ robię dużą listę i mogę uruchamiać tylko wiele wątków naraz, to i tak zajmie im to trochę czasu.

Czy istnieje lepszy sposób na znalezienie wpisy reverse DNS, że będzie szybciej?

Odpowiedz

5

Niestety, nie ma sposobu, (z których jestem świadomy), aby zmienić ten limit czasu w Windows API po stronie klienta. Najlepsze, co możesz zrobić, to edytować rejestr w celu zmiany czasu oczekiwania na kwerendy DNS. Szczegóły: this technet article. Według mojej wiedzy, próby 1, 2, & 3 są uruchamiane, gdy to robisz, stąd 5-sekundowe opóźnienie.

Jedyną inną opcją jest użycie jakiejś formy przetwarzania w tle, takiej jak ta asynchronous version wyszukiwania wstecznego DNS. To będzie używać wątków, więc w końcu natkniesz się na limity czasu (będzie lepiej, ponieważ będzie to dotyczyło wielu oczekujących wątków, ale wciąż nie jest doskonałe). Osobiście, jeśli masz zamiar przetworzyć ogromną liczbę, połączyłbym oba podejścia - wykonam odwrotne wyszukiwanie w skrócie ORAZ zmodyfikuję rejestr w celu skrócenia czasu oczekiwania.


Edit po komentarzach:

Jeśli spojrzeć na flagi na getnameinfo, istnieje parametr flagi. Wierzę, że możesz P/Wywołać do tego i ustawić flagi NI_NAMEREQD | NI_NUMERICHOST, aby uzyskać pożądane zachowanie. (Pierwszy z nich mówi o błędzie natychmiast, jeśli nie ma wpisu DNS, co pomaga w przekroczeniu limitu czasu - drugi mówi, aby wykonać odwrotne wyszukiwanie.)

+1

Właściwie skorzystałem z tej wersji. Skutecznie omija problem z przekroczeniem limitu czasu. Mój problem polega bardziej na tym, że musi upłynąć trochę czasu. Idź uruchomić nslookup lub wykop w wierszu poleceń z przypadkowym adresem IP - zwykle powróci w ciągu <1 s i powie "*** serwer.pf.local nie może znaleźć 42.23.1.42: Nieistniejąca domena" (lub NXDOMAIN, w przypadku wykopywania) - Zastanawiam się, dlaczego GetHostEntry() nie działa w ten sam sposób. – gregmac

+0

Wierzę, że możesz osiągnąć to, co chcesz, poprzez P/Invoke, używając różnych flag niż domyślne w getnameinfo. Zobacz moją edycję. –

0

Głównie dodawanie komentarza na wypadek, gdyby ktoś znalazł to przez Google, tak jak ja. ..

Zachowanie może być zależne od wersji systemu operacyjnego; te uwagi dotyczą serwera 2008 R2.

Flaga NI_NUMERICHOST nie rób co chcesz; w tym przypadku interfejs API zwraca numeryczną wersję identyfikatora hosta (tj.: adres IP), a nie nazwę hosta.

Nawet NI_NAMEREQD, nadal istnieje limit czasu, jeśli informacja nie została znaleziona (5 sekund domyślnie). Nie jestem pewien, czy jest to spowodowane kaskadowym czasem oczekiwania na wyszukiwanie, czy też czymś innym, ale ta flaga nie zapobiega przekroczeniu limitu czasu (ani żadnej innej flagi, o ile wiem).

Wygląda na to, że to wywołuje API WSALookupService API, chociaż nie jest jasne, jakie flagi są przekazywane. Zwróć też uwagę, że zwrócone informacje mogą być nieprawidłowe; w jednym z moich przypadków testowych nslookup nie zwrócił żadnego wyniku, ale getnameinfo został zwrócony jako niedokładna i niekwalifikowana nazwa. Więc ... tak, nie ma jeszcze dobrej odpowiedzi, ale mam nadzieję, że ta informacja jest pomocna.

8

Może to może pomóc?Wayback wersja Maszyna dead link.

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

Kod dla potomności:

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

dead link ... :( –

4

Można zwiększyć szybkość nieudanej odnośnika znacznie przez odpytywanie domenę in-addr.arpa. E.g, aby wykonać odwrotne wyszukiwanie adresu IP dla adresu IP A.B.C.D powinieneś zapytać DNS dla domeny D.C.B.A.in-addr.arpa. Jeśli możliwe jest wyszukiwanie wsteczne, zwracany jest rekord PTR z nazwą hosta.

Niestety .NET nie ma ogólnego interfejsu API do wysyłania zapytań do DNS. Ale używając P/Invoke możesz wywołać API DNS, aby uzyskać pożądany wynik (funkcja zwróci null, jeśli odwrotne wyszukiwanie się nie powiedzie).

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

To zadziałało dobrze dla adresu, który można rozwiązać, ale zawieszono, gdy nadałem mu nierozstrzygalny adres: – JimSTAT

0

W przypadku ktoś uderza to ...

przeszedłem z użyciem konstruktora TcpClient wywołaniem nieaktualne Dns.GetHostByName zamiast.

Z jakiegokolwiek powodu działa znacznie lepiej.

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

Działa to tylko wtedy, gdy masz już nazwę hosta, a nie, jeśli masz adres IP i chcesz mieć nazwę hosta, jak w oryginalne pytanie –

Powiązane problemy