Skip to content

Commit 067aa16

Browse files
authored
Expose a global switch to disable IPV6 (#55012)
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.
1 parent e4cc8f9 commit 067aa16

File tree

10 files changed

+133
-20
lines changed

10 files changed

+133
-20
lines changed

src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Unix.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,8 @@
77

88
namespace System.Net
99
{
10-
internal static class SocketProtocolSupportPal
10+
internal static partial class SocketProtocolSupportPal
1111
{
12-
public static bool OSSupportsIPv6 { get; } = IsSupported(AddressFamily.InterNetworkV6);
13-
public static bool OSSupportsIPv4 { get; } = IsSupported(AddressFamily.InterNetwork);
14-
public static bool OSSupportsUnixDomainSockets { get; } = IsSupported(AddressFamily.Unix);
15-
1612
private static unsafe bool IsSupported(AddressFamily af)
1713
{
1814
IntPtr invalid = (IntPtr)(-1);

src/libraries/Common/src/System/Net/SocketProtocolSupportPal.Windows.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,8 @@
99

1010
namespace System.Net
1111
{
12-
internal static class SocketProtocolSupportPal
12+
internal static partial class SocketProtocolSupportPal
1313
{
14-
public static bool OSSupportsIPv6 { get; } = IsSupported(AddressFamily.InterNetworkV6);
15-
public static bool OSSupportsIPv4 { get; } = IsSupported(AddressFamily.InterNetwork);
16-
public static bool OSSupportsUnixDomainSockets { get; } = IsSupported(AddressFamily.Unix);
17-
1814
private static bool IsSupported(AddressFamily af)
1915
{
2016
Interop.Winsock.EnsureInitialized();
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Net.Sockets;
5+
6+
namespace System.Net
7+
{
8+
internal static partial class SocketProtocolSupportPal
9+
{
10+
private const string DisableIPv6AppCtxSwitch = "System.Net.DisableIPv6";
11+
private const string DisableIPv6EnvironmentVariable = "DOTNET_SYSTEM_NET_DISABLEIPV6";
12+
13+
public static bool OSSupportsIPv6 { get; } = IsSupported(AddressFamily.InterNetworkV6) && !IsIPv6Disabled();
14+
public static bool OSSupportsIPv4 { get; } = IsSupported(AddressFamily.InterNetwork);
15+
public static bool OSSupportsUnixDomainSockets { get; } = IsSupported(AddressFamily.Unix);
16+
17+
private static bool IsIPv6Disabled()
18+
{
19+
// First check for the AppContext switch, giving it priority over the environment variable.
20+
if (AppContext.TryGetSwitch(DisableIPv6AppCtxSwitch, out bool disabled))
21+
{
22+
return disabled;
23+
}
24+
25+
// AppContext switch wasn't used. Check the environment variable.
26+
string? envVar = Environment.GetEnvironmentVariable(DisableIPv6EnvironmentVariable);
27+
28+
if (envVar is not null)
29+
{
30+
return envVar == "1" || envVar.Equals("true", StringComparison.OrdinalIgnoreCase);
31+
}
32+
33+
return false;
34+
}
35+
}
36+
}

src/libraries/System.Net.NameResolution/src/System.Net.NameResolution.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
Link="Common\System\Net\IPAddressParserStatics.cs" />
3232
<Compile Include="$(CommonPath)System\Net\IPEndPointStatics.cs"
3333
Link="Common\System\Net\IPEndPointStatics.cs" />
34+
<Compile Include="$(CommonPath)System\Net\SocketProtocolSupportPal.cs"
35+
Link="Common\System\Net\SocketProtocolSupportPal.cs" />
3436
</ItemGroup>
3537
<ItemGroup Condition=" '$(TargetsWindows)' == 'true'">
3638
<Compile Include="System\Net\NameResolutionPal.Windows.cs" />

src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Unix.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,11 @@ private static unsafe void ParseHostEntry(Interop.Sys.HostEntry hostEntry, bool
7676
Interop.Sys.IPAddress* addressHandle = hostEntry.IPAddressList;
7777
for (int i = 0; i < hostEntry.IPAddressCount; i++)
7878
{
79-
if (Array.IndexOf(nativeAddresses, addressHandle[i], 0, nativeAddressCount) == -1)
79+
Interop.Sys.IPAddress nativeAddr = addressHandle[i];
80+
if (Array.IndexOf(nativeAddresses, nativeAddr, 0, nativeAddressCount) == -1 &&
81+
(!nativeAddr.IsIPv6 || SocketProtocolSupportPal.OSSupportsIPv6)) // Do not include IPv6 addresses if IPV6 support is force-disabled
8082
{
81-
nativeAddresses[nativeAddressCount++] = addressHandle[i];
83+
nativeAddresses[nativeAddressCount++] = nativeAddr;
8284
}
8385
}
8486

src/libraries/System.Net.NameResolution/tests/FunctionalTests/GetHostEntryTest.cs

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
using System.Net.Sockets;
77
using System.Threading;
88
using System.Threading.Tasks;
9-
9+
using Microsoft.DotNet.RemoteExecutor;
1010
using Microsoft.DotNet.XUnitExtensions;
1111
using Xunit;
1212

@@ -21,9 +21,16 @@ public async Task Dns_GetHostEntryAsync_IPAddress_Ok()
2121
await TestGetHostEntryAsync(() => Dns.GetHostEntryAsync(localIPAddress));
2222
}
2323

24-
[ActiveIssue("https://github.com/dotnet/runtime/issues/1488", TestPlatforms.OSX)]
25-
[ActiveIssue("https://github.com/dotnet/runtime/issues/51377", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)]
26-
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotArm64Process))] // [ActiveIssue("https://github.com/dotnet/runtime/issues/27622")]
24+
25+
public static bool GetHostEntryWorks =
26+
// [ActiveIssue("https://github.com/dotnet/runtime/issues/27622")]
27+
PlatformDetection.IsNotArmNorArm64Process &&
28+
// [ActiveIssue("https://github.com/dotnet/runtime/issues/1488", TestPlatforms.OSX)]
29+
PlatformDetection.IsNotOSX &&
30+
// [ActiveIssue("https://github.com/dotnet/runtime/issues/51377", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)]
31+
!PlatformDetection.IsiOS && !PlatformDetection.IstvOS && !PlatformDetection.IsMacCatalyst;
32+
33+
[ConditionalTheory(nameof(GetHostEntryWorks))]
2734
[InlineData("")]
2835
[InlineData(TestSettings.LocalHost)]
2936
public async Task Dns_GetHostEntry_HostString_Ok(string hostName)
@@ -77,12 +84,10 @@ public async Task Dns_GetHostEntry_HostString_Ok(string hostName)
7784
}
7885
}
7986

80-
[ActiveIssue("https://github.com/dotnet/runtime/issues/1488", TestPlatforms.OSX)]
81-
[ActiveIssue("https://github.com/dotnet/runtime/issues/51377", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)]
82-
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotArm64Process))] // [ActiveIssue("https://github.com/dotnet/runtime/issues/27622")]
87+
[ConditionalTheory(nameof(GetHostEntryWorks))]
8388
[InlineData("")]
8489
[InlineData(TestSettings.LocalHost)]
85-
public async Task Dns_GetHostEntryAsync_HostString_Ok(string hostName)
90+
public async Task Dns_GetHostEntryAsync_HostString_Ok(string hostName)
8691
{
8792
if (PlatformDetection.IsSLES)
8893
{
@@ -112,6 +117,44 @@ private static async Task TestGetHostEntryAsync(Func<Task<IPHostEntry>> getHostE
112117
Assert.Equal<IPAddress>(list1, list2);
113118
}
114119

120+
public static bool GetHostEntry_DisableIPv6_Condition = GetHostEntryWorks && RemoteExecutor.IsSupported;
121+
122+
[ConditionalTheory(nameof(GetHostEntry_DisableIPv6_Condition))]
123+
[InlineData("")]
124+
[InlineData(TestSettings.LocalHost)]
125+
public void Dns_GetHostEntry_DisableIPv6_ExcludesIPv6Addresses(string hostnameOuter)
126+
{
127+
RemoteExecutor.Invoke(RunTest, hostnameOuter).Dispose();
128+
129+
static void RunTest(string hostnameInner)
130+
{
131+
AppContext.SetSwitch("System.Net.DisableIPv6", true);
132+
IPHostEntry entry = Dns.GetHostEntry(hostnameInner);
133+
foreach (IPAddress address in entry.AddressList)
134+
{
135+
Assert.NotEqual(AddressFamily.InterNetworkV6, address.AddressFamily);
136+
}
137+
}
138+
}
139+
140+
[ConditionalTheory(nameof(GetHostEntry_DisableIPv6_Condition))]
141+
[InlineData("")]
142+
[InlineData(TestSettings.LocalHost)]
143+
public void Dns_GetHostEntryAsync_DisableIPv6_ExcludesIPv6Addresses(string hostnameOuter)
144+
{
145+
RemoteExecutor.Invoke(RunTest, hostnameOuter).Dispose();
146+
147+
static async Task RunTest(string hostnameInner)
148+
{
149+
AppContext.SetSwitch("System.Net.DisableIPv6", true);
150+
IPHostEntry entry = await Dns.GetHostEntryAsync(hostnameInner);
151+
foreach (IPAddress address in entry.AddressList)
152+
{
153+
Assert.NotEqual(AddressFamily.InterNetworkV6, address.AddressFamily);
154+
}
155+
}
156+
}
157+
115158
[Fact]
116159
public async Task Dns_GetHostEntry_NullStringHost_Fail()
117160
{

src/libraries/System.Net.NameResolution/tests/PalTests/System.Net.NameResolution.Pal.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
Link="Common\System\Net\IPEndPointStatics.cs" />
2929
<Compile Include="$(CommonPath)System\Net\IPAddressParserStatics.cs"
3030
Link="Common\System\Net\IPAddressParserStatics.cs" />
31+
<Compile Include="$(CommonPath)System\Net\SocketProtocolSupportPal.cs"
32+
Link="Common\System\Net\SocketProtocolSupportPal.cs" />
3133
<Compile Include="$(CommonTestPath)System\Net\Configuration.cs"
3234
Link="Common\System\Net\Configuration.cs" />
3335
<Compile Include="$(CommonTestPath)System\Net\Configuration.Http.cs"

src/libraries/System.Net.Ping/src/System.Net.Ping.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
Link="Common\System\Net\IPAddressParserStatics.cs" />
2222
<Compile Include="$(CommonPath)System\Net\SocketAddress.cs"
2323
Link="Common\System\Net\SocketAddress.cs" />
24+
<Compile Include="$(CommonPath)System\Net\SocketProtocolSupportPal.cs"
25+
Link="Common\System\Net\SocketProtocolSupportPal.cs" />
2426
<!-- Logging -->
2527
<Compile Include="$(CommonPath)System\Net\Logging\NetEventSource.Common.cs"
2628
Link="Common\System\Net\Logging\NetEventSource.Common.cs" />

src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@
6565
Link="Common\System\Net\SocketAddress.cs" />
6666
<Compile Include="$(CommonPath)System\Net\TcpValidationHelpers.cs"
6767
Link="Common\System\Net\TcpValidationHelpers.cs" />
68+
<Compile Include="$(CommonPath)System\Net\SocketProtocolSupportPal.cs"
69+
Link="Common\System\Net\SocketProtocolSupportPal.cs" />
6870
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs"
6971
Link="Common\System\Threading\Tasks\TaskToApm.cs" />
7072
<!-- System.Net.Internals -->

src/libraries/System.Net.Sockets/tests/FunctionalTests/OSSupport.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44

55
using System.Threading;
6+
using Microsoft.DotNet.RemoteExecutor;
67
using Xunit;
78

89
namespace System.Net.Sockets.Tests
@@ -25,6 +26,37 @@ public void SupportsIPv6_MatchesOSSupportsIPv6()
2526
#pragma warning restore
2627
}
2728

29+
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
30+
public void DisableIPv6_OSSupportsIPv6_False()
31+
{
32+
RemoteInvokeOptions options = new RemoteInvokeOptions();
33+
options.StartInfo.EnvironmentVariables["DOTNET_SYSTEM_NET_DISABLEIPV6"] = "1";
34+
RemoteExecutor.Invoke(RunTest, options).Dispose();
35+
36+
static void RunTest()
37+
{
38+
Assert.False(Socket.OSSupportsIPv6);
39+
}
40+
}
41+
42+
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
43+
public void DisableIPv6_SocketConstructor_CreatesIPv4Socket()
44+
{
45+
RemoteExecutor.Invoke(RunTest).Dispose();
46+
47+
static void RunTest()
48+
{
49+
AppContext.SetSwitch("System.Net.DisableIPv6", true);
50+
using Socket socket1 = new Socket(SocketType.Stream, ProtocolType.Tcp);
51+
using Socket socket2 = new Socket(SocketType.Dgram, ProtocolType.Udp);
52+
53+
Assert.Equal(AddressFamily.InterNetwork, socket1.AddressFamily);
54+
Assert.Equal(AddressFamily.InterNetwork, socket2.AddressFamily);
55+
Assert.False(socket1.DualMode);
56+
Assert.False(socket2.DualMode);
57+
}
58+
}
59+
2860
[Fact]
2961
public void IOControl_FIONREAD_Success()
3062
{

0 commit comments

Comments
 (0)