Skip to content

Conversation

gbr-mendes
Copy link

This is a draft PR for issue #118569.

Motivation

RFC 6761 defines special-use domain names:

  • invalid and *.invalid must always return NXDOMAIN
  • localhost and *.localhost must always resolve to the loopback address

Currently, .NET relies fully on the OS resolver, which may allow invalid to resolve if defined in /etc/hosts or DNS, and may not handle *.localhost consistently across platforms.

Proposed approach

  • Intercept invalid and *.invalid before OS resolution → throw SocketException with HostNotFound (simulate NXDOMAIN).
  • Intercept localhost and *.localhost before OS resolution → return IPAddress.Loopback and IPAddress.IPv6Loopback.
  • Otherwise, fallback to the platform resolver as today.

Status

  • Draft PR to validate the idea/approach with maintainers.
  • No tests included yet.
  • Once approach is confirmed, I’ll proceed with the real implementation, unit tests and CI validation.

@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Oct 3, 2025
Copy link
Contributor

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Copy link
Member

@rzikm rzikm left a comment

Choose a reason for hiding this comment

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

I think the approach is fine, but needs a bit of improvement and testing.

Comment on lines 429 to 430
if (hostName.Equals("invalid", StringComparison.OrdinalIgnoreCase) ||
hostName.EndsWith(".invalid", StringComparison.OrdinalIgnoreCase))
Copy link
Member

Choose a reason for hiding this comment

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

I think we can put this to a helper method and use fewer comparison to check:

        static bool MatchesReservedName(string name, string reservedName)
        {
            // check if equal to reserved name or is a subdomain of it
            return name.Equals(reservedName, StringComparison.OrdinalIgnoreCase) ||
                (name.Length > reservedName.Length && name[name.Length - reservedName.Length - 1] == '.');
        }

Copy link
Member

@filipnavara filipnavara Oct 6, 2025

Choose a reason for hiding this comment

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

@rzikm That doesn't sound like a correct comparison. It only checks for the . character and not the rest of the string, so it would also match foo.xxvalid with reservedName == invalid.

Copy link
Member

Choose a reason for hiding this comment

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

You could probably do something like name.EndsWith(reservedName, StringComparison.OrdinalIgnoreCase) && (name.Length == reservedName.Length || (name.Length > reservedName.Length && name[name.Length - reservedName.Length - 1] == '.')). It's not very readable though...

Copy link
Member

Choose a reason for hiding this comment

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

right, I messed it, it was supposed to be as you wrote.

As for readability, that's what the descriptive name of the helper function is for :)

Copy link
Author

Choose a reason for hiding this comment

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

Thanks, everyone. I'll continue with the adjustments. Considering the example provided by @filipnavara , I'll write some tests to ensure the OS resolver is called when expected.

Copy link
Member

@MihaZupan MihaZupan Oct 6, 2025

Choose a reason for hiding this comment

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

Perf-wise, you're likely looking at sub-nanosecond differences between the two (the JIT will inline direct comparisons here, there's no call overhead), so I'd prefer whatever option we feel is more readable. I'd lean towards the pattern used in the PR.

Comment on lines 435 to 440
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<string>() };
}
Copy link
Member

Choose a reason for hiding this comment

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

This needs to handle cases where IPv6 is disabled. We should not return IPv6Loopback in those cases. Symmetrically for IPv4.

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 also wondering if one could pre-allocate immutable answer is this should probably not change through the process life.

return resultOnFailure;
}

if (hostName.Equals("invalid", StringComparison.OrdinalIgnoreCase) ||
Copy link
Member

Choose a reason for hiding this comment

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

I think the whole new block should be inside the resolution activity BeforeResolution and AfterResolution. Otherwise, there won't be any diagnostics about this special case.
And addressFamily should be taken into consideration, current code return both IP v4 and v6 regardless of this value.

Copy link
Member

Choose a reason for hiding this comment

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

may be worth of diag log as well.

- 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
@gbr-mendes
Copy link
Author

@rzikm based on the discussion I implemented the changes to consolidate reserved name checks, filter supported loopbacks by AddressFamily, and add diagnostic logging for special-use domains.

During testing, I tried disabling IPv6 on my host, and it was still returned by GetLoopbacksForAddressFamily. My concern is that using SocketProtocolSupportPal.OSSupportsIPv6 directly might not reflect the actual interface capabilities. Perhaps it would be safer to check the network interfaces themselves via NetworkInterface.Supports(NetworkInterfaceComponent), as described here:
https://learn.microsoft.com/en-us/dotnet/api/system.net.networkinformation.networkinterface.supports?view=net-9.0

I’d like some guidance on whether this approach makes sense, or if the current OS-level check is sufficient.

@gbr-mendes
Copy link
Author

@gbr-mendes please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@dotnet-policy-service agree [company="{your company}"]

Options:

  • (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
@dotnet-policy-service agree
  • (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
@dotnet-policy-service agree company="Microsoft"

Contributor License Agreement

@dotnet-policy-service agree

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Net community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants