Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
56 changes: 48 additions & 8 deletions src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,10 @@ private static bool ValidateAddressFamily(ref AddressFamily addressFamily, strin
private const string Localhost = "localhost";
private const string InvalidDomain = "invalid";

// Android's default /etc/hosts maps ::1 to "ip6-localhost" instead of "localhost",
// which causes getaddrinfo("localhost", AF_INET6) to fail with EAI_NONAME.
private const string AndroidIPv6Localhost = "ip6-localhost";
Copy link
Member

Choose a reason for hiding this comment

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

I'm wondering if we should leave it as general fallback
My host file on Linux looks similar

cat /etc/hosts
127.0.0.1 localhost
# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback

ping6  localhost
ping6: localhost: Address family for hostname not supported

Alternatively, if IPv6 resolution for localhost fails, we can fail the other lookups as well, e.g. update the test to deal with it. Any thoughts on this @dotnet/ncl ?


/// <summary>
/// Checks if the given host name matches a reserved name or is a subdomain of it.
/// For example, IsReservedName("foo.localhost", "localhost") returns true.
Expand Down Expand Up @@ -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 (OperatingSystem.IsAndroid() && addressFamily == AddressFamily.InterNetworkV6 && ex.SocketErrorCode == SocketError.HostNotFound)
{
// Android's default /etc/hosts maps ::1 to "ip6-localhost" instead of "localhost".
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(Localhost, $"localhost resolution with IPv6 failed on Android, retrying with '{AndroidIPv6Localhost}'");
return GetHostEntryOrAddressesCore(AndroidIPv6Localhost, justAddresses, addressFamily);
}
}

Debug.Assert(result is not null);
Expand Down Expand Up @@ -795,8 +808,7 @@ static async Task<T> 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<T>)(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)
Expand All @@ -805,8 +817,7 @@ static async Task<T> 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<T>)(Task)Dns.GetHostEntryAsync(Localhost, addressFamily, cancellationToken)).ConfigureAwait(false);
return await GetLocalhostEntryAsync(addressFamily, cancellationToken).ConfigureAwait(false);
}

return result;
Expand All @@ -818,9 +829,9 @@ static async Task<T> CompleteAsync(Task task, string hostName, bool justAddresse
NameResolutionTelemetry.Log.AfterResolution(hostName, activity, answer: null, exception: ex);
fallbackOccurred = true;

return await ((Task<T>)(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)
{
Expand All @@ -834,6 +845,35 @@ static async Task<T> 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.
// On Android, if IPv6 resolution fails with HostNotFound, retries with "ip6-localhost"
// because Android's default /etc/hosts maps ::1 to "ip6-localhost" instead of "localhost".
static async Task<T> GetLocalhostAddressesAsync(AddressFamily family, CancellationToken cancellationToken)
{
try
{
return await ((Task<T>)(Task)Dns.GetHostAddressesAsync(Localhost, family, cancellationToken)).ConfigureAwait(false);
}
catch (SocketException ex) when (OperatingSystem.IsAndroid() && family == AddressFamily.InterNetworkV6 && ex.SocketErrorCode == SocketError.HostNotFound)
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(Localhost, $"localhost resolution with IPv6 failed on Android, retrying with '{AndroidIPv6Localhost}'");
return await ((Task<T>)(Task)Dns.GetHostAddressesAsync(AndroidIPv6Localhost, family, cancellationToken)).ConfigureAwait(false);
}
}

static async Task<T> GetLocalhostEntryAsync(AddressFamily family, CancellationToken cancellationToken)
{
try
{
return await ((Task<T>)(Task)Dns.GetHostEntryAsync(Localhost, family, cancellationToken)).ConfigureAwait(false);
}
catch (SocketException ex) when (OperatingSystem.IsAndroid() && family == AddressFamily.InterNetworkV6 && ex.SocketErrorCode == SocketError.HostNotFound)
{
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(Localhost, $"localhost resolution with IPv6 failed on Android, retrying with '{AndroidIPv6Localhost}'");
return await ((Task<T>)(Task)Dns.GetHostEntryAsync(AndroidIPv6Localhost, family, cancellationToken)).ConfigureAwait(false);
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading