Skip to content

Implement DnsResolver for Windows#129845

Open
rzikm wants to merge 24 commits into
dotnet:mainfrom
rzikm:dns-resolver
Open

Implement DnsResolver for Windows#129845
rzikm wants to merge 24 commits into
dotnet:mainfrom
rzikm:dns-resolver

Conversation

@rzikm

@rzikm rzikm commented Jun 25, 2026

Copy link
Copy Markdown
Member

Replacement for #129302 (thanks, gh stack submit 😠)

Implements the API approved in #19443:

New Dns.Resolve*[Async] static methods for A/AAAA/SRV/MX/TXT/CNAME/PTR/NS records.
New DnsResolver / DnsResolverOptions for instance-based resolution with optional custom DNS servers.
Record types AddressRecord, SrvRecord, MxRecord, TxtRecord, CNameRecord, PtrRecord, NsRecord.
DnsResponseCode enum and DnsResult envelope carrying ResponseCode, Records, and NegativeCacheTtl.
Windows implementation uses DnsQueryEx. Non-Windows platforms get PlatformNotSupportedException stubs pending follow-up implementations (splitting to multiple stacked PRs for easier review).

rzikm and others added 18 commits June 3, 2026 15:17
Implements the API approved in dotnet#19443:
* New Dns.Resolve*[Async] static methods for A/AAAA/SRV/MX/TXT/CNAME/PTR/NS records.
* New DnsResolver / DnsResolverOptions for instance-based resolution with optional custom DNS servers.
* Record types AddressRecord, SrvRecord, MxRecord, TxtRecord, CNameRecord, PtrRecord, NsRecord.
* DnsResponseCode enum and DnsResult<T> envelope carrying ResponseCode, Records, and NegativeCacheTtl.

Windows implementation uses DnsQueryEx (DNS_QUERY_REQUEST v1 by default; DNS_QUERY_REQUEST3 when the caller supplies non-default ports on Windows 11 build 22000+, throwing PlatformNotSupportedException otherwise). Non-Windows platforms get PlatformNotSupportedException stubs pending follow-up implementations.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds argument-validation tests and Windows-only OuterLoop network tests for the new DnsResolver / Dns.Resolve* APIs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… port 53

DnsQueryEx only contacts custom DNS servers on the standard port 53 and
requires the sockaddr port field to be 0; any non-zero port (even 53) is
rejected with ERROR_INVALID_PARAMETER. Simplify the Windows resolver to
always use the v1 DNS_QUERY_REQUEST path, write sockaddr port 0, and throw
PlatformNotSupportedException for server endpoints requesting a non-default
port. Remove the now-unused v3 custom-server code path.

Add an in-process loopback DNS server (bound to 127.0.0.1:53, skipped when
the port is unavailable) and a comprehensive behavioral test suite covering
address/SRV/MX/TXT/CNAME/PTR/NS resolution, NXDOMAIN vs NODATA, TTLs,
SRV additional-address glue, port-0 acceptance, and in-flight cancellation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The synchronous Resolve* methods previously blocked on the async path via
GetAwaiter().GetResult(). DnsQueryEx executes synchronously when no completion
callback is supplied, so call it directly instead of going through the async
state machine. Record-list parsing is factored into shared helpers reused by
both the sync and async paths, and the loopback behavioral tests are
parameterized over both APIs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move all platform-specific logic from DnsResolver.Windows.cs and
DnsResolver.WindowsAsync.cs into a new DnsResolverPal.Windows.cs static
PAL class, mirroring the existing NameResolutionPal pattern. The
cross-platform Resolve*Core methods remain on DnsResolver and delegate to
the PAL, providing a seam for future instrumentation and telemetry.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Instrument the Resolve*Core seam in DnsResolver with the existing
NameResolutionTelemetry infrastructure (EventSource counters, the
DnsLookup Activity span, and the dns.lookup.duration metric), matching
the static Dns class.

When no diagnostics consumer is enabled the PAL task is returned
directly, keeping the common path allocation-free and preserving the
synchronous-completion invariant the sync Resolve* methods depend on.

Extend NameResolutionActivity.Stop to accept a string[] answer so the
new record types can populate the dns.answers tag.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Resolve*Core methods invoked the PAL eagerly and only then wrapped
the resulting task with telemetry. On the synchronous path the Windows
PAL executes the query while creating the task, so BeforeResolution ran
after most of the work was done and the recorded duration excluded the
query time. Defer the PAL invocation behind a Func so telemetry brackets
the entire query for both sync and async paths.

