diff --git a/src/libraries/Common/src/System/AppContextSwitchHelper.cs b/src/libraries/Common/src/System/AppContextSwitchHelper.cs
new file mode 100644
index 00000000000000..e2607ca66eb3e8
--- /dev/null
+++ b/src/libraries/Common/src/System/AppContextSwitchHelper.cs
@@ -0,0 +1,35 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Globalization;
+
+namespace System
+{
+ internal static class AppContextSwitchHelper
+ {
+ internal static bool GetBooleanConfig(string switchName, bool defaultValue = false) =>
+ AppContext.TryGetSwitch(switchName, out bool value) ? value : defaultValue;
+
+ internal static bool GetBooleanConfig(string switchName, string envVariable, bool defaultValue = false)
+ {
+ if (AppContext.TryGetSwitch(switchName, out bool value))
+ {
+ return value;
+ }
+
+ if (Environment.GetEnvironmentVariable(envVariable) is string str)
+ {
+ if (str == "1" || string.Equals(str, bool.TrueString, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ if (str == "0" || string.Equals(str, bool.FalseString, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+ }
+
+ return defaultValue;
+ }
+ }
+}
diff --git a/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj b/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj
index f09a5d14dd08b0..546996f4b97c33 100644
--- a/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj
+++ b/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj
@@ -25,6 +25,7 @@
+
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs
index 0358742914485d..e609a69f20f582 100644
--- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs
+++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs
@@ -4,6 +4,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Net.Sockets;
+using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Quic;
using static Microsoft.Quic.MsQuic;
@@ -75,7 +76,7 @@ private MsQuicApi(QUIC_API_TABLE* apiTable)
static MsQuicApi()
{
bool loaded = false;
- IntPtr msQuicHandle;
+ IntPtr msQuicHandle = IntPtr.Zero;
Version = default;
// MsQuic is using DualMode sockets and that will fail even for IPv4 if AF_INET6 is not available.
@@ -94,7 +95,7 @@ static MsQuicApi()
// Windows ships msquic in the assembly directory next to System.Net.Quic, so load that.
// For single-file deployments, the assembly location is an empty string so we fall back
// to AppContext.BaseDirectory which is the directory containing the single-file executable.
- string path = typeof(MsQuicApi).Assembly.Location is string assemblyLocation && !string.IsNullOrEmpty(assemblyLocation)
+ string path = !ShouldUseAppLocalMsQuic() && typeof(MsQuicApi).Assembly.Location is string assemblyLocation && !string.IsNullOrEmpty(assemblyLocation)
? System.IO.Path.GetDirectoryName(assemblyLocation)!
: AppContext.BaseDirectory;
@@ -278,4 +279,7 @@ private static bool IsTls13Disabled(bool isServer)
#endif
return false;
}
+
+ private static bool ShouldUseAppLocalMsQuic() => AppContextSwitchHelper.GetBooleanConfig(
+ "System.Net.Quic.AppLocalMsQuic");
}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicConfiguration.Cache.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicConfiguration.Cache.cs
index 6beddff6638336..4e06f6c6b0bd1e 100644
--- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicConfiguration.Cache.cs
+++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicConfiguration.Cache.cs
@@ -16,27 +16,9 @@ namespace System.Net.Quic;
internal static partial class MsQuicConfiguration
{
- private const string DisableCacheEnvironmentVariable = "DOTNET_SYSTEM_NET_QUIC_DISABLE_CONFIGURATION_CACHE";
- private const string DisableCacheCtxSwitch = "System.Net.Quic.DisableConfigurationCache";
-
- internal static bool ConfigurationCacheEnabled { get; } = GetConfigurationCacheEnabled();
-
- private static bool GetConfigurationCacheEnabled()
- {
- // AppContext switch takes precedence
- if (AppContext.TryGetSwitch(DisableCacheCtxSwitch, out bool value))
- {
- return !value;
- }
- // check environment variable second
- else if (Environment.GetEnvironmentVariable(DisableCacheEnvironmentVariable) is string envVar)
- {
- return !(envVar == "1" || envVar.Equals("true", StringComparison.OrdinalIgnoreCase));
- }
-
- // enabled by default
- return true;
- }
+ internal static bool ConfigurationCacheEnabled { get; } = !AppContextSwitchHelper.GetBooleanConfig(
+ "System.Net.Quic.DisableConfigurationCache",
+ "DOTNET_SYSTEM_NET_QUIC_DISABLE_CONFIGURATION_CACHE");
private static readonly MsQuicConfigurationCache s_configurationCache = new MsQuicConfigurationCache();
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs
index 5e3b440377814a..7f9af662755d60 100644
--- a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs
+++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs
@@ -720,7 +720,7 @@ public async Task ConnectWithClientCertificate(bool sendCertificate, ClientCertS
await serverConnection.DisposeAsync();
}
- [Fact]
+ [ConditionalFact(typeof(QuicTestCollection), nameof(QuicTestCollection.IsUsingSchannelBackend))]
[PlatformSpecific(TestPlatforms.Windows)]
public async Task Server_CertificateWithEphemeralKey_Throws()
{
@@ -764,7 +764,7 @@ public async Task Server_CertificateWithEphemeralKey_Throws()
}
}
- [Fact]
+ [ConditionalFact(typeof(QuicTestCollection), nameof(QuicTestCollection.IsUsingSchannelBackend))]
[PlatformSpecific(TestPlatforms.Windows)]
public async Task Client_CertificateWithEphemeralKey_Throws()
{
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs
index 0cc5c418719b61..2a4eb4db68a80f 100644
--- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs
+++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs
@@ -44,6 +44,18 @@ public abstract class QuicTestBase : IDisposable
public const int PassingTestTimeoutMilliseconds = 4 * 60 * 1000;
public static TimeSpan PassingTestTimeout => TimeSpan.FromMilliseconds(PassingTestTimeoutMilliseconds);
+ static QuicTestBase()
+ {
+ // Opt in to run with OpenSSL version on MsQuic on older windows where Schannel
+ // version is not supported.
+ //
+ // This has to happen here in order to be called before QuicTestBase.IsSupported
+ if (PlatformDetection.IsWindows10OrLater && !QuicTestCollection.IsWindowsVersionWithSchannelSupport())
+ {
+ AppContext.SetSwitch("System.Net.Quic.AppLocalMsQuic", true);
+ }
+ }
+
public QuicTestBase(ITestOutputHelper output)
{
_output = output;
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestCollection.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestCollection.cs
index 699a41fcd1042b..1ca9a9e9dd7e5a 100644
--- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestCollection.cs
+++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestCollection.cs
@@ -26,8 +26,9 @@ public unsafe class QuicTestCollection : ICollectionFixture,
public QuicTestCollection()
{
string msQuicLibraryVersion = GetMsQuicLibraryVersion();
+ string tlsBackend = IsUsingSchannelBackend() ? "Schannel" : "OpenSSL";
// If any of the reflection bellow breaks due to changes in "System.Net.Quic.MsQuicApi", also check and fix HttpStress project as it uses the same hack.
- Console.WriteLine($"MsQuic {(IsSupported ? "supported" : "not supported")} and using '{msQuicLibraryVersion}'.");
+ Console.WriteLine($"MsQuic {(IsSupported ? "supported" : "not supported")} and using '{msQuicLibraryVersion}' {(IsSupported ? $"({tlsBackend})" : "")}.");
if (IsSupported)
{
@@ -97,6 +98,13 @@ void DumpCounter(QUIC_PERFORMANCE_COUNTERS counter)
Console.WriteLine($"Unobserved exceptions of {s_unobservedExceptions.Count} different types: {Environment.NewLine}{string.Join(Environment.NewLine + new string('=', 120) + Environment.NewLine, s_unobservedExceptions.Select(pair => $"Count {pair.Value}: {pair.Key}"))}");
}
+ internal static bool IsWindowsVersionWithSchannelSupport()
+ {
+ // copied from MsQuicApi implementation to avoid triggering the static constructor
+ Version minWindowsVersion = new Version(10, 0, 20145, 1000);
+ return OperatingSystem.IsWindowsVersionAtLeast(minWindowsVersion.Major, minWindowsVersion.Minor, minWindowsVersion.Build, minWindowsVersion.Revision);
+ }
+
private static Version GetMsQuicVersion()
{
Type msQuicApiType = Type.GetType("System.Net.Quic.MsQuicApi, System.Net.Quic");
@@ -111,6 +119,13 @@ private static Version GetMsQuicVersion()
return (string)msQuicApiType.GetProperty("MsQuicLibraryVersion", BindingFlags.NonPublic | BindingFlags.Static).GetGetMethod(true).Invoke(null, Array.Empty