Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Narrow AddressFamily passed to getaddrinfo when IPv6 is unsupported #112642

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
47 changes: 47 additions & 0 deletions src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.Versioning;
using System.Diagnostics.CodeAnalysis;

namespace System.Net
{
Expand Down Expand Up @@ -386,10 +387,44 @@ private static IPHostEntry GetHostEntryCore(string hostName, AddressFamily addre
private static IPAddress[] GetHostAddressesCore(string hostName, AddressFamily addressFamily, NameResolutionActivity? activityOrDefault = default) =>
(IPAddress[])GetHostEntryOrAddressesCore(hostName, justAddresses: true, addressFamily, activityOrDefault);

private static bool ValidateAddressFamily(ref AddressFamily addressFamily, string hostName, bool justAddresses, [NotNullWhen(false)] out object? resultOnFailure)
{
if (!SocketProtocolSupportPal.OSSupportsIPv6)
Copy link
Member

Choose a reason for hiding this comment

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

should this be symmetric e.g. should we apply same logic to IPv4?

Copy link
Member Author

@antonfirsov antonfirsov Feb 18, 2025

Choose a reason for hiding this comment

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

Is OSSupportsIPv4 == false a practical real-life user scenario? I would be hesitant to add logic to handle it otherwise. It would be virtually untestable; we don't have a System.Net.DisableIPv4 switch, and I don't think it would make much sense to add one.

{
if (addressFamily == AddressFamily.InterNetworkV6)
{
// The caller requested IPv6, but the OS doesn't support it; return an empty result.
IPAddress[] addresses = Array.Empty<IPAddress>();
resultOnFailure = justAddresses ? (object)
addresses :
new IPHostEntry
{
AddressList = addresses,
HostName = hostName,
Aliases = Array.Empty<string>()
};
return false;
}
else if (addressFamily == AddressFamily.Unspecified)
{
// Narrow the query to IPv4.
addressFamily = AddressFamily.InterNetwork;
}
}

resultOnFailure = null;
return true;
}

private static object GetHostEntryOrAddressesCore(string hostName, bool justAddresses, AddressFamily addressFamily, NameResolutionActivity? activityOrDefault = default)
{
ValidateHostName(hostName);

if (!ValidateAddressFamily(ref addressFamily, hostName, justAddresses, out object? resultOnFailure))
{
return resultOnFailure;
}

// NameResolutionActivity may have already been set if we're being called from RunAsync.
NameResolutionActivity activity = activityOrDefault ?? NameResolutionTelemetry.Log.BeforeResolution(hostName);

Expand Down Expand Up @@ -463,6 +498,11 @@ private static object GetHostEntryOrAddressesCore(IPAddress address, bool justAd

NameResolutionTelemetry.Log.AfterResolution(address, activity, answer: name);

if (!ValidateAddressFamily(ref addressFamily, name, justAddresses, out object? resultOnFailure))
{
return resultOnFailure;
}

// Do the forward lookup to get the IPs for that host name
activity = NameResolutionTelemetry.Log.BeforeResolution(name);

Expand Down Expand Up @@ -518,6 +558,13 @@ private static Task GetHostEntryOrAddressesCoreAsync(string hostName, bool justR
Task.FromCanceled<IPHostEntry>(cancellationToken);
}

if (!ValidateAddressFamily(ref family, hostName, justAddresses, out object? resultOnFailure))
{
return justAddresses ? (Task)
Task.FromResult((IPAddress[])resultOnFailure) :
Task.FromResult((IPHostEntry)resultOnFailure);
}

object asyncState;

// See if it's an IP Address.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

using Microsoft.DotNet.RemoteExecutor;
using Xunit;

namespace System.Net.NameResolution.Tests
Expand Down Expand Up @@ -171,6 +171,39 @@ public async Task DnsGetHostAddresses_PreCancelledToken_Throws()
OperationCanceledException oce = await Assert.ThrowsAnyAsync<OperationCanceledException>(() => Dns.GetHostAddressesAsync(TestSettings.LocalHost, cts.Token));
Assert.Equal(cts.Token, oce.CancellationToken);
}

[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[InlineData(false)]
[InlineData(true)]
public void GetHostAddresses_DisableIPv6_ExcludesIPv6Addresses(bool useAsyncOuter)
{
RemoteExecutor.Invoke(RunTest, useAsyncOuter.ToString()).Dispose();

static async Task RunTest(string useAsync)
{
AppContext.SetSwitch("System.Net.DisableIPv6", true);
IPAddress[] addresses =
bool.Parse(useAsync) ? await Dns.GetHostAddressesAsync(TestSettings.LocalHost) :
Dns.GetHostAddresses(TestSettings.LocalHost);
Assert.All(addresses, address => Assert.Equal(AddressFamily.InterNetwork, address.AddressFamily));
}
}

[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[InlineData(false)]
[InlineData(true)]
public void GetHostAddresses_DisableIPv6_AddressFamilyInterNetworkV6_ReturnsEmpty(bool useAsyncOuter)
{
RemoteExecutor.Invoke(RunTest, useAsyncOuter.ToString()).Dispose();
static async Task RunTest(string useAsync)
{
AppContext.SetSwitch("System.Net.DisableIPv6", true);
IPAddress[] addresses =
bool.Parse(useAsync) ? await Dns.GetHostAddressesAsync(TestSettings.LocalHost, AddressFamily.InterNetworkV6) :
Dns.GetHostAddresses(TestSettings.LocalHost, AddressFamily.InterNetworkV6);
Assert.Empty(addresses);
}
}
}

// Cancellation tests are sequential to reduce the chance of timing issues.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -108,38 +109,41 @@ private static async Task TestGetHostEntryAsync(Func<Task<IPHostEntry>> getHostE
public static bool GetHostEntry_DisableIPv6_Condition = GetHostEntryWorks && RemoteExecutor.IsSupported;

[ConditionalTheory(nameof(GetHostEntry_DisableIPv6_Condition))]
[InlineData("")]
[InlineData(TestSettings.LocalHost)]
public void Dns_GetHostEntry_DisableIPv6_ExcludesIPv6Addresses(string hostnameOuter)
[InlineData("", false)]
[InlineData("", true)]
[InlineData(TestSettings.LocalHost, false)]
[InlineData(TestSettings.LocalHost, true)]
public void GetHostEntry_DisableIPv6_ExcludesIPv6Addresses(string hostnameOuter, bool useAsyncOuter)
{
RemoteExecutor.Invoke(RunTest, hostnameOuter).Dispose();
string expectedHostName = Dns.GetHostEntry(hostnameOuter).HostName;
RemoteExecutor.Invoke(RunTest, hostnameOuter, expectedHostName, useAsyncOuter.ToString()).Dispose();

static void RunTest(string hostnameInner)
static async Task RunTest(string hostnameInner, string expectedHostName, string useAsync)
{
AppContext.SetSwitch("System.Net.DisableIPv6", true);
IPHostEntry entry = Dns.GetHostEntry(hostnameInner);
foreach (IPAddress address in entry.AddressList)
{
Assert.NotEqual(AddressFamily.InterNetworkV6, address.AddressFamily);
}

IPHostEntry entry = bool.Parse(useAsync) ?
await Dns.GetHostEntryAsync(hostnameInner) :
Dns.GetHostEntry(hostnameInner);

Assert.Equal(entry.HostName, expectedHostName);
Assert.All(entry.AddressList, address => Assert.Equal(AddressFamily.InterNetwork, address.AddressFamily));
}
}

[ConditionalTheory(nameof(GetHostEntry_DisableIPv6_Condition))]
[InlineData("")]
[InlineData(TestSettings.LocalHost)]
public void Dns_GetHostEntryAsync_DisableIPv6_ExcludesIPv6Addresses(string hostnameOuter)
[InlineData(false)]
[InlineData(true)]
public void GetHostEntry_DisableIPv6_AddressFamilyInterNetworkV6_ReturnsEmpty(bool useAsyncOuter)
{
RemoteExecutor.Invoke(RunTest, hostnameOuter).Dispose();

static async Task RunTest(string hostnameInner)
RemoteExecutor.Invoke(RunTest, useAsyncOuter.ToString()).Dispose();
static async Task RunTest(string useAsync)
{
AppContext.SetSwitch("System.Net.DisableIPv6", true);
IPHostEntry entry = await Dns.GetHostEntryAsync(hostnameInner);
foreach (IPAddress address in entry.AddressList)
{
Assert.NotEqual(AddressFamily.InterNetworkV6, address.AddressFamily);
}
IPHostEntry entry = bool.Parse(useAsync) ?
await Dns.GetHostEntryAsync(TestSettings.LocalHost, AddressFamily.InterNetworkV6) :
Dns.GetHostEntry(TestSettings.LocalHost, AddressFamily.InterNetworkV6);
Assert.Empty(entry.AddressList);
}
}

Expand Down
Loading