Adds a regression test asserting the recorded dns.lookup.duration covers
a delayed server response on both sync and async paths.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Conform instance DnsResolver to approved API: remove ResolvePtr(IPAddress)
  overloads (static Dns keeps them, delegating via BuildArpaName).
- Validate DnsResolverOptions.Servers setter is non-null; resolver takes a
  defensive snapshot of the configured servers.
- Fix negative-cache TTL: always extract from authority SOA and surface it on
  the success path (covers NODATA), plus merge max TTL when combining A/AAAA.
- PAL cleanups: typed GCHandle<T>, bool completion flag, span-based address
  parsing, mixed-address-family validation, checked size arithmetic, simplified
  using-based query helpers.
- Allocation-free telemetry path via TState + static delegates.
- Simplify reverse-arpa name building (plain interpolation / string.Concat).
- Add XML docs across the new public surface; reword/extend SR strings.
- Tests: assert NegativeCacheTtl for NODATA/NXDOMAIN; skip custom-server test
  when no system DNS server is configured; move IPAddress PTR tests to static API.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The approved API shape was corrected to include the IPAddress reverse-lookup
overloads on the instance resolver, so restore ResolvePtr(IPAddress) and
ResolvePtrAsync(IPAddress) on DnsResolver. The static Dns entry points now
delegate to them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace pointer-based record and sockaddr handling with safe Span<byte> and
Marshal-based reads where possible:

- TryParseAddress now reads DNS_A_DATA/DNS_AAAA_DATA via Marshal.PtrToStructure
  instead of byte* spans; DNS_AAAA_DATA uses an InlineArray field, removing the
  fixed buffer (and unsafe) from the interop struct.
- BuildAddrArray populates the DNS_ADDR_ARRAY through a Span<byte> using
  BinaryPrimitives; WriteSockAddr takes Span<byte> instead of byte*.
- Drop the unnecessary 'unsafe' modifier from PtrToString.

Also fix a pre-existing compile break in MergeAddressResults (Math.Min has no
TimeSpan overload).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Dns.DefaultResolver: use LazyInitializer.EnsureInitialized for
  thread-safe one-time initialization instead of non-atomic ??=.
- DnsResolver ctor: reject null entries in the server snapshot with an
  ArgumentException pointing at the public Servers property.
- BuildAddrArray: surface mixed-address-family ArgumentException with the
  public-facing 'Servers' parameter name instead of the internal 'servers'.
- LoopbackDnsServer: use ConcurrentDictionary for _responses to avoid
  races between test-thread mutations and listener-thread reads.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Convert the Windows network tests and the DnsQueryEx synchronous-completion
regression test to [ConditionalTheory] parameterized over bool async, using
sync/async dispatch helpers mirroring DnsResolverLoopbackTest, so the
synchronous Resolve* overloads are exercised alongside the async ones.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- WriteSockAddr now takes an IPAddress and always serializes a port-0 SOCKADDR,
  so custom-server endpoints with port 53 are normalized to 0 as DnsQueryEx
  requires (avoids serializing a non-default port). Taking IPAddress also avoids
  reading from the caller's mutable IPEndPoint.
- Clarify the ValidateServerPorts comment: 0 and 53 are accepted and normalized
  to 0; any other port is rejected.
- Fix the DNS_RECORD_HEADER comment: the Data union offset is 24 bytes on 32-bit
  and 32 bytes on 64-bit (two header pointers), so callers use Unsafe.SizeOf.
- Loopback test fixture: start the server lazily on first access instead of in
  the IClassFixture constructor, and convert the tests to ConditionalFact/
  ConditionalTheory so LoopbackDnsServer.Start()'s SkipTestException is honored
  (port 53 unavailable now skips rather than failing). Also stop assigning null
  to the server field.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 25, 2026 11:31
@rzikm rzikm changed the title Dns resolver Implement DnsResolver for Windows Jun 25, 2026
@rzikm rzikm requested a review from a team June 25, 2026 11:32
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @karelz, @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new DNS query API surface to System.Net.NameResolution and implements the Windows backend using DnsQueryEx, with functional tests (including a loopback DNS server) to validate parsing/behavior and some telemetry integration.

