【问题标题】:DnsQuery vs DnsQueryEx - Improve performance and usage or alternativesDnsQuery 与 DnsQueryEx - 提高性能和使用或替代方案
【发布时间】:2023-01-24 07:10:10
【问题描述】:

我正在尝试在 Windows 上对 IPv4/6 使用反向 dns 查找(反垃圾邮件,如 spamhaus)。到目前为止,结果令人失望。

我的要求是:

  • 能够同时查找 IPv4 和 IPV6
  • 使用自定义 DNS 服务器,如 1.1.1.1、1.0.0.1、2606:4700:4700::1111、2606:4700:4700::1001(添加超过 1 个)
  • 可接受的查找性能

这是我到目前为止发现的:

  • DnsQuery_A/W 速度很快,但不支持 IPv6 DNS
  • DnsQueryEx 支持 IPv6 DNS,但比 DnsQuery_A/W 慢,至少在我使用同步模式的测试中是这样(我确实注意到使用异步模式的性能显着提高,但是我无法在每个循环中正确地“等待”它知识产权)
  • GetAddrInfoExW 性能太差了,所以我什至不打算谈论它

以下是在发布和默认优化下迭代 73 IP blacklist DNS 的简单向量的一些结果:

  • DnsQuery_W:11 秒
  • DnsQueryEx:24 秒

这个测试重复了几次以确保大致的时间安排。 DnsQuery_W 在任何情况下都是赢家,但是它不支持 IPv6 DNS。此外,没有文档说明如何将超过 1 个 DNS 添加到阵列中。

当然,我确实理解 DNS 服务器有时回复速度较慢;但是 20 秒是很长的时间……太长了。

示例代码 DnsQuery_W:

PDNS_RECORD pDnsRecord = { 0 };

// Calling function DnsQuery to query Host or PTR records 
DNS_STATUS status = DnsQuery_W(temp.c_str(), //Pointer to OwnerName. 
                    DNS_TYPE_A, //Type of the record to be queried.
                    DNS_QUERY_BYPASS_CACHE, // Bypasses the resolver cache on the lookup. 
                    pSrvList, //Contains DNS server IP address.
                    &pDnsRecord, //Resource record that contains the response.
                    NULL); //Reserved for future use.

if (status)
{
    wprintf(L"Failed to query the host record for %ws and the error is %ws \n", temp.c_str(), GetErrorMessage(status).c_str());
}
else
{
    wprintf(L"Found %ws in %ws and the error is %d \n", temp.c_str(), list.second.c_str(), status);

    // Free memory allocated for DNS records.
    DNS_FREE_TYPE freetype;
    freetype = DnsFreeRecordListDeep;
    DnsRecordListFree(pDnsRecord, freetype);
}

示例代码 DnsQueryEx:

SOCKADDR_STORAGE SockAddr           = { 0 };
INT AddressLength                   = sizeof(SockAddr);
WSAStringToAddressW((PWSTR)L"1.1.1.1", AF_INET, NULL, (LPSOCKADDR)&SockAddr, &AddressLength);

DNS_ADDR_ARRAY DnsServerList        = { 0 };
DnsServerList.MaxCount              = 1;
DnsServerList.AddrCount             = 1;
CopyMemory(DnsServerList.AddrArray[0].MaxSa, &SockAddr, DNS_ADDR_MAX_SOCKADDR_LENGTH);

PDNS_QUERY_CONTEXT pDnsQueryContext = NULL;
pDnsQueryContext                    = (PDNS_QUERY_CONTEXT)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(DNS_QUERY_CONTEXT));

if (NULL == pDnsQueryContext) {
    std::wcout << L"HeapAlloc() failed with error: " << GetErrorMessage(GetLastError()).c_str();
    continue;
}

pDnsQueryContext->QueryType             = DNS_TYPE_A;
pDnsQueryContext->QueryResult.Version   = DNS_QUERY_REQUEST_VERSION1;
pDnsQueryContext->Callback              = NULL;

DNS_QUERY_REQUEST DnsQueryRequest       = { 0 };
DnsQueryRequest.Version                 = DNS_QUERY_REQUEST_VERSION1;
DnsQueryRequest.QueryName               = temp.c_str();
DnsQueryRequest.QueryType               = pDnsQueryContext->QueryType;
DnsQueryRequest.QueryOptions            = DNS_QUERY_BYPASS_CACHE;
DnsQueryRequest.pDnsServerList          = &DnsServerList;
DnsQueryRequest.InterfaceIndex          = 0;
// By omitting the DNS_QUERY_COMPLETION_ROUTINE callback from the pQueryCompleteCallback member of this structure, DnsQueryEx is called synchronously.
DnsQueryRequest.pQueryCompletionCallback = NULL;
DnsQueryRequest.pQueryContext           = pDnsQueryContext;

