我正在尝试在Windows上为IPv4/6使用反向dns查找(类似反垃圾邮件的spamhaus(。到目前为止,结果非常令人失望。
我的要求是:
- 同时查找IPv4和IPV6的能力
- 使用自定义DNS服务器,如1.1.1.1、1.0.0.1、2606:4700:4700::1111、2606: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的异步方法,我将非常乐意;等待每个结果";在矢量循环中。
非常感谢
如果你仍然感兴趣,下面是我的经验。我使用DnsQuery_W,速度足够快,不需要异步,也不像DnsQueryEx那样复杂。您可以很好地指定多个DNS服务器地址,也可以使用IPv6。你肯定安装了Powershell。如果您使用ILSpy或.NET反射器来查看";dnslookup.dll";文件中,你会发现一个很好的例子,说明如何在";ResolveDnsName"。它是Powershell模块"的源代码;解析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个地址的限制。如果你相应地调整结构,也许你可以增加这个数字。玩得高兴