Changes:

  • Introduces new public DNS query APIs: Dns.Resolve* static methods, DnsResolver/DnsResolverOptions, record structs, DnsResult<T>, and DnsResponseCode.
  • Implements the Windows PAL (DnsResolverPal.Windows) using DnsQueryEx, including cancellation, TXT/SRV parsing, and negative-cache TTL extraction from SOA.
  • Adds functional tests for argument validation + real-network Windows coverage and deterministic loopback-driven Windows coverage.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/libraries/System.Net.NameResolution/tests/FunctionalTests/System.Net.NameResolution.Functional.Tests.csproj Includes new functional test sources in the test project.
src/libraries/System.Net.NameResolution/tests/FunctionalTests/LoopbackDnsServer.cs Adds an in-process loopback DNS server used by deterministic Windows tests.
src/libraries/System.Net.NameResolution/tests/FunctionalTests/DnsResponseBuilder.cs Adds a small DNS response byte builder used by the loopback server/tests.
src/libraries/System.Net.NameResolution/tests/FunctionalTests/DnsResolverTest.cs Adds functional tests covering validation + Windows network scenarios.
src/libraries/System.Net.NameResolution/tests/FunctionalTests/DnsResolverLoopbackTest.cs Adds deterministic Windows tests using the loopback DNS server (port 53).
src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionTelemetry.cs Updates telemetry tagging to support string[] answers.
src/libraries/System.Net.NameResolution/src/System/Net/DnsResult.cs Adds DnsResult<T> envelope type (ResponseCode/Records/NegativeCacheTtl).
src/libraries/System.Net.NameResolution/src/System/Net/DnsResponseCode.cs Adds DnsResponseCode enum.
src/libraries/System.Net.NameResolution/src/System/Net/DnsResolverPal.Windows.cs Implements Windows DNS querying/parsing via DnsQueryEx.
src/libraries/System.Net.NameResolution/src/System/Net/DnsResolverPal.Unsupported.cs Adds non-Windows stub PAL (currently PNSE).
src/libraries/System.Net.NameResolution/src/System/Net/DnsResolverOptions.cs Adds options type (custom server endpoints).
src/libraries/System.Net.NameResolution/src/System/Net/DnsResolver.cs Adds instance-based DNS query API with sync/async methods + telemetry.
src/libraries/System.Net.NameResolution/src/System/Net/DnsRecords.cs Adds record structs (Address/Srv/Mx/Txt/CName/Ptr/Ns).
src/libraries/System.Net.NameResolution/src/System/Net/Dns.Resolve.cs Adds new static Dns.Resolve* APIs backed by a shared resolver instance.
src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs Makes Dns partial to host the new Dns.Resolve* surface.
src/libraries/System.Net.NameResolution/src/System.Net.NameResolution.csproj Wires new source files + Windows interop compile items.
src/libraries/System.Net.NameResolution/src/Resources/Strings.resx Adds new resource strings for DNS resolver errors.
src/libraries/System.Net.NameResolution/ref/System.Net.NameResolution.cs Adds the new public API surface to the ref assembly.
src/libraries/Common/src/Interop/Windows/Interop.Libraries.cs Adds dnsapi.dll constant for P/Invoke.
src/libraries/Common/src/Interop/Windows/Dnsapi/Interop.DnsTypes.cs Adds DnsQueryEx-related structs and DNS record type layouts.
src/libraries/Common/src/Interop/Windows/Dnsapi/Interop.DnsApi.cs Adds DnsQueryEx/DnsCancelQuery/DnsFree P/Invokes + constants.

Comment thread src/libraries/System.Net.NameResolution/src/System/Net/DnsResolverPal.Windows.cs Outdated
- MergeAddressResults: pick the minimum non-zero negative-cache TTL when merging A/AAAA results instead of the first positive one.
- Use a dedicated SR string (net_dns_unsupported_address_family) for unsupported AddressFamily values in AddressFamilyToQueryType and BuildArpaName instead of the unrelated net_invalid_ip_addr message.
- LoopbackDnsServer: wrap the fire-and-forget UDP request handler in a try/catch for expected teardown exceptions to avoid unobserved task exceptions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@teo-tsirpanis teo-tsirpanis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some opportunities to use more modern interop.

