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 67c29760dea20b..fdac620bbf15df 100644 --- a/src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs +++ b/src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs @@ -419,6 +419,10 @@ private static bool ValidateAddressFamily(ref AddressFamily addressFamily, strin private const string Localhost = "localhost"; private const string InvalidDomain = "invalid"; + // Some systems (e.g. Android, some Linux distros) map ::1 to "ip6-localhost" instead of + // "localhost" in /etc/hosts, which causes getaddrinfo("localhost", AF_INET6) to fail with EAI_NONAME. + private const string IPv6Localhost = "ip6-localhost"; + /// /// Checks if the given host name matches a reserved name or is a subdomain of it. /// For example, IsReservedName("foo.localhost", "localhost") returns true. @@ -550,7 +554,16 @@ private static object GetHostEntryOrAddressesCore(string hostName, bool justAddr if (fallbackToLocalhost) { - return GetHostEntryOrAddressesCore(Localhost, justAddresses, addressFamily); + try + { + return GetHostEntryOrAddressesCore(Localhost, justAddresses, addressFamily); + } + catch (SocketException ex) when (addressFamily == AddressFamily.InterNetworkV6 && ex.SocketErrorCode == SocketError.HostNotFound) + { + // Some systems map ::1 to "ip6-localhost" instead of "localhost" in /etc/hosts. + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(Localhost, $"localhost IPv6 resolution failed, retrying with '{IPv6Localhost}'"); + return GetHostEntryOrAddressesCore(IPv6Localhost, justAddresses, addressFamily); + } } Debug.Assert(result is not null); @@ -795,8 +808,7 @@ static async Task CompleteAsync(Task task, string hostName, bool justAddresse NameResolutionTelemetry.Log.AfterResolution(hostName, activity, answer: result, exception: null); fallbackOccurred = true; - // result is IPAddress[] so justAddresses is guaranteed true here. - return await ((Task)(Task)Dns.GetHostAddressesAsync(Localhost, addressFamily, cancellationToken)).ConfigureAwait(false); + return await GetLocalhostAddressesAsync(addressFamily, cancellationToken).ConfigureAwait(false); } if (isLocalhostSubdomain && result is IPHostEntry entry && entry.AddressList.Length == 0) @@ -805,8 +817,7 @@ static async Task CompleteAsync(Task task, string hostName, bool justAddresse NameResolutionTelemetry.Log.AfterResolution(hostName, activity, answer: result, exception: null); fallbackOccurred = true; - // result is IPHostEntry so justAddresses is guaranteed false here. - return await ((Task)(Task)Dns.GetHostEntryAsync(Localhost, addressFamily, cancellationToken)).ConfigureAwait(false); + return await GetLocalhostEntryAsync(addressFamily, cancellationToken).ConfigureAwait(false); } return result; @@ -818,9 +829,9 @@ static async Task CompleteAsync(Task task, string hostName, bool justAddresse NameResolutionTelemetry.Log.AfterResolution(hostName, activity, answer: null, exception: ex); fallbackOccurred = true; - return await ((Task)(justAddresses - ? (Task)Dns.GetHostAddressesAsync(Localhost, addressFamily, cancellationToken) - : Dns.GetHostEntryAsync(Localhost, addressFamily, cancellationToken))).ConfigureAwait(false); + return justAddresses + ? await GetLocalhostAddressesAsync(addressFamily, cancellationToken).ConfigureAwait(false) + : await GetLocalhostEntryAsync(addressFamily, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -834,6 +845,35 @@ static async Task CompleteAsync(Task task, string hostName, bool justAddresse NameResolutionTelemetry.Log.AfterResolution(hostName, activity, answer: result, exception: exception); } } + + // Resolves "localhost" with the given address family, returning addresses. + // If IPv6 resolution fails with HostNotFound, retries with "ip6-localhost" + // because some systems map ::1 to "ip6-localhost" instead of "localhost" in /etc/hosts. + static async Task GetLocalhostAddressesAsync(AddressFamily family, CancellationToken cancellationToken) + { + try + { + return await ((Task)(Task)Dns.GetHostAddressesAsync(Localhost, family, cancellationToken)).ConfigureAwait(false); + } + catch (SocketException ex) when (family == AddressFamily.InterNetworkV6 && ex.SocketErrorCode == SocketError.HostNotFound) + { + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(Localhost, $"localhost IPv6 resolution failed, retrying with '{IPv6Localhost}'"); + return await ((Task)(Task)Dns.GetHostAddressesAsync(IPv6Localhost, family, cancellationToken)).ConfigureAwait(false); + } + } + + static async Task GetLocalhostEntryAsync(AddressFamily family, CancellationToken cancellationToken) + { + try + { + return await ((Task)(Task)Dns.GetHostEntryAsync(Localhost, family, cancellationToken)).ConfigureAwait(false); + } + catch (SocketException ex) when (family == AddressFamily.InterNetworkV6 && ex.SocketErrorCode == SocketError.HostNotFound) + { + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(Localhost, $"localhost IPv6 resolution failed, retrying with '{IPv6Localhost}'"); + return await ((Task)(Task)Dns.GetHostEntryAsync(IPv6Localhost, family, cancellationToken)).ConfigureAwait(false); + } + } } } diff --git a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostAddressesTest.cs b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostAddressesTest.cs index e98abe1ab53e2f..b91219cf6fed19 100644 --- a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostAddressesTest.cs +++ b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostAddressesTest.cs @@ -249,7 +249,6 @@ public async Task DnsGetHostAddresses_LocalhostSubdomain_ReturnsLoopback(string [Theory] [InlineData(AddressFamily.InterNetwork)] [InlineData(AddressFamily.InterNetworkV6)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/124751", TestPlatforms.Android)] public async Task DnsGetHostAddresses_LocalhostSubdomain_RespectsAddressFamily(AddressFamily addressFamily) { // Skip IPv6 test if OS doesn't support it. diff --git a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs index 58c8015a50bd70..e3cb7066eadc17 100644 --- a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs +++ b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs @@ -414,7 +414,6 @@ public async Task DnsGetHostEntry_LocalhostWithTrailingDot_ReturnsLoopback() [Theory] [InlineData(AddressFamily.InterNetwork)] [InlineData(AddressFamily.InterNetworkV6)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/124751", TestPlatforms.Android)] public async Task DnsGetHostEntry_LocalhostSubdomain_RespectsAddressFamily(AddressFamily addressFamily) { // Skip IPv6 test if OS doesn't support it.