auto start = std::chrono::high_resolution_clock::now();

DNS_STATUS DnsStatus = DnsQueryEx(&DnsQueryRequest, &pDnsQueryContext->QueryResult, &pDnsQueryContext->DnsCancelHandle);

auto stop = std::chrono::high_resolution_clock::now();

std::wcout << L"DnsStatus: " << DnsStatus << L" (" << std::chrono::duration_cast<std::chrono::milliseconds>(stop - start).count() << L"ms) -> " << GetErrorMessage(DnsStatus).c_str();

pDnsQueryContext->QueryResult.QueryStatus = DnsStatus;

if (pDnsQueryContext->QueryResult.QueryStatus != ERROR_SUCCESS)
{
    if (NULL != pDnsQueryContext->QueryResult.pQueryRecords) {
        DnsRecordListFree(pDnsQueryContext->QueryResult.pQueryRecords, DnsFreeRecordList);
    }
    HeapFree(GetProcessHeap(), NULL, pDnsQueryContext);
    continue;
}

for (PDNS_RECORD p = pDnsQueryContext->QueryResult.pQueryRecords; p; p = p->pNext)
{
    WCHAR ipAddress[128] = {0};

    switch (p->wType)
    {
        case DNS_TYPE_A:
        {
            IN_ADDR ipv4;
            ipv4.S_un.S_addr = p->Data.A.IpAddress;
            RtlIpv4AddressToStringW(&ipv4, ipAddress);
        }
        break;

        case DNS_TYPE_AAAA:
        {
            IN6_ADDR ipv6;
            memcpy(ipv6.u.Byte, p->Data.AAAA.Ip6Address.IP6Byte, sizeof(ipv6.u.Byte));
            RtlIpv6AddressToStringW(&ipv6, ipAddress);
        }
        break;

        default:
            break;
    }
        
    std::wcout << L"Found IP: " << ipAddress << L" in DNS: " << temp.c_str() << std::endl;
}

DnsRecordListFree(pDnsQueryContext->QueryResult.pQueryRecords, DnsFreeRecordList);
HeapFree(GetProcessHeap(), NULL, pDnsQueryContext);

有人可以就如何实现我的目标提出建议吗?

我很乐意使用任何 C++ 库,如 Boost 等,如果有任何开箱即用的东西可以正常工作的话。

另外,如果有人能告诉我如何在向量循环内“等待每个结果”,我会非常乐意使用 DnsQueryEx 的异步方法。

非常感激!

【问题讨论】:

  • 我没有看到 DnsQuery()DnsQueryEx() 之间的性能有任何显着差异,而且我肯定没有看到任何接近 10 秒的水平。我在一个循环中运行了 100 次,每次总共花费大约 1.4 到 1.7 秒。不过,我只查询 1.1.1.1,所以问题可能出在其他 DNS 服务器之一或特定于您的网络。
  • @Luke 这很奇怪。 1.1.1.1 是地球上最快的 dns,但仍然很奇怪。您可以尝试使用像 REVERSED_IP.zen.spamhaus.org 这样的反向 dns 吗?
  • @Luke 我刚刚意识到... 1.4 秒对于 dns 解析来说已经很多了吗?
  • 这 1.4 秒用于 100 个 DNS 查询。使用 zen.spamhaus.org 的速度要慢得多,仅 10 个 DNS 查询就需要大约 1.7 秒。
  • 哎呀,无视那个。测试被我愚蠢的 VPN 干扰了。查询 100 次大约需要 1.5 秒。

标签: c++ c windows winapi dns


【解决方案1】:

如果您仍然感兴趣,这是我的经验。我使用 DnsQuery_W,速度足够快,无需异步操作,而且不像 DnsQueryEx 那样复杂。您可以很好地指定多个 DNS 服务器地址并也使用 IPv6。您肯定安装了 Powershell。如果您使用 ILSpy 或 .NET Reflector 查看已安装模块中的“dnslookup.dll”文件,您会在“ResolveDnsName”下找到一个如何使用 DnsQuery_W 的很好示例。它是 Powershell 模块“Resolve-DnsName”的源代码。反向查询比 GetHostEntry 快得多。

这是一个 - 不是很短 - 总结。我仅将 DnsQuery 用于反向查询,因此这是一个真正精简的示例。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;


public class DnsQuerySample
{
    #region structs and enums

    [StructLayout(LayoutKind.Sequential)]
    public struct sockaddr_in
    {
        private ushort Family;
        private ushort sin6_port;
        private uint IP4Addr;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8, ArraySubType = UnmanagedType.U1)]
        private char[] zero;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct sockaddr_in6
    {
        internal ushort sin6_family;
        private ushort sin6_port;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.U1)]
        public byte[] IP4Addr;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)]
        public byte[] IP6Address;
        private uint sin6_scope_id;
    }

    [StructLayout(LayoutKind.Explicit)]
    internal struct DnsAddr
    {
        [FieldOffset(0)]
        internal sockaddr_in6 SockAddr;
        [FieldOffset(0x20)]
        internal uint SockAddrLength;
        [FieldOffset(0x24)]
        internal uint SubnetLength;
        [FieldOffset(40)]
        internal uint Flags;
        [FieldOffset(0x2c)]
        internal uint Status;
        [FieldOffset(0x30)]
        internal uint Priority;
        [FieldOffset(0x34)]
        internal uint Weight;
        [FieldOffset(0x38)]
        internal uint Tag;
        [FieldOffset(60)]
        internal uint PayloadSize;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct DnsAddrArray
    {
        internal uint MaxCount;
        internal uint AddrCount;
        private uint Tag;
        internal ushort Family;
        private ushort Reserved;
        private uint Flags;
        private uint MatchFlag;
        private uint Reserved1;
        private uint Reserved2;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
        internal DnsAddr[] DnsAddr;
    }


    [StructLayout(LayoutKind.Sequential, Size = 120)]
    internal struct DnsExtraInfo
    {
        public uint Version;
        public uint Size;
        public IntPtr pNext;
        public uint ID;
        public uint Reserved;
        public IntPtr ServerList;
        public DnsExtraInfo(IPAddress[] DnsServers)
        {
            this = new DnsExtraInfo();
            this.ID = 10;
            this.Version = 0x80000001;
            this.pNext = IntPtr.Zero;
            DnsAddrArray structure = new DnsAddrArray
            {
                AddrCount = (uint)DnsServers.Length,
                MaxCount = 5,
                DnsAddr = new DnsAddr[5]
            };
            for (int i = 0; (i < DnsServers.Length) && (i < 5); i++)
            {
                structure.Family = (ushort)DnsServers[i].AddressFamily;
                structure.DnsAddr[i].SockAddr.sin6_family = (ushort)DnsServers[i].AddressFamily;
                if (DnsServers[i].AddressFamily == ((AddressFamily)((int)AddressFamily.InterNetworkV6)))
                {
                    structure.DnsAddr[i].SockAddr.IP6Address = DnsServers[i].GetAddressBytes();
                    structure.DnsAddr[i].SockAddrLength = (uint)Marshal.SizeOf<sockaddr_in6>();
                }
                else
                {
                    structure.DnsAddr[i].SockAddr.IP4Addr = DnsServers[i].GetAddressBytes();
                    structure.DnsAddr[i].SockAddrLength = (uint)Marshal.SizeOf<sockaddr_in>();
                }
            }
            this.ServerList = Marshal.AllocHGlobal(Marshal.SizeOf<DnsAddrArray>());
            Marshal.StructureToPtr<DnsAddrArray>(structure, this.ServerList, false);
        }
    }

    internal enum RecordType : ushort
    {
        PTR = 12
    }

    internal enum QueryOptions : ulong
    {
        DNS_QUERY_BYPASS_CACHE = 0x00000008,    // Bypasses the resolver cache on the lookup.
        DNS_QUERY_NO_HOSTS_FILE = 0x00000040,   // Prevents the DNS query from consulting the HOSTS file.Windows 2000 Server and Windows 2000 Professional: This value is not supported.
        DNS_QUERY_NO_NETBT = 0x00000080,        // Prevents the DNS query from using NetBT for resolution.Windows 2000 Server and Windows 2000 Professional: This value is not supported.
        DNS_QUERY_WIRE_ONLY = 0x00000100,       // Directs DNS to perform a query using the network only, bypassing local information.Windows 2000 Server and Windows 2000 Professional: This value is not supported.
        DNS_QUERY_MULTICAST_ONLY = 0x00000400,  // Prevents the query from using DNS and uses only Local Link Multicast Name Resolution (LLMNR).Windows Vista and Windows Server 2008 or later.: This value is supported.
        DNS_QUERY_NO_MULTICAST = 0x00000800,    // (opposite of DNS_QUERY_MULTICAST_ONLY)
        DNS_QUERY_TREAT_AS_FQDN = 0x00001000    // Prevents the DNS response from attaching suffixes to the submitted name in a name resolution process.
    }

    /// <summary>
    /// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms682082(v=vs.85).aspx
    /// These field offsets could be different depending on endianness and bitness
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    internal struct DnsRecord
    {
        public IntPtr pNext;        // DnsRecord*
        public IntPtr pName;        // string
        public RecordType wType;
        public ushort wDataLength;
        public FlagsUnion Flags;
        public uint dwTtl;
        public uint dwReserved;     // maybe QuestionClass, alway be 1 (DNS_CLASS_INTERNET)
        public DataUnion Data;
    }

    [StructLayout(LayoutKind.Explicit)]
    internal struct DataUnion
    {
        [FieldOffset(0)]
        public DNS_PTR_DATA PTR, NS, CNAME, DNAME, MB, MD, MF, MG, MR;
        [FieldOffset(0)]
        public IntPtr pDataPtr;
    }

    /// <summary>
    /// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms682080(v=vs.85).aspx
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public struct DNS_PTR_DATA
    {
        public IntPtr pNameHost;    // string
        public string NameHost { get { return Marshal.PtrToStringAuto(pNameHost); } }
    }

    [StructLayout(LayoutKind.Explicit)]
    internal struct FlagsUnion
    {
        [FieldOffset(0)]
        public uint DW;
        [FieldOffset(0)]
        public DnsRecordFlags S;
    }

    /// <summary>
    /// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms682084(v=vs.85).aspx
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    internal struct DnsRecordFlags
    {
        internal uint data;

        // DWORD Section :2;
        public DnsSection Section
        {
            get { return (DnsSection)(data & 0x3u); }
            set { data = (data & ~0x3u) | (((uint)value) & 0x3u); }
        }

        // DWORD Delete :1;
        public uint Delete // Reserved. Do not use.
        {
            get { return (data >> 2) & 0x1u; }
            set { data = (data & ~(0x1u << 2)) | (value & 0x1u) << 2; }
        }

        // DWORD CharSet :2;
        public DnsCharset CharSet
        {
            get { return (DnsCharset)((data >> 3) & 0x3u); }
            set { data = (data & ~(0x3u << 3)) | (((uint)value) & 0x3u) << 3; }
        }

        // DWORD Unused :3;
        public uint Unused // Reserved. Do not use.
        {
            get { return (data >> 5) & 0x7u; }
            set { data = (data & ~(0x7u << 5)) | (value & 0x7u) << 5; }
        }

        // DWORD Reserved :24;
        public uint Reserved // Reserved. Do not use.
        {
            get { return (data >> 8) & 0xFFFFFFu; }
            set { data = (data & ~(0xFFFFFFu << 8)) | (value & 0xFFFFFFu) << 8; }
        }
    }

    internal enum DnsSection : uint
    {
        Question = 0,
        Answer = 1,
        Authority = 2,
        Additional = 3
    }

    internal enum DnsCharset : uint
    {
        Unknown = 0,
        Unicode = 1,
        Utf8 = 2,
        Ansi = 3
    }


    /// <summary>
    /// Call Win32 DNS API DnsQuery.
    /// </summary>
    /// <param name="pszName">Host name.</param>
    /// <param name="wType">DNS Record type.</param>
    /// <param name="options">DNS Query options.</param>
    /// <param name="pExtra">Fka aipServers, now reserved but functional; IP4AddressArray or DnsExtraInfo, we use DnsExtraInfo.</param>
    /// <param name="ppQueryResults">Query results.</param>
    /// <param name="pReserved">Reserved argument.</param>
    /// <returns>WIN32 status code</returns>
    /// <remarks>For aipServers, DnqQuery expects either null or an array of one IPv4 address.</remarks>
    [DllImport("dnsapi", CharSet = CharSet.Unicode, EntryPoint = "DnsQuery_W", ExactSpelling = true, SetLastError = true)]
    private static extern int _DnsQueryW(
        [In] string pszName,
        [In] ushort wType,
        [In] uint options,
        [In][Out] ref DnsExtraInfo pExtra,
        [Out] out IntPtr ppQueryResults,
        [Out] IntPtr pReserved);

    internal static int DnsQuery(string pszName, RecordType wType, QueryOptions options, ref DnsExtraInfo pExtra, out IntPtr ppQueryResults)
    {
        return _DnsQueryW(pszName, (ushort)wType, (uint)options, ref pExtra, out ppQueryResults, IntPtr.Zero);
    }

    /// <summary>
    /// Call Win32 DNS API DnsRecordListFree.
    /// </summary>
    /// <param name="pRecordList">DNS records pointer</param>
    /// <param name="FreeType">Record List Free type</param>
    [DllImport("dnsapi", CharSet = CharSet.Auto, EntryPoint = "DnsRecordListFree", ExactSpelling = true, SetLastError = true)]
    private static extern void _DnsRecordListFree(IntPtr pRecordList, int FreeType);

    #endregion structs and enums

    internal static void DnsRecordListFree(IntPtr pRecordList)
    {
        _DnsRecordListFree(pRecordList, 0);
    }

    public static List<string> GetHostName(IPAddress iPAddress, IPAddress[] DNSServerAddresses)
    {
        if (Environment.OSVersion.Platform != PlatformID.Win32NT)
        {
            throw new NotSupportedException();
        }

        // Use DNS only, fastest queries with these options.
        QueryOptions options = QueryOptions.DNS_QUERY_BYPASS_CACHE |
                               QueryOptions.DNS_QUERY_NO_HOSTS_FILE |
                               QueryOptions.DNS_QUERY_NO_MULTICAST |
                               QueryOptions.DNS_QUERY_NO_NETBT |
                               QueryOptions.DNS_QUERY_TREAT_AS_FQDN |
                               QueryOptions.DNS_QUERY_WIRE_ONLY;

        DnsExtraInfo pExtra = new DnsExtraInfo();

        if (DNSServerAddresses != null)
        {
            if (DNSServerAddresses.Length > 5)
            {
                throw new ArgumentException("A maximum of 5 addresses are supported!", "DNSServerAddresses");
            }
            pExtra = new DnsExtraInfo(DNSServerAddresses);
        }

        var recordsArray = IntPtr.Zero;
        string reverse = ToReverseLookup(iPAddress);
        try
        {
            var result = DnsQuery(reverse,
                                  RecordType.PTR,
                                  options,
                                  ref pExtra,
                                  out recordsArray);
            if (result != 0)
            {
                Win32Exception ex = new Win32Exception(result);
                throw new Win32Exception(result, iPAddress.ToString() + " : " + ex.Message);
            }

            DnsRecord record;
            var recordList = new List<string>();
            for (var recordPtr = recordsArray; !recordPtr.Equals(IntPtr.Zero); recordPtr = record.pNext)
            {
                record = (DnsRecord)Marshal.PtrToStructure(recordPtr, typeof(DnsRecord));
                if (record.wType == RecordType.PTR)
                {
                    recordList.Add(record.Data.PTR.NameHost);
                    //break; // one name is enough
                }
            }

            return recordList;
        }
        finally
        {
            if (recordsArray != IntPtr.Zero)
            {
                DnsRecordListFree(recordsArray);
            }
            if (pExtra.ServerList != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(pExtra.ServerList);
                pExtra.ServerList = IntPtr.Zero;
            }
        }
    }

    internal static string ToReverseLookup(IPAddress iPAddress)
    {
        string text = iPAddress.ToString();
        if (iPAddress.AddressFamily == AddressFamily.InterNetwork)
        {
            string[] array = text.Split(new char[1] { '.' });
            text = array[3] + "." + array[2] + "." + array[1] + "." + array[0] + ".in-addr.arpa";
        }
        else
        {
            if (iPAddress.AddressFamily != AddressFamily.InterNetworkV6)
            {
                throw new NotSupportedException();
            }
            text = "ip6.arpa";
            byte[] addressBytes = iPAddress.GetAddressBytes();
            foreach (byte num in addressBytes)
            {
                byte b = (byte)(num & 0xFu);
                byte b2 = (byte)((num & 0xF0) >> 4);
                text = string.Format("{0:X}.{1:X}.{2}", b, b2, text);
            }
        }
        return text;
    }

    public DnsQuerySample()
    { }
}

您可以将其用于:

IPAddress[] servers =
{
    IPAddress.Parse("1.1.1.1"),
    IPAddress.Parse("8.8.8.8"),
    IPAddress.Parse("2606:4700:4700::1111"),
    IPAddress.Parse("2001:4860:4860::8888")
};

IPAddress search = IPAddress.Parse("2a02:2e0:3fe:1001:302::");

var ret = DnsQuerySample.GetHostName(search, servers);
foreach (var name in ret)
{
    Console.WriteLine(name);
}

使用 Wireshark 或防火墙,您可以看到它适用于 IPv6,并且 DnsQuery 使用服务器地址。不知道DnsQuery中是否存储了对5个地址的限制。如果相应地调整结构,也许可以增加数量。玩得开心!

【讨论】:

    猜你喜欢
    • 2019-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-07
    相关资源
    最近更新 更多