DnsQuery 与 DnsQueryEx - 提高性能和使用率或替代方案

发布于 2025-01-12 03:37:03 字数 5850 浏览 5 评论 0原文

我正在尝试在 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 至少在我使用同步模式的测试中(我确实注意到使用异步模式的性能明显更快,但是我无法在每个 IP 的循环内正确“等待”它)
  • GetAddrInfoExW 的性能很糟糕,所以甚至不打算谈论它

以下是迭代 73 IP 黑名单 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 的异步方法。

非常感谢!

I am trying to use reverse dns lookup (antispam like spamhaus) for IPv4/6 on Windows. So far the results are more than disappointing.

My requirement would be:

  • Ability to lookup both IPv4 and IPV6
  • Use custom DNS servers like 1.1.1.1, 1.0.0.1, 2606:4700:4700::1111, 2606:4700:4700::1001 (add more than 1)
  • Acceptable performance for lookup

Here is what I found so far:

  • DnsQuery_A/W is fast-ish, but does not support IPv6 DNS
  • DnsQueryEx supports IPv6 DNS, but is slower than DnsQuery_A/W, at least in my tests using synchronous mode (I did notice significant faster performance using asynchronous mode, however I am unable to "wait" for it properly inside a loop for each IP)
  • GetAddrInfoExW is just terrible in performance, so not even going to talk about it

And here are some results from iterating a simple vector of 73 IP blacklist DNS, under Release and default optimizations:

  • DnsQuery_W: 11 seconds
  • DnsQueryEx: 24 seconds

This test was repeated several times to ensure a rough timing. DnsQuery_W is the winner in any case, however this does not support IPv6 DNS. Furthermore, there is no documentation how to add more than 1 DNS into the array.

Of course, I do understand that DNS servers can reply sometimes slower; however 20 seconds is a long time... too long.

Sample code 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);
}

Sample code 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);

Can someone please advise on how to achieve my goal(s)?

I'm happy to use any C++ libraries like Boost, etc, if there is anything out of the box that works decently.

Also, I would be more than happy to use the async method of DnsQueryEx, if someone can show me how to "wait for each result" inside a vector loop.

Much appreciated!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

梦屿孤独相伴 2025-01-19 03:37:03

如果你还有兴趣的话,这是我的经验。我使用 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 && DNSServerAddresses.Length != 0)
        {
            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个地址的限制。如果相应地调整结构,也许可以增加数量。玩得开心!

If you're still interested, here's my experience. I use DnsQuery_W, fast enough to do without asynchronous and not as complicated as DnsQueryEx. And you can very well specify more than one DNS server address and use IPv6 too. You certainly have Powershell installed. If you use ILSpy or .NET Reflector to look at the "dnslookup.dll" file in the installed modules, you'll find a nice example of how to use DnsQuery_W under "ResolveDnsName". It is the source code for the Powershell module "Resolve-DnsName". Extremely faster than GetHostEntry for reverse queries.

Here is a - not so short - summary. I use DnsQuery only for reverse queries, so this is a really stripped down sample.

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 && DNSServerAddresses.Length != 0)
        {
            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()
    { }
}

You can use it with:

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);
}

With Wireshark or a firewall you can see that it works with IPv6 and that DnsQuery uses the server addresses. I don't know if the restriction to 5 addresses is stored in DnsQuery. Maybe you can increase the number if you adjust the structures accordingly. Have fun!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文