diff --git a/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Unix.cs b/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Unix.cs index d06ce8117fc5a..8de1e2a0f4ce3 100644 --- a/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Unix.cs +++ b/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Unix.cs @@ -7,12 +7,8 @@ namespace System.Net { - internal static class SocketProtocolSupportPal + internal static partial class SocketProtocolSupportPal { - public static bool OSSupportsIPv6 { get; } = IsSupported(AddressFamily.InterNetworkV6); - public static bool OSSupportsIPv4 { get; } = IsSupported(AddressFamily.InterNetwork); - public static bool OSSupportsUnixDomainSockets { get; } = IsSupported(AddressFamily.Unix); - private static unsafe bool IsSupported(AddressFamily af) { IntPtr invalid = (IntPtr)(-1); diff --git a/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Windows.cs b/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Windows.cs index de0465a51c6c7..50e7db176e2f1 100644 --- a/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Windows.cs +++ b/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Windows.cs @@ -9,12 +9,8 @@ namespace System.Net { - internal static class SocketProtocolSupportPal + internal static partial class SocketProtocolSupportPal { - public static bool OSSupportsIPv6 { get; } = IsSupported(AddressFamily.InterNetworkV6); - public static bool OSSupportsIPv4 { get; } = IsSupported(AddressFamily.InterNetwork); - public static bool OSSupportsUnixDomainSockets { get; } = IsSupported(AddressFamily.Unix); - private static bool IsSupported(AddressFamily af) { Interop.Winsock.EnsureInitialized(); diff --git a/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.cs b/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.cs new file mode 100644 index 0000000000000..a61f47a0fa458 --- /dev/null +++ b/src/libraries/Common/src/System/Net/SocketProtocolSupportPal.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net.Sockets; + +namespace System.Net +{ + internal static partial class SocketProtocolSupportPal + { + private const string DisableIPv6AppCtxSwitch = "System.Net.DisableIPv6"; + private const string DisableIPv6EnvironmentVariable = "DOTNET_SYSTEM_NET_DISABLEIPV6"; + + public static bool OSSupportsIPv6 { get; } = IsSupported(AddressFamily.InterNetworkV6) && !IsIPv6Disabled(); + public static bool OSSupportsIPv4 { get; } = IsSupported(AddressFamily.InterNetwork); + public static bool OSSupportsUnixDomainSockets { get; } = IsSupported(AddressFamily.Unix); + + private static bool IsIPv6Disabled() + { + // First check for the AppContext switch, giving it priority over the environment variable. + if (AppContext.TryGetSwitch(DisableIPv6AppCtxSwitch, out bool disabled)) + { + return disabled; + } + + // AppContext switch wasn't used. Check the environment variable. + string? envVar = Environment.GetEnvironmentVariable(DisableIPv6EnvironmentVariable); + + if (envVar is not null) + { + return envVar == "1" || envVar.Equals("true", StringComparison.OrdinalIgnoreCase); + } + + return false; + } + } +} diff --git a/src/libraries/System.Net.NameResolution/src/System.Net.NameResolution.csproj b/src/libraries/System.Net.NameResolution/src/System.Net.NameResolution.csproj index d2d9b9b46910c..1642f1fff33dd 100644 --- a/src/libraries/System.Net.NameResolution/src/System.Net.NameResolution.csproj +++ b/src/libraries/System.Net.NameResolution/src/System.Net.NameResolution.csproj @@ -31,6 +31,8 @@ Link="Common\System\Net\IPAddressParserStatics.cs" /> + diff --git a/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Unix.cs b/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Unix.cs index 46858edea5dfa..74c62684a82c5 100644 --- a/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Unix.cs +++ b/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Unix.cs @@ -76,9 +76,11 @@ private static unsafe void ParseHostEntry(Interop.Sys.HostEntry hostEntry, bool Interop.Sys.IPAddress* addressHandle = hostEntry.IPAddressList; for (int i = 0; i < hostEntry.IPAddressCount; i++) { - if (Array.IndexOf(nativeAddresses, addressHandle[i], 0, nativeAddressCount) == -1) + Interop.Sys.IPAddress nativeAddr = addressHandle[i]; + if (Array.IndexOf(nativeAddresses, nativeAddr, 0, nativeAddressCount) == -1 && + (!nativeAddr.IsIPv6 || SocketProtocolSupportPal.OSSupportsIPv6)) // Do not include IPv6 addresses if IPV6 support is force-disabled { - nativeAddresses[nativeAddressCount++] = addressHandle[i]; + nativeAddresses[nativeAddressCount++] = nativeAddr; } } diff --git a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs index f860a2274e818..7a5d4c899e921 100644 --- a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs +++ b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs @@ -6,7 +6,7 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; - +using Microsoft.DotNet.RemoteExecutor; using Microsoft.DotNet.XUnitExtensions; using Xunit; @@ -21,9 +21,16 @@ public async Task Dns_GetHostEntryAsync_IPAddress_Ok() await TestGetHostEntryAsync(() => Dns.GetHostEntryAsync(localIPAddress)); } - [ActiveIssue("https://github.com/dotnet/runtime/issues/1488", TestPlatforms.OSX)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/51377", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotArm64Process))] // [ActiveIssue("https://github.com/dotnet/runtime/issues/27622")] + + public static bool GetHostEntryWorks = + // [ActiveIssue("https://github.com/dotnet/runtime/issues/27622")] + PlatformDetection.IsNotArmNorArm64Process && + // [ActiveIssue("https://github.com/dotnet/runtime/issues/1488", TestPlatforms.OSX)] + PlatformDetection.IsNotOSX && + // [ActiveIssue("https://github.com/dotnet/runtime/issues/51377", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] + !PlatformDetection.IsiOS && !PlatformDetection.IstvOS && !PlatformDetection.IsMacCatalyst; + + [ConditionalTheory(nameof(GetHostEntryWorks))] [InlineData("")] [InlineData(TestSettings.LocalHost)] public async Task Dns_GetHostEntry_HostString_Ok(string hostName) @@ -77,12 +84,10 @@ public async Task Dns_GetHostEntry_HostString_Ok(string hostName) } } - [ActiveIssue("https://github.com/dotnet/runtime/issues/1488", TestPlatforms.OSX)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/51377", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotArm64Process))] // [ActiveIssue("https://github.com/dotnet/runtime/issues/27622")] + [ConditionalTheory(nameof(GetHostEntryWorks))] [InlineData("")] [InlineData(TestSettings.LocalHost)] - public async Task Dns_GetHostEntryAsync_HostString_Ok(string hostName) + public async Task Dns_GetHostEntryAsync_HostString_Ok(string hostName) { if (PlatformDetection.IsSLES) { @@ -112,6 +117,44 @@ private static async Task TestGetHostEntryAsync(Func> getHostE Assert.Equal(list1, list2); } + 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) + { + RemoteExecutor.Invoke(RunTest, hostnameOuter).Dispose(); + + static void RunTest(string hostnameInner) + { + AppContext.SetSwitch("System.Net.DisableIPv6", true); + IPHostEntry entry = Dns.GetHostEntry(hostnameInner); + foreach (IPAddress address in entry.AddressList) + { + Assert.NotEqual(AddressFamily.InterNetworkV6, address.AddressFamily); + } + } + } + + [ConditionalTheory(nameof(GetHostEntry_DisableIPv6_Condition))] + [InlineData("")] + [InlineData(TestSettings.LocalHost)] + public void Dns_GetHostEntryAsync_DisableIPv6_ExcludesIPv6Addresses(string hostnameOuter) + { + RemoteExecutor.Invoke(RunTest, hostnameOuter).Dispose(); + + static async Task RunTest(string hostnameInner) + { + AppContext.SetSwitch("System.Net.DisableIPv6", true); + IPHostEntry entry = await Dns.GetHostEntryAsync(hostnameInner); + foreach (IPAddress address in entry.AddressList) + { + Assert.NotEqual(AddressFamily.InterNetworkV6, address.AddressFamily); + } + } + } + [Fact] public async Task Dns_GetHostEntry_NullStringHost_Fail() { diff --git a/src/libraries/System.Net.NameResolution/tests/PalTests/System.Net.NameResolution.Pal.Tests.csproj b/src/libraries/System.Net.NameResolution/tests/PalTests/System.Net.NameResolution.Pal.Tests.csproj index 7b2b9ee2c9fd6..3b9a81c4ee5d3 100644 --- a/src/libraries/System.Net.NameResolution/tests/PalTests/System.Net.NameResolution.Pal.Tests.csproj +++ b/src/libraries/System.Net.NameResolution/tests/PalTests/System.Net.NameResolution.Pal.Tests.csproj @@ -28,6 +28,8 @@ Link="Common\System\Net\IPEndPointStatics.cs" /> + + diff --git a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj index 35d10bb679f16..496c628956f8c 100644 --- a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj +++ b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj @@ -65,6 +65,8 @@ Link="Common\System\Net\SocketAddress.cs" /> + diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/OSSupport.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/OSSupport.cs index 3e2886baf52b7..9ed625aecd0ff 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/OSSupport.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/OSSupport.cs @@ -3,6 +3,7 @@ using System.Threading; +using Microsoft.DotNet.RemoteExecutor; using Xunit; namespace System.Net.Sockets.Tests @@ -25,6 +26,37 @@ public void SupportsIPv6_MatchesOSSupportsIPv6() #pragma warning restore } + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void DisableIPv6_OSSupportsIPv6_False() + { + RemoteInvokeOptions options = new RemoteInvokeOptions(); + options.StartInfo.EnvironmentVariables["DOTNET_SYSTEM_NET_DISABLEIPV6"] = "1"; + RemoteExecutor.Invoke(RunTest, options).Dispose(); + + static void RunTest() + { + Assert.False(Socket.OSSupportsIPv6); + } + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void DisableIPv6_SocketConstructor_CreatesIPv4Socket() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + AppContext.SetSwitch("System.Net.DisableIPv6", true); + using Socket socket1 = new Socket(SocketType.Stream, ProtocolType.Tcp); + using Socket socket2 = new Socket(SocketType.Dgram, ProtocolType.Udp); + + Assert.Equal(AddressFamily.InterNetwork, socket1.AddressFamily); + Assert.Equal(AddressFamily.InterNetwork, socket2.AddressFamily); + Assert.False(socket1.DualMode); + Assert.False(socket2.DualMode); + } + } + [Fact] public void IOControl_FIONREAD_Success() {