Skip to content

Resolve the localhost name family to loopback on Android (RFC 6761)#128771

Draft
simonrozsival wants to merge 1 commit into
mainfrom
dev/simonrozsival/android-localhost-loopback-augmentation
Draft

Resolve the localhost name family to loopback on Android (RFC 6761)#128771
simonrozsival wants to merge 1 commit into
mainfrom
dev/simonrozsival/android-localhost-loopback-augmentation

Conversation

@simonrozsival
Copy link
Copy Markdown
Member

Note

This pull request was authored with the assistance of GitHub Copilot.

Summary

On Android, the localhost name family did not resolve to loopback as required by RFC 6761 §6.3:

  • localhost could return non-loopback addresses (interface addresses leaked into the result).
  • localhost. (fully-qualified form with a trailing dot) and *.localhost subdomains failed with SocketException because Android's getaddrinfo does not strip the trailing dot and has no /etc/hosts entry for subdomains.

This PR makes the whole localhost family (localhost, localhost., *.localhost, *.localhost.) resolve to the loopback address, consistent with desktop glibc/Windows behavior.

Changes

Native (src/native/libs/System.Native/pal_networking.c) — Android-only

SystemNative_GetHostEntryForName augments getaddrinfo results with local interface addresses (from getifaddrs) when the queried name equals gethostname(). On Android, gethostname() can itself return "localhost", which caused non-loopback interface addresses to pollute localhost resolution. A new helper ShouldAugmentWithInterfaceAddresses skips augmentation for localhost on Android only.

Managed (src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs) — all platforms

  • Normalize an exact localhost.localhost (NormalizeLocalhostName) at both the sync and async resolution cores, so the fully-qualified form resolves identically to localhost. This is platform-agnostic per the RFC; it only changes behavior on resolvers that don't strip the trailing dot themselves (Android), and is a no-op on glibc/Windows which already resolve both.
  • RFC 6761 fallback for *.localhost subdomains: try the OS resolver first; if it fails or returns no addresses, fall back to resolving plain localhost (IsLocalhostSubdomain). The fallback target localhost is not a subdomain, so the fallback cannot recurse.

Tests (tests/FunctionalTests/GetHostAddressesTest.cs, GetHostEntryTest.cs)

*.localhost subdomains now require loopback on Android (requireLoopback = !PlatformDetection.IsAppleMobile), and added/clarified localhost.-with-trailing-dot and subdomain-with-trailing-dot coverage.

Validation

  • Android emulator (API 36, Mono arm64): 107 passed / 0 failed; all localhost-family tests pass, including *_LocalhostWithTrailingDot_ReturnsLoopback (previously failing).
  • macOS host: 131 / 0 failed — no desktop regression.

@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.

@simonrozsival
Copy link
Copy Markdown
Member Author

/azp run runtime-android

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adjusts name resolution behavior so the localhost name family behaves consistently with RFC 6761 expectations on Android, and adds managed normalization/fallback logic to make localhost. and *.localhost variants resolve more reliably across platforms.

Changes:

  • Native (Android-only): prevents interface-address augmentation from polluting localhost results when Android’s gethostname() is "localhost".
  • Managed (all platforms): normalizes exact "localhost.""localhost" and falls back from *.localhost to plain localhost when the OS resolver fails or returns no addresses.
  • Tests: updates functional tests to require loopback for *.localhost on Android (while still allowing Apple mobile exceptions) and adds/clarifies trailing-dot coverage.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
src/native/libs/System.Native/pal_networking.c Adds ShouldAugmentWithInterfaceAddresses to skip Android augmentation for localhost, preventing non-loopback leakage.
src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs Normalizes localhost. and implements RFC 6761-style fallback for *.localhost when OS resolution fails/returns empty.
src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs Updates localhost-subdomain expectations (Android now requires loopback) and documents/validates localhost. behavior.
src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostAddressesTest.cs Same as above for GetHostAddresses, including trailing-dot and subdomain coverage.

On Android the "localhost" name family did not resolve to loopback as
required by RFC 6761 Section 6.3:

- "localhost" could return non-loopback addresses because interface
  addresses leaked into the result.
- "localhost." (fully-qualified form) and "*.localhost" subdomains failed
  with SocketException, since Android's getaddrinfo does not strip the
  trailing dot and has no /etc/hosts entry for subdomains.

Native (pal_networking.c, Android-only):
  SystemNative_GetHostEntryForName augments getaddrinfo results with local
  interface addresses (getifaddrs) when the queried name equals
  gethostname(). On Android gethostname() can return "localhost", polluting
  its resolution with non-loopback addresses. New helper
  ShouldAugmentWithInterfaceAddresses skips augmentation for "localhost" on
  Android only.

Managed (Dns.cs, all platforms):
  - Normalize an exact "localhost." to "localhost" at both the sync and
    async resolution cores so the fully-qualified form resolves identically
    to "localhost". This is a no-op on glibc/Windows, which already strip
    the trailing dot.
  - For "*.localhost" subdomains, try the OS resolver first and fall back to
    resolving plain "localhost" if it fails or returns no addresses. The
    fallback target is not a subdomain, so it cannot recurse.

Tests:
  Require loopback for "*.localhost" on Android, and add coverage for
  "localhost." and subdomain-with-trailing-dot forms.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/android-localhost-loopback-augmentation branch from bba7bfb to 9997901 Compare May 29, 2026 16:30
@github-actions
Copy link
Copy Markdown
Contributor

System.Net Code Review — DNS / RFC 6761 localhost resolution on Android

Protocols: DNS resolution (RFC 6761 §6.3)
Platforms: Android (native fix), all platforms (managed normalization)

Summary

Severity Finding
💡 Trivial Non-Android Linux with hostname "localhost" would still augment — pre-existing, not a realistic config

Analysis

The two-layer fix is well-designed:

  1. Native (pal_networking.c)ShouldAugmentWithInterfaceAddresses correctly prevents interface addresses from leaking into localhost resolution when Android's gethostname() returns "localhost". The #ifdef TARGET_ANDROID guard is appropriately scoped.

  2. Managed (Dns.cs)NormalizeLocalhostName strips the trailing dot from "localhost." before calling the OS resolver. This is correct per DNS conventions (trailing dot = FQDN root notation) and fixes Android's getaddrinfo("localhost.") failure without regressing glibc/Windows which already handle both forms.

RFC 6761 compliance: ✅ Section 6.3 states localhost names SHOULD resolve to loopback. Both the FQDN normalization and the subdomain fallback correctly implement this.

Security: ✅ The change tightens behavior — prevents non-loopback addresses from appearing in localhost resolution.

Edge cases: The native strcasecmp comparisons are safe (constant strings, no buffer concerns). NormalizeLocalhostName is pure/stateless — no concurrency issues.

Tests: ✅ Android exclusions properly removed; loopback is now asserted on Android.

Verdict

LGTM — Clean, well-documented fix with correct RFC references and appropriate platform scoping.

Generated by Code Review for issue #128771 · ● 4.4M ·

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.

2 participants