From d7ff6428004449663b1ee187f27c5ea06b29d4d9 Mon Sep 17 00:00:00 2001 From: Gabriel Mendes Date: Fri, 3 Oct 2025 15:50:55 -0300 Subject: [PATCH 1/2] Draft: RFC 6761 handling for invalid and *.localhost --- .../System.Net.NameResolution/src/System/Net/Dns.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs b/src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs index 62a389ffe4ba5b..54c01d791aee88 100644 --- a/src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs +++ b/src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs @@ -426,6 +426,19 @@ private static object GetHostEntryOrAddressesCore(string hostName, bool justAddr return resultOnFailure; } + if (hostName.Equals("invalid", StringComparison.OrdinalIgnoreCase) || + hostName.EndsWith(".invalid", StringComparison.OrdinalIgnoreCase)) + { + throw new SocketException((int)SocketError.HostNotFound); + } + + if (hostName.Equals("localhost", StringComparison.OrdinalIgnoreCase) || + hostName.EndsWith(".localhost", StringComparison.OrdinalIgnoreCase)) + { + IPAddress[] loopbacks = new IPAddress[] { IPAddress.Loopback, IPAddress.IPv6Loopback }; + return justAddresses ? (object)loopbacks : new IPHostEntry { AddressList = loopbacks, HostName = hostName, Aliases = Array.Empty() }; + } + // NameResolutionActivity may have already been set if we're being called from RunAsync. NameResolutionActivity activity = activityOrDefault ?? NameResolutionTelemetry.Log.BeforeResolution(hostName); From df915c7c05db3cefef661088944ed1d7c42e6a16 Mon Sep 17 00:00:00 2001 From: Gabriel Mendes Date: Mon, 6 Oct 2025 21:58:55 -0300 Subject: [PATCH 2/2] Draft: Refine RFC 6761 handling for invalid and *.localhost - Consolidated reserved name checks into MatchesReservedName helper - Return only supported IP versions in GetLoopbacksForAddressFamily - Added diagnostic logging for special-use domains - Adjusted handling to respect AddressFamily and OS IPv4/IPv6 support - Special-name handling now runs between BeforeResolution and AfterResolution --- .../src/System/Net/Dns.cs | 52 ++++++++++++++----- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs b/src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs index 54c01d791aee88..1f3068700f237c 100644 --- a/src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs +++ b/src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs @@ -426,25 +426,24 @@ private static object GetHostEntryOrAddressesCore(string hostName, bool justAddr return resultOnFailure; } - if (hostName.Equals("invalid", StringComparison.OrdinalIgnoreCase) || - hostName.EndsWith(".invalid", StringComparison.OrdinalIgnoreCase)) - { - throw new SocketException((int)SocketError.HostNotFound); - } - - if (hostName.Equals("localhost", StringComparison.OrdinalIgnoreCase) || - hostName.EndsWith(".localhost", StringComparison.OrdinalIgnoreCase)) - { - IPAddress[] loopbacks = new IPAddress[] { IPAddress.Loopback, IPAddress.IPv6Loopback }; - return justAddresses ? (object)loopbacks : new IPHostEntry { AddressList = loopbacks, HostName = hostName, Aliases = Array.Empty() }; - } - // NameResolutionActivity may have already been set if we're being called from RunAsync. NameResolutionActivity activity = activityOrDefault ?? NameResolutionTelemetry.Log.BeforeResolution(hostName); object result; try { + if (MatchesReservedName(hostName, "invalid")) + { + LogSpecialUse(hostName); + throw new SocketException((int)SocketError.HostNotFound); + } + + if (MatchesReservedName(hostName, "localhost")) + { + LogSpecialUse(hostName); + return GetLoopbacksForAddressFamily(addressFamily); + } + SocketError errorCode = NameResolutionPal.TryGetAddrInfo(hostName, justAddresses, addressFamily, out string? newHostName, out string[] aliases, out IPAddress[] addresses, out int nativeErrorCode); if (errorCode != SocketError.Success) @@ -715,6 +714,33 @@ private static void ValidateHostName(string hostName) } } + private static IPAddress[] GetLoopbacksForAddressFamily(AddressFamily addressFamily) + { + bool ipv4Enabled = SocketProtocolSupportPal.OSSupportsIPv4; + bool ipv6Enabled = SocketProtocolSupportPal.OSSupportsIPv6; + + return addressFamily switch + { + AddressFamily.InterNetwork when ipv4Enabled => new[] { IPAddress.Loopback }, + AddressFamily.InterNetworkV6 when ipv6Enabled => new[] { IPAddress.IPv6Loopback }, + AddressFamily.Unspecified when ipv4Enabled && ipv6Enabled => new[] { IPAddress.Loopback, IPAddress.IPv6Loopback }, + AddressFamily.Unspecified when ipv4Enabled => new[] { IPAddress.Loopback }, + AddressFamily.Unspecified when ipv6Enabled => new[] { IPAddress.IPv6Loopback }, + _ => Array.Empty(), + }; + } + + private static bool MatchesReservedName(string name, string reservedName) + { + return name.Equals(reservedName, StringComparison.OrdinalIgnoreCase) || + name.EndsWith("." + reservedName, StringComparison.OrdinalIgnoreCase); + } + + private static void LogSpecialUse(string hostName) + { + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(hostName, $"Special-use domain intercepted before calling OS resolver: {hostName}"); + } + private static bool LogFailure(object hostNameOrAddress, in NameResolutionActivity activity, Exception exception) { NameResolutionTelemetry.Log.AfterResolution(hostNameOrAddress, activity, answer: null, exception: exception);