2009-06-15 31 views
7

Tengo una aplicación WinForms, y estoy tratando de obtener entradas DNS inversas para una lista de direcciones IP que se muestran en el formulario.GetHostEntry es muy lento

El problema principal con el que me he encontrado es System.Net.Dns.GetHostEntry es ridículamente lento, particularmente cuando no se encuentra una entrada DNS inversa. Con DNS directo, esto debería ser rápido, ya que el servidor DNS devolverá NXDOMAIN. Internamente, está llamando al ws2_32.dll getnameinfo(), que dice "La resolución del nombre puede ser por el Sistema de nombres de dominio (DNS), un archivo de hosts local o por otros mecanismos de denominación", así que supongo que son esos "otros mecanismos de nomenclatura" los que lo causan. sea ​​tan lento, pero ¿alguien sabe cuáles son esos mecanismos?

Generalmente esto demora 5 segundos por IP, a menos que encuentre una entrada inversa, y luego es casi inmediato. En parte, he trabajado en esto usando hilos, pero dado que estoy haciendo una gran lista y solo puedo ejecutar tantos hilos a la vez, todavía me lleva un tiempo superarlos.

¿Existe una forma mejor de encontrar entradas DNS inversas que sea más rápida?

Respuesta

5

Desafortunadamente, no hay forma (de lo que soy consciente) de cambiar este tiempo de espera en la API de Windows, en el lado del cliente. Lo mejor que puede hacer es editar el registro para modificar la duración de los tiempos de espera en las consultas DNS. Vea this technet article para más detalles. Que yo sepa, los intentos 1, 2, & 3 se ejecutan cuando haces esto, de ahí el retraso de 5 segundos.

La única otra opción es utilizar algún tipo de procesamiento de fondo, como este asynchronous version de búsquedas DNS inversas. Sin embargo, esto va a utilizar subprocesos, por lo que eventualmente se encontrará con los tiempos de espera (será mejor, ya que estará en muchos hilos de espera, pero aún no es perfecto). Personalmente, si va a procesar un número enorme, mezclaría ambos enfoques: realice una búsqueda inversa ansiosamente y modifique el registro para acortar el tiempo de espera.


Edición después de comentarios:

Si nos fijamos en las banderas en getnameinfo, hay un parámetro flags. Creo que puedes P/Invocar en esto y configurar los indicadores NI_NAMEREQD | NI_NUMERICHOST para obtener el comportamiento que buscas. (El primero dice que el error sale inmediatamente si no hay una entrada DNS, lo que ayuda a agotar el tiempo de espera; el segundo dice que haga la búsqueda inversa).

+1

En realidad usé esa versión para comenzar. Efectivamente soluciona el problema del tiempo de espera. Mi problema es más que tiene que haber un tiempo de espera en absoluto. Ve a ejecutar nslookup o excava en la línea de comando con una IP aleatoria; por lo general, volverá en <1 s y dirá "*** server.pf.local no puede encontrar 42.23.1.42: dominio inexistente" (o NXDOMAIN, en el caso de dig) - Me pregunto por qué GetHostEntry() no funciona de la misma manera. – gregmac

+0

Creo que puede lograr lo que quiere a través de P/Invoke, mediante el uso de diferentes banderas que los valores predeterminados en getnameinfo. Ver mi edición –

0

Principalmente agregando un comentario en caso de que alguien lo encuentre a través de google, como lo hice. ..

El comportamiento puede ser específico de la versión del SO; estas notas se aplican para Server 2008 R2.

La bandera NI_NUMERICHOST no hace lo que desea; en este caso, la API devuelve la versión numérica del identificador del host (es decir, la dirección IP), en lugar del nombre de host.

Incluso con NI_NAMEREQD, todavía hay un tiempo de espera si la información no se encuentra (5 segundos por defecto). No estoy seguro de si esto se debe a un tiempo de espera de búsqueda en cascada, o algo más, pero esta bandera no impide el tiempo de espera (ni lo hace ninguna otra bandera, por lo que yo sé).

Parece que esto llama a la API de WSALookupService internamente, aunque no está claro qué banderas se están pasando. Además, tenga en cuenta que la información devuelta puede ser incorrecta; en uno de mis casos de prueba, nslookup no arrojó ningún resultado, pero se devolvió getnameinfo como un nombre inexacto y no calificado. Entonces ... sí, todavía no hay una buena respuesta, pero espero que esta información sea útil.

8

Quizás esto puede ayudar?La versión Wayback Machine de la dead link.

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

el código, para la posteridad:

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

vínculo muerto ... :( –

4

se puede mejorar la velocidad de una búsqueda fallida considerablemente mediante la consulta del dominio in-addr.arpa. Por ejemplo, para realizar una búsqueda IP inversa para la dirección IP A.B.C.D debe consultar DNS para el dominio D.C.B.A.in-addr.arpa. Si es posible la búsqueda inversa, se devuelve un registro PTR con el nombre de host.

Desafortunadamente .NET no tiene una API general para consultar el DNS. Pero al usar P/Invoke puede llamar a la API DNS para obtener el resultado deseado (la función devolverá null si falla la búsqueda inversa).

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

Esto funcionó bien para una dirección resoluble, pero colgó cuando le di una dirección insoluble. – JimSTAT

0

En caso de que alguien llega a este ...

he pasado de utilizar el constructor TcpClient a llamar a la obsoleta Dns.GetHostByName lugar.

Por alguna razón, funciona mucho mejor.

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

Esto solo funciona si ya tiene un nombre de host, no si tiene una dirección IP y desea un nombre de host como en la pregunta original –