Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
21787a2
Add System.Net.Dns resolver API surface and Windows implementation
rzikm Jun 3, 2026
77dd68c
Add DnsResolver tests
rzikm Jun 3, 2026
f0ef4af
Add loopback DNS test suite and restrict DnsQueryEx custom servers to…
rzikm Jun 4, 2026
a589fad
Polish tests
rzikm Jun 11, 2026
73acd3c
Add synchronous DnsResolver methods using synchronous DnsQueryEx
rzikm Jun 11, 2026
c375219
Unify sync/async DnsResolver core methods into single bool-async methods
rzikm Jun 11, 2026
8196da6
Assert sync DnsResolver core tasks complete synchronously
rzikm Jun 11, 2026
522c396
Refactor Windows DnsResolver logic into DnsResolverPal (PAL pattern)
rzikm Jun 11, 2026
6157140
Add telemetry to DnsResolver
rzikm Jun 11, 2026
6f99d7b
Fix DnsResolver telemetry under-measuring synchronous queries
rzikm Jun 11, 2026
6068489
Address Windows DNS PR review feedback
rzikm Jun 12, 2026
42bb74e
Keep instance DnsResolver.ResolvePtr(IPAddress) overloads
rzikm Jun 16, 2026
6a9f986
More code review feedback
rzikm Jun 17, 2026
023ff8b
Reduce unsafe code in Windows DNS PAL/interop layer
rzikm Jun 17, 2026
4247861
More minor changes
rzikm Jun 17, 2026
3af512a
Address PR review feedback: thread-safety and server validation
rzikm Jun 18, 2026
14b6592
Add synchronous API coverage to DnsResolverTest via bool async theories
rzikm Jun 18, 2026
ed8096c
Address PR review feedback: port normalization, skip handling, comments
rzikm Jun 18, 2026
693bd6e
Address PR #129845 review feedback
rzikm Jun 25, 2026
982d370
Modernize DnsQueryEx interop per review feedback
rzikm Jun 26, 2026
99054e7
Address PR review feedback on DNS resolver
rzikm Jun 26, 2026
0f2ebd6
More feedback
rzikm Jun 27, 2026
ff8e4fc
Feedback
rzikm Jun 27, 2026
38dd455
regenerate ref source
rzikm Jun 27, 2026
83c8739
Address PR feedback: remove dead interop, fix positive-result TTL
rzikm Jun 30, 2026
ea64984
Move server validation into a platform-specific PAL hook
rzikm Jun 30, 2026
be74d7f
Make loopback tests innerloop
rzikm Jul 1, 2026
796a3dc
bind loopback to ephemeral port on non-windows platforms
rzikm Jul 1, 2026
d14266f
Make NegativeCacheTtl best-effort and document thread safety
rzikm Jul 1, 2026
97ad29d
Address review: accurate name exception docs and atomic TcpRequestCount
rzikm Jul 2, 2026
e61a53f
Add impersonation tests for DnsResolver APIs
rzikm Jul 2, 2026
c35aa4b
Apply suggestions from code review
rzikm Jul 2, 2026
7ef05d1
Apply suggestions from code review
rzikm Jul 2, 2026
7e7f2f1
Surface locally-resolved address records from the QUESTION section
rzikm Jul 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions src/libraries/Common/src/Interop/Windows/Dnsapi/Interop.DnsApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Dnsapi
{
// ---- Query types we use ----
internal const ushort DNS_TYPE_A = 0x0001;
internal const ushort DNS_TYPE_NS = 0x0002;
internal const ushort DNS_TYPE_CNAME = 0x0005;
internal const ushort DNS_TYPE_SOA = 0x0006;
internal const ushort DNS_TYPE_PTR = 0x000c;
internal const ushort DNS_TYPE_MX = 0x000f;
internal const ushort DNS_TYPE_TEXT = 0x0010;
internal const ushort DNS_TYPE_AAAA = 0x001c;
internal const ushort DNS_TYPE_SRV = 0x0021;

// ---- DnsQueryEx return codes / Win32 error codes ----
internal const int DNS_REQUEST_PENDING = 9506;
internal const int ERROR_SUCCESS = 0;
internal const int DNS_INFO_NO_RECORDS = 9501;
internal const int DNS_ERROR_RCODE_FORMAT_ERROR = 9001;
internal const int DNS_ERROR_RCODE_SERVER_FAILURE = 9002;
internal const int DNS_ERROR_RCODE_NAME_ERROR = 9003;
internal const int DNS_ERROR_RCODE_NOT_IMPLEMENTED = 9004;
internal const int DNS_ERROR_RCODE_REFUSED = 9005;

// ---- DnsQueryEx options ----
internal const ulong DNS_QUERY_STANDARD = 0x00000000;

// ---- Query request versions ----
internal const uint DNS_QUERY_REQUEST_VERSION1 = 0x1;

// ---- DNS_ADDR address family marker — addresses are stored in SOCKADDR form ----
internal const ushort AF_INET = 2;
internal const ushort AF_INET6 = 23;

// ---- DnsFreeType for DnsFree ----
internal const int DnsFreeRecordList = 1;

[LibraryImport(Libraries.Dnsapi, EntryPoint = "DnsQueryEx")]
internal static unsafe partial int DnsQueryEx(
DNS_QUERY_REQUEST* pQueryRequest,
DNS_QUERY_RESULT* pQueryResults,
DNS_QUERY_CANCEL* pCancelHandle);

[LibraryImport(Libraries.Dnsapi, EntryPoint = "DnsCancelQuery")]
internal static unsafe partial int DnsCancelQuery(DNS_QUERY_CANCEL* pCancelHandle);

[LibraryImport(Libraries.Dnsapi, EntryPoint = "DnsFree")]
internal static partial void DnsFree(IntPtr pData, int freeType);
}
}
172 changes: 172 additions & 0 deletions src/libraries/Common/src/Interop/Windows/Dnsapi/Interop.DnsTypes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Dnsapi
{
// DNS_QUERY_REQUEST (v1) — Win8 / Server 2012+
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct DNS_QUERY_REQUEST
{
public uint Version;
public char* QueryName; // PCWSTR
public ushort QueryType;
public ulong QueryOptions;
public DNS_ADDR_ARRAY* pDnsServerList;
public uint InterfaceIndex;
public delegate* unmanaged[Stdcall]<nint, nint, void> pQueryCompletionCallback; // PDNS_QUERY_COMPLETION_ROUTINE
public IntPtr pQueryContext;
}

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct DNS_QUERY_RESULT
{
public uint Version;
public int QueryStatus;
public ulong QueryOptions;
public IntPtr pQueryRecords; // DNS_RECORD*
public IntPtr Reserved;
}

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct DNS_QUERY_CANCEL
{
public fixed byte Reserved[32];
}

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct DNS_ADDR
{
// SOCKET_ADDRESS-like: 32 bytes of SOCKADDR_STORAGE-ish + extras.
// DnsApi documents this struct as 64 bytes total with the first 32
// being the SOCKADDR (IPv4/IPv6 SOCKADDR fits within).
public fixed byte MaxSa[32];
public uint DnsAddrUserDword0;
public uint DnsAddrUserDword1;
public uint DnsAddrUserDword2;
public uint DnsAddrUserDword3;
public uint DnsAddrUserDword4;
public uint DnsAddrUserDword5;
public uint DnsAddrUserDword6;
public uint DnsAddrUserDword7;
}

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct DNS_ADDR_ARRAY
{
public uint MaxCount;
public uint AddrCount;
public uint Tag;
public ushort Family;
public ushort WordReserved;
public uint Flags;
public uint MatchFlag;
public uint Reserved1;
public uint Reserved2;
// followed by AddrCount entries of DNS_ADDR
// (we allocate the trailing array contiguously)
}

// ---- DNS_RECORD (variable layout: header + Data union) ----
// We declare the fixed header layout and read the data area as a byte blob,
// re-interpreting per record type. The Data union follows the header; because the
// header contains two pointers, its size (and therefore the Data offset) depends on
// the pointer width - 24 bytes on 32-bit and 32 bytes on 64-bit. Callers must use
// sizeof(DNS_RECORD_HEADER) rather than a hard-coded offset.
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct DNS_RECORD_HEADER
{
public IntPtr pNext; // DNS_RECORD*
public char* pName; // PCWSTR
public ushort wType;
public ushort wDataLength; // not always reliable; use type to interpret
public uint Flags; // contains Section in the low bits
public uint dwTtl;
public uint dwReserved;
// followed by Data union
}

// ---- Section field within DNS_RECORD.Flags ----
// The Section is the lowest 2 bits of the DW_FLAGS field.
internal const uint DNSREC_SECTION_MASK = 0x3;
internal const uint DNSREC_QUESTION = 0;
internal const uint DNSREC_ANSWER = 1;
internal const uint DNSREC_AUTHORITY = 2;
internal const uint DNSREC_ADDITIONAL = 3;

// ---- Data unions ----
[StructLayout(LayoutKind.Sequential)]
internal struct DNS_A_DATA
{
public uint IpAddress; // network byte order
}

[StructLayout(LayoutKind.Sequential)]
internal struct DNS_AAAA_DATA
{
public InlineArray16<byte> Ip6Address;
}
Comment thread
rzikm marked this conversation as resolved.

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct DNS_PTR_DATA
{
public char* pNameHost; // PCWSTR
}

// Same shape as DNS_PTR_DATA — Windows uses DNS_PTR_DATA for NS/CNAME too,
// but typed aliases keep call sites self-documenting.
#pragma warning disable CS0649 // fields populated via native marshalling
internal unsafe struct DNS_CNAME_DATA
{
public char* pNameHost;
}

internal unsafe struct DNS_NS_DATA
{
public char* pNameHost;
}
#pragma warning restore CS0649

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct DNS_MX_DATA
{
public char* pNameExchange; // PCWSTR
public ushort wPreference;
public ushort Pad;
}

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct DNS_SRV_DATA
{
public char* pNameTarget; // PCWSTR
public ushort wPriority;
public ushort wWeight;
public ushort wPort;
public ushort Pad;
}

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct DNS_TXT_DATA
{
public uint dwStringCount;
// followed by dwStringCount entries of PCWSTR
}

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct DNS_SOA_DATA
{
public char* pNamePrimaryServer; // PCWSTR
public char* pNameAdministrator; // PCWSTR
public uint dwSerialNo;
public uint dwRefresh;
public uint dwRetry;
public uint dwExpire;
public uint dwDefaultTtl;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ internal static partial class Libraries
internal const string Credui = "credui.dll";
internal const string Crypt32 = "crypt32.dll";
internal const string CryptUI = "cryptui.dll";
internal const string Dnsapi = "dnsapi.dll";
internal const string Dsrole = "dsrole.dll";
internal const string Gdi32 = "gdi32.dll";
internal const string HttpApi = "httpapi.dll";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@

namespace System.Net
{
public readonly partial struct AddressRecord
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public System.Net.IPAddress Address { get { throw null; } }
public System.TimeSpan Ttl { get { throw null; } }
}
public readonly partial struct CNameRecord
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public string CanonicalName { get { throw null; } }
public System.TimeSpan Ttl { get { throw null; } }
}
public static partial class Dns
{
public static System.IAsyncResult BeginGetHostAddresses(string hostNameOrAddress, System.AsyncCallback? requestCallback, object? state) { throw null; }
Expand Down Expand Up @@ -42,6 +56,73 @@ public static partial class Dns
public static string GetHostName() { throw null; }
[System.ObsoleteAttribute("Resolve has been deprecated. Use GetHostEntry instead.")]
public static System.Net.IPHostEntry Resolve(string hostName) { throw null; }
public static System.Net.DnsResult<System.Net.AddressRecord> ResolveAddresses(string name) { throw null; }
public static System.Net.DnsResult<System.Net.AddressRecord> ResolveAddresses(string name, System.Net.Sockets.AddressFamily addressFamily) { throw null; }
public static System.Threading.Tasks.Task<System.Net.DnsResult<System.Net.AddressRecord>> ResolveAddressesAsync(string name, System.Net.Sockets.AddressFamily addressFamily, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.Task<System.Net.DnsResult<System.Net.AddressRecord>> ResolveAddressesAsync(string name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Net.DnsResult<System.Net.CNameRecord> ResolveCName(string name) { throw null; }
public static System.Threading.Tasks.Task<System.Net.DnsResult<System.Net.CNameRecord>> ResolveCNameAsync(string name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Net.DnsResult<System.Net.MxRecord> ResolveMx(string name) { throw null; }
public static System.Threading.Tasks.Task<System.Net.DnsResult<System.Net.MxRecord>> ResolveMxAsync(string name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Net.DnsResult<System.Net.NsRecord> ResolveNs(string name) { throw null; }
public static System.Threading.Tasks.Task<System.Net.DnsResult<System.Net.NsRecord>> ResolveNsAsync(string name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Net.DnsResult<System.Net.PtrRecord> ResolvePtr(System.Net.IPAddress address) { throw null; }
public static System.Net.DnsResult<System.Net.PtrRecord> ResolvePtr(string name) { throw null; }
public static System.Threading.Tasks.Task<System.Net.DnsResult<System.Net.PtrRecord>> ResolvePtrAsync(System.Net.IPAddress address, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Threading.Tasks.Task<System.Net.DnsResult<System.Net.PtrRecord>> ResolvePtrAsync(string name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Net.DnsResult<System.Net.SrvRecord> ResolveSrv(string name) { throw null; }
public static System.Threading.Tasks.Task<System.Net.DnsResult<System.Net.SrvRecord>> ResolveSrvAsync(string name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public static System.Net.DnsResult<System.Net.TxtRecord> ResolveTxt(string name) { throw null; }
public static System.Threading.Tasks.Task<System.Net.DnsResult<System.Net.TxtRecord>> ResolveTxtAsync(string name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
public sealed partial class DnsResolver : System.IAsyncDisposable, System.IDisposable
{
public DnsResolver() { }
public DnsResolver(System.Net.DnsResolverOptions options) { }
public void Dispose() { }
public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
public System.Net.DnsResult<System.Net.AddressRecord> ResolveAddresses(string name) { throw null; }
public System.Net.DnsResult<System.Net.AddressRecord> ResolveAddresses(string name, System.Net.Sockets.AddressFamily addressFamily) { throw null; }
public System.Threading.Tasks.Task<System.Net.DnsResult<System.Net.AddressRecord>> ResolveAddressesAsync(string name, System.Net.Sockets.AddressFamily addressFamily, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public System.Threading.Tasks.Task<System.Net.DnsResult<System.Net.AddressRecord>> ResolveAddressesAsync(string name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public System.Net.DnsResult<System.Net.CNameRecord> ResolveCName(string name) { throw null; }
public System.Threading.Tasks.Task<System.Net.DnsResult<System.Net.CNameRecord>> ResolveCNameAsync(string name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public System.Net.DnsResult<System.Net.MxRecord> ResolveMx(string name) { throw null; }
public System.Threading.Tasks.Task<System.Net.DnsResult<System.Net.MxRecord>> ResolveMxAsync(string name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public System.Net.DnsResult<System.Net.NsRecord> ResolveNs(string name) { throw null; }
public System.Threading.Tasks.Task<System.Net.DnsResult<System.Net.NsRecord>> ResolveNsAsync(string name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public System.Net.DnsResult<System.Net.PtrRecord> ResolvePtr(System.Net.IPAddress address) { throw null; }
public System.Net.DnsResult<System.Net.PtrRecord> ResolvePtr(string name) { throw null; }
public System.Threading.Tasks.Task<System.Net.DnsResult<System.Net.PtrRecord>> ResolvePtrAsync(System.Net.IPAddress address, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public System.Threading.Tasks.Task<System.Net.DnsResult<System.Net.PtrRecord>> ResolvePtrAsync(string name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public System.Net.DnsResult<System.Net.SrvRecord> ResolveSrv(string name) { throw null; }
public System.Threading.Tasks.Task<System.Net.DnsResult<System.Net.SrvRecord>> ResolveSrvAsync(string name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public System.Net.DnsResult<System.Net.TxtRecord> ResolveTxt(string name) { throw null; }
public System.Threading.Tasks.Task<System.Net.DnsResult<System.Net.TxtRecord>> ResolveTxtAsync(string name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
public sealed partial class DnsResolverOptions
{
public DnsResolverOptions() { }
public System.Collections.Generic.IList<System.Net.IPEndPoint> Servers { get { throw null; } set { } }
}
[System.CLSCompliantAttribute(false)]
public enum DnsResponseCode : ushort
{
NoError = (ushort)0,
FormatError = (ushort)1,
ServerFailure = (ushort)2,
NxDomain = (ushort)3,
NotImplemented = (ushort)4,
Refused = (ushort)5,
}
public readonly partial struct DnsResult<T>
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public System.TimeSpan NegativeCacheTtl { get { throw null; } }
public System.Collections.Generic.IReadOnlyList<T> Records { get { throw null; } }
[System.CLSCompliantAttribute(false)]
public System.Net.DnsResponseCode ResponseCode { get { throw null; } }
}
public partial class IPHostEntry
{
Expand All @@ -50,4 +131,48 @@ public IPHostEntry() { }
public string[] Aliases { get { throw null; } set { } }
public string HostName { get { throw null; } set { } }
}
public readonly partial struct MxRecord
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public string Exchange { get { throw null; } }
[System.CLSCompliantAttribute(false)]
public ushort Preference { get { throw null; } }
public System.TimeSpan Ttl { get { throw null; } }
}
public readonly partial struct NsRecord
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public string Name { get { throw null; } }
public System.TimeSpan Ttl { get { throw null; } }
}
public readonly partial struct PtrRecord
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public string Name { get { throw null; } }
public System.TimeSpan Ttl { get { throw null; } }
}
public readonly partial struct SrvRecord
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public System.Collections.Generic.IReadOnlyList<System.Net.AddressRecord> Addresses { get { throw null; } }
[System.CLSCompliantAttribute(false)]
public ushort Port { get { throw null; } }
[System.CLSCompliantAttribute(false)]
public ushort Priority { get { throw null; } }
public string Target { get { throw null; } }
public System.TimeSpan Ttl { get { throw null; } }
[System.CLSCompliantAttribute(false)]
public ushort Weight { get { throw null; } }
}
public readonly partial struct TxtRecord
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public System.TimeSpan Ttl { get { throw null; } }
public System.Collections.Generic.IReadOnlyList<string> Values { get { throw null; } }
}
}
Loading
Loading