Comment thread src/libraries/Common/src/Interop/Windows/Dnsapi/Interop.DnsTypes.cs Outdated
Comment thread src/libraries/Common/src/Interop/Windows/Dnsapi/Interop.DnsTypes.cs Outdated
Comment thread src/libraries/System.Net.NameResolution/src/System/Net/DnsResolverPal.Windows.cs Outdated
- Type PCWSTR struct fields as char* instead of IntPtr across the Dnsapi interop structs (QueryName, record name fields, SOA/MX/SRV/PTR/CNAME/NS data, custom server template).
- Pass the completion routine as a delegate* unmanaged[Stdcall] function pointer and mark QueryCompletionCallback with [UnmanagedCallersOnly], removing the managed DnsQueryCompletionRoutine delegate and Marshal.GetFunctionPointerForDelegate.
- Add a char* overload of PtrToString for reading the typed name pointers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 26, 2026 14:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 3 comments.

Comment thread src/libraries/System.Net.NameResolution/src/System/Net/DnsResolverPal.Windows.cs Outdated
Comment thread src/libraries/System.Net.NameResolution/src/System/Net/DnsResolverPal.Windows.cs Outdated
Comment thread src/libraries/System.Net.NameResolution/src/System/Net/DnsResolverPal.Windows.cs Outdated
Comment thread src/libraries/System.Net.NameResolution/src/System/Net/DnsResolverPal.Windows.cs Outdated
Comment thread src/libraries/System.Net.NameResolution/src/System/Net/DnsResolverPal.Windows.cs Outdated
Comment thread src/libraries/System.Net.NameResolution/src/System/Net/DnsResolverPal.Windows.cs Outdated
Comment thread src/libraries/System.Net.NameResolution/src/System/Net/DnsResolverPal.Windows.cs Outdated
Comment thread src/libraries/System.Net.NameResolution/src/System/Net/DnsResolverPal.Windows.cs Outdated
Comment thread src/libraries/System.Net.NameResolution/ref/System.Net.NameResolution.cs Outdated
Comment thread src/libraries/System.Net.NameResolution/src/System/Net/Dns.Resolve.cs Outdated
@MichalPetryka

Copy link
Copy Markdown
Contributor

Linking #129302 (comment) here so it's not forgotten.

rzikm and others added 2 commits June 27, 2026 14:32
- DNS_AAAA_DATA now uses InlineArray16<byte> instead of a custom inline-array struct.

- Dns.DefaultResolver uses the 'field' keyword instead of LazyInitializer.

- PAL signatures take IPEndPoint[] instead of IList<IPEndPoint>.

- Move record selectors onto cached static unsafe methods.

- Use sizeof(struct) instead of Unsafe.SizeOf<T>(); mark the methods that need it unsafe.

- Store typed pointers in DnsQueryAsyncState; move CT registration out of the try/catch.

- Use MinNonZero for the positive merge branch and a target-typed TaskCompletionSource.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 27, 2026 12:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 3 comments.

Comment thread src/libraries/System.Net.NameResolution/src/System/Net/DnsResolverPal.Windows.cs Outdated
Comment thread src/libraries/System.Net.NameResolution/src/System/Net/DnsResolverPal.Windows.cs Outdated
Copilot AI review requested due to automatic review settings June 27, 2026 15:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 1 comment.

@MihaZupan MihaZupan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks reasonable to me (sand sync APIs :))

Would be great if we could get someone else with knowledge around DNS to take a look


// DNS_QUERY_REQUEST3 — Win11 Build 22000+
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct DNS_QUERY_REQUEST3

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This (and DNS_QUERY_REQUEST_VERSION3, and similar) seem to be unused

Comment on lines +661 to +663
if (servers[i].AddressFamily != family)
{
throw new ArgumentException(SR.net_dns_mixed_address_families, nameof(DnsResolverOptions.Servers));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check this in the ctor?

// default port") or 53 (the port DnsQueryEx will actually use) and normalize both
// to 0 when building the native server list (see WriteSockAddr). Any other port
// cannot be honored on Windows and is rejected here.
private static void ValidateServerPorts(IPEndPoint[] servers)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check this in the ctor?

//
// These tests cover the record-parsing and response-handling behavior that the
// OuterLoop tests in DnsResolverTest.cs cannot exercise deterministically.
[OuterLoop("Binds the loopback DNS port 53 and issues real DnsQueryEx calls.")]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're skipping the test rather than failing on a port conflict, is there a reason this couldn't be innerloop?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants