Implement DnsResolver for Linux#129846
Draft
rzikm wants to merge 19 commits into
Draft
Conversation
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>
Implements the DnsResolver PAL for the unix TFM as a managed stub resolver. It builds and parses DNS wire messages and talks to the configured servers over UDP (with TCP fallback on truncation) using System.Net.Sockets. When no servers are configured, the system servers from /etc/resolv.conf are used, falling back to loopback. - Add internal wire primitives: message header, reader, writer, encoded-name (with IDN/ACE support), and per-type record parsers. - Add DnsResolverPal.Managed.cs query engine with shared sync/async paths; the sync path uses blocking socket calls and returns a completed Task, preserving the task.IsCompleted invariant. - Add ResolvConf.cs nameserver discovery plus parser unit tests. - Wire the new sources into the unix ItemGroup; reference the System.Net.Sockets reference assembly to break the project cycle (Sockets references NameResolution); the implementation resolves from the shared framework at runtime. - Generalize the loopback tests to run on Linux by binding an ephemeral port on non-Windows. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
|
Tagging subscribers to this area: @karelz, @dotnet/ncl |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds a Unix/Linux implementation of the new DnsResolver / Dns.Resolve* DNS query APIs in System.Net.NameResolution, including a managed stub resolver (UDP with TCP fallback) and /etc/resolv.conf server discovery, plus new functional and PAL tests to validate parsing and resolver behavior.
Changes:
- Add managed DNS resolver PAL for Unix: build DNS wire-format queries, send over UDP with TCP fallback, parse responses into typed record results (incl. negative-cache TTL).
- Add
/etc/resolv.confparsing to discover system DNS servers when no custom servers are configured. - Add new test infrastructure (loopback DNS server + response builder) and functional/PAL tests for deterministic resolver behavior.
Reviewed changes
Copilot reviewed 31 out of 31 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/libraries/System.Net.NameResolution/tests/PalTests/System.Net.NameResolution.Pal.Tests.csproj | Includes ResolvConf production source + new PAL tests on Unix. |
| src/libraries/System.Net.NameResolution/tests/PalTests/ResolvConfTests.cs | Unit tests for parsing nameserver directives and edge cases. |
| src/libraries/System.Net.NameResolution/tests/FunctionalTests/System.Net.NameResolution.Functional.Tests.csproj | Adds new functional test sources for resolver + loopback server. |
| src/libraries/System.Net.NameResolution/tests/FunctionalTests/LoopbackDnsServer.cs | Minimal in-process DNS server used by resolver loopback tests. |
| src/libraries/System.Net.NameResolution/tests/FunctionalTests/DnsResponseBuilder.cs | Helper for constructing raw DNS responses for deterministic tests. |
| src/libraries/System.Net.NameResolution/tests/FunctionalTests/DnsResolverTest.cs | API argument validation + (Windows-only) network coverage for new APIs. |
| src/libraries/System.Net.NameResolution/tests/FunctionalTests/DnsResolverLoopbackTest.cs | Deterministic resolver behavior tests using the loopback DNS server (UDP/TCP, parsing, TTL, metrics). |
| src/libraries/System.Net.NameResolution/src/System/Net/ResolvConf.cs | Reads/parses /etc/resolv.conf to get system DNS servers. |
| src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionTelemetry.cs | Extends telemetry answer-shaping to support string[] answers. |
| src/libraries/System.Net.NameResolution/src/System/Net/DnsWireEnums.cs | Adds internal enums for DNS wire constants (TYPE/CLASS/OPCODE/flags). |
| src/libraries/System.Net.NameResolution/src/System/Net/DnsResult.cs | Adds DnsResult<T> envelope (ResponseCode, Records, NegativeCacheTtl). |
| src/libraries/System.Net.NameResolution/src/System/Net/DnsResponseCode.cs | Adds DnsResponseCode public enum. |
| src/libraries/System.Net.NameResolution/src/System/Net/DnsResolverPal.Windows.cs | Windows PAL implementation (per request, Windows parts not reviewed here). |
| src/libraries/System.Net.NameResolution/src/System/Net/DnsResolverPal.Unsupported.cs | PNSE stubs for unsupported targets (e.g., WASI). |
| src/libraries/System.Net.NameResolution/src/System/Net/DnsResolverPal.Managed.cs | Unix/Linux managed resolver: query engine, UDP/TCP fallback, parsing. |
| src/libraries/System.Net.NameResolution/src/System/Net/DnsResolverOptions.cs | Public options type (Servers list). |
| src/libraries/System.Net.NameResolution/src/System/Net/DnsResolver.cs | Public resolver implementation + telemetry integration + PTR arpa-name builder. |
| src/libraries/System.Net.NameResolution/src/System/Net/DnsRecords.cs | Public record structs (Address/Srv/Mx/Txt/CName/Ptr/Ns). |
| src/libraries/System.Net.NameResolution/src/System/Net/DnsRecordParsing.cs | Typed RDATA parsing helpers over decoded records. |
| src/libraries/System.Net.NameResolution/src/System/Net/DnsMessageWriter.cs | Writes DNS query messages (header + questions). |
| src/libraries/System.Net.NameResolution/src/System/Net/DnsMessageReader.cs | Reads DNS header/questions/records from response buffers. |
| src/libraries/System.Net.NameResolution/src/System/Net/DnsMessageHeader.cs | Encodes/decodes DNS header flags + counters. |
| src/libraries/System.Net.NameResolution/src/System/Net/DnsEncodedName.cs | DNS name encoding/decoding with compression-pointer support and IDN handling. |
| src/libraries/System.Net.NameResolution/src/System/Net/Dns.Resolve.cs | Adds Dns.Resolve* static APIs backed by a shared DnsResolver. |
| src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs | Makes Dns partial to enable Dns.Resolve.cs partial definition. |
| src/libraries/System.Net.NameResolution/src/System.Net.NameResolution.csproj | Wires in new sources, Unix managed resolver sources, and Unix-only project refs needed by implementation. |
| src/libraries/System.Net.NameResolution/src/Resources/Strings.resx | Adds SR strings for new exceptions and validation messages. |
| src/libraries/System.Net.NameResolution/ref/System.Net.NameResolution.cs | Adds new public surface area to the reference assembly contract. |
| src/libraries/Common/src/Interop/Windows/Interop.Libraries.cs | Windows interop library constant for dnsapi (per request, Windows parts not reviewed here). |
| src/libraries/Common/src/Interop/Windows/Dnsapi/Interop.DnsTypes.cs | Windows Dnsapi structs/constants (per request, Windows parts not reviewed here). |
| src/libraries/Common/src/Interop/Windows/Dnsapi/Interop.DnsApi.cs | Windows Dnsapi P/Invokes/constants (per request, Windows parts not reviewed here). |
Comment on lines
+726
to
+732
| IAsyncResult ar = socket.BeginConnect(server, null, null); | ||
| if (!ar.AsyncWaitHandle.WaitOne(s_queryTimeout)) | ||
| { | ||
| socket.Close(); | ||
| throw new SocketException((int)SocketError.TimedOut); | ||
| } | ||
| socket.EndConnect(ar); |
Comment on lines
+414
to
+419
| DnsResponseCode chosenRc = a.ResponseCode == DnsResponseCode.NxDomain || b.ResponseCode == DnsResponseCode.NxDomain | ||
| ? DnsResponseCode.NxDomain | ||
| : (a.ResponseCode != DnsResponseCode.NoError ? a.ResponseCode : b.ResponseCode); | ||
| TimeSpan negTtl = a.NegativeCacheTtl > TimeSpan.Zero ? a.NegativeCacheTtl : b.NegativeCacheTtl; | ||
| return new DnsResult<AddressRecord>(chosenRc, null, negTtl); | ||
| } |
Comment on lines
+45
to
+62
| 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.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.Net.Sockets.AddressFamily addressFamily, 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.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.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 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.PtrRecord> ResolvePtr(string name) { throw null; } | ||
| public static System.Net.DnsResult<System.Net.PtrRecord> ResolvePtr(System.Net.IPAddress address) { 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.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.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; } |
This was referenced Jun 25, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow up on #129845
When reviewing, ignore all Windows parts (they are part of the base PR)