GetHostEntry очень медленно - PullRequest
8 голосов
/ 15 июня 2009

У меня есть приложение WinForms, и я пытаюсь получить обратные записи DNS для списка IP-адресов, отображаемых в форме.

Основная проблема, с которой я столкнулся, заключается в том, что System.Net.Dns.GetHostEntry работает смехотворно медленно, особенно когда не найдено обратной записи DNS. С прямым DNS это должно быть быстро, так как сервер DNS вернет NXDOMAIN. Внутренне он вызывает ws2_32.dll getnameinfo () , в котором говорится, что «Разрешение имен может быть с помощью системы доменных имен (DNS), локального файла хостов или других механизмов именования» - так что я предполагаю это те "другие механизмы именования", которые заставляют его работать так медленно, но кто-нибудь знает, что это за механизмы?

Обычно это занимает 5 секунд на каждый IP-адрес, если он не находит обратную запись, а затем это почти мгновенно. Я частично работал над этим, используя потоки, но поскольку я делаю большой список, и я могу запускать только столько потоков одновременно, все равно требуется время, чтобы пройти их все.

Есть ли лучший способ найти обратные записи DNS, который будет быстрее?

Ответы [ 5 ]

10 голосов
/ 02 мая 2012

Может быть, это может помочь? Версия WayBack Machine мертвая ссылка.

(Мёртвая ссылка: 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;
    }
}
5 голосов
/ 15 июня 2009

К сожалению, нет способа (о котором я знаю) изменить это время ожидания в Windows API на стороне клиента. Лучшее, что вы можете сделать, - это отредактировать реестр, чтобы изменить время ожидания в DNS-запросах. Подробнее см. в этой статье . Насколько мне известно, при этом выполняются попытки 1, 2 и 3, поэтому задержка составляет 5 секунд.

Единственным другим вариантом является использование некоторой формы фоновой обработки, такой как асинхронная версия обратного поиска DNS. Тем не менее, здесь будет использоваться многопоточность, так что вы в конечном итоге столкнетесь с тайм-аутами (это будет лучше, так как это будет происходить во многих ожидающих потоках, но все еще не идеально). Лично, если вы собираетесь обрабатывать огромное количество, я бы смешал оба подхода - выполнить обратный поиск без асинхронного поиска и изменить реестр, чтобы сократить время ожидания.


Редактировать после комментариев:

Если вы посмотрите на флаги в getnameinfo , есть параметр flags. Я считаю, что вы можете P / Invoke в это и установить флаги NI_NAMEREQD | NI_NUMERICHOST, чтобы получить поведение, которое вы после. (Первое говорит об ошибке немедленно, если нет записи DNS, что помогает тайм-ауту, второе говорит об обратном поиске.)

4 голосов
/ 29 июля 2012

Вы можете значительно повысить скорость неудачного поиска, запросив домен in-addr.arpa . Например, чтобы выполнить обратный поиск IP для IP-адреса A.B.C.D , необходимо запросить DNS для домена D.C.B.A.in-addr.arpa . Если обратный поиск возможен, возвращается запись PTR с именем хоста.

К сожалению, .NET не имеет общего API для запросов DNS. Но с помощью 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;
}
1 голос
/ 21 июля 2016

На случай, если кто-нибудь нажмет это ...

Я перешел от использования конструктора 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);
}
0 голосов
/ 21 января 2012

В основном добавление комментария на случай, если кто-то найдет это через Google, как я сделал ...

Поведение может зависеть от версии ОС; эти примечания относятся к Server 2008 R2.

Флаг NI_NUMERICHOST не делает то, что вы хотите; в этом случае API возвращает цифровую версию идентификатора хоста (т.е. IP-адрес), а не имя хоста.

Даже с NI_NAMEREQD, все еще есть тайм-аут, если информация не найдена (5 секунд по умолчанию). Я не уверен, является ли это следствием тайм-аута каскадного поиска или чего-то еще, но этот флаг не предотвращает тайм-аут (как и любой другой, насколько я могу судить).

Похоже, это вызывает API-интерфейс WSALookupService внутри, хотя неясно, какие флаги передаются. Также обратите внимание, что возвращаемая информация может быть неверной; в одном из моих тестов nslookup не дал результата, но getnameinfo вернулось как неточное и неквалифицированное имя. Так что ... да, пока нет хорошего ответа, но, надеюсь, эта информация полезна.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...