Skip to content

Commit

Permalink
Expose a global switch to disable IPV6 (#55012)
Browse files Browse the repository at this point in the history
Fixes #47583. Resolves #52287, #54807 and similar issues, without changing customer application code.

Introduces an AppContext switch `System.Net.DisableIPv6` and environment variable `DOTNET_SYSTEM_NET_DISABLEIPV6` to emulate the lack of OS-level IPv6 support. This is useful to workaround dual-stack socket issues when the OS reports support of IPv6, but some other underlying infrastructure element (typically VPN) doesn't function well with IPv6 request.

For consistency, this switch also impacts NameResolution and Ping, not only Sockets.
  • Loading branch information
antonfirsov committed Jul 12, 2021
1 parent e4cc8f9 commit 067aa16
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
36 changes: 36 additions & 0 deletions src/libraries/Common/src/System/Net/SocketProtocolSupportPal.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
Link="Common\System\Net\IPAddressParserStatics.cs" />
<Compile Include="$(CommonPath)System\Net\IPEndPointStatics.cs"
Link="Common\System\Net\IPEndPointStatics.cs" />
<Compile Include="$(CommonPath)System\Net\SocketProtocolSupportPal.cs"
Link="Common\System\Net\SocketProtocolSupportPal.cs" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetsWindows)' == 'true'">
<Compile Include="System\Net\NameResolutionPal.Windows.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

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 Microsoft.DotNet.XUnitExtensions;
using Xunit;

Expand All @@ -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)
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -112,6 +117,44 @@ private static async Task TestGetHostEntryAsync(Func<Task<IPHostEntry>> getHostE
Assert.Equal<IPAddress>(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()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
Link="Common\System\Net\IPEndPointStatics.cs" />
<Compile Include="$(CommonPath)System\Net\IPAddressParserStatics.cs"
Link="Common\System\Net\IPAddressParserStatics.cs" />
<Compile Include="$(CommonPath)System\Net\SocketProtocolSupportPal.cs"
Link="Common\System\Net\SocketProtocolSupportPal.cs" />
<Compile Include="$(CommonTestPath)System\Net\Configuration.cs"
Link="Common\System\Net\Configuration.cs" />
<Compile Include="$(CommonTestPath)System\Net\Configuration.Http.cs"
Expand Down
2 changes: 2 additions & 0 deletions src/libraries/System.Net.Ping/src/System.Net.Ping.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
Link="Common\System\Net\IPAddressParserStatics.cs" />
<Compile Include="$(CommonPath)System\Net\SocketAddress.cs"
Link="Common\System\Net\SocketAddress.cs" />
<Compile Include="$(CommonPath)System\Net\SocketProtocolSupportPal.cs"
Link="Common\System\Net\SocketProtocolSupportPal.cs" />
<!-- Logging -->
<Compile Include="$(CommonPath)System\Net\Logging\NetEventSource.Common.cs"
Link="Common\System\Net\Logging\NetEventSource.Common.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
Link="Common\System\Net\SocketAddress.cs" />
<Compile Include="$(CommonPath)System\Net\TcpValidationHelpers.cs"
Link="Common\System\Net\TcpValidationHelpers.cs" />
<Compile Include="$(CommonPath)System\Net\SocketProtocolSupportPal.cs"
Link="Common\System\Net\SocketProtocolSupportPal.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs"
Link="Common\System\Threading\Tasks\TaskToApm.cs" />
<!-- System.Net.Internals -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@


using System.Threading;
using Microsoft.DotNet.RemoteExecutor;
using Xunit;

namespace System.Net.Sockets.Tests
Expand All @@ -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()
{
Expand Down

0 comments on commit 067aa16

Please sign in to comment.