diff --git a/src/libraries/Common/src/System/AppContextSwitchHelper.cs b/src/libraries/Common/src/System/AppContextSwitchHelper.cs
new file mode 100644
index 0000000000000..5d6f5f2b7c405
--- /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 == "1" || 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 3dc678ce8442c..3f45ed87f8165 100644
--- a/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj
+++ b/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj
@@ -29,6 +29,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 28c326b7b65fb..5509a842a9b0f 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;
@@ -23,6 +24,9 @@ internal sealed unsafe partial class MsQuicApi
private static readonly delegate* unmanaged[Cdecl] MsQuicOpenVersion;
private static readonly delegate* unmanaged[Cdecl] MsQuicClose;
+ [LibraryImport("Kernel32.dll", EntryPoint = "GetModuleFileNameW")]
+ internal static partial int GetModuleNameW(IntPtr hModule, char* lpFilename, int nSize);
+
public MsQuicSafeHandle Registration { get; }
public QUIC_API_TABLE* ApiTable { get; }
@@ -73,7 +77,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.
@@ -89,8 +93,29 @@ static MsQuicApi()
if (OperatingSystem.IsWindows())
{
- // Windows ships msquic in the assembly directory.
- loaded = NativeLibrary.TryLoad(Interop.Libraries.MsQuic, typeof(MsQuicApi).Assembly, DllImportSearchPath.AssemblyDirectory, out msQuicHandle);
+ // We ship Schannel version of MsQuic as part of the .NET runtime.
+ // This version works on newer OS versions (see s_minWindowsVersion).
+ //
+ // For cases where the Schannel version cannot be used, we want to
+ // support developers explicitly providing OpenSSL version of MsQuic.
+ // in the application directory.
+ if (ShouldUseAppLocalMsQuic())
+ {
+ var path = Path.Combine(AppContext.BaseDirectory, Interop.Libraries.MsQuic);
+
+ if (NetEventSource.Log.IsEnabled())
+ {
+ NetEventSource.Info(null, $"Attempting to load MsQuic library from '{path}'.");
+ }
+
+ System.Console.WriteLine($"Attempting to load MsQuic library from '{path}'.");
+ loaded = NativeLibrary.TryLoad(path, out msQuicHandle);
+ }
+ else
+ {
+ System.Console.WriteLine("Loading from assembly directory");
+ loaded = NativeLibrary.TryLoad(Interop.Libraries.MsQuic, typeof(MsQuicApi).Assembly, DllImportSearchPath.AssemblyDirectory, out msQuicHandle);
+ }
}
else
{
@@ -110,6 +135,22 @@ static MsQuicApi()
return;
}
+ if (OperatingSystem.IsWindows())
+ {
+ Span pathBuffer = stackalloc char[260];
+ int len;
+ fixed (char* pathBufferPtr = pathBuffer)
+ {
+ len = GetModuleNameW(msQuicHandle, pathBufferPtr, pathBuffer.Length);
+ }
+
+ if (len > 0)
+ {
+ string path = new string(pathBuffer.Slice(0, len));
+ System.Console.WriteLine($"Loaded MsQuic library from '{path}'.");
+ }
+ }
+
MsQuicOpenVersion = (delegate* unmanaged[Cdecl])NativeLibrary.GetExport(msQuicHandle, nameof(MsQuicOpenVersion));
MsQuicClose = (delegate* unmanaged[Cdecl])NativeLibrary.GetExport(msQuicHandle, nameof(MsQuicClose));
@@ -263,4 +304,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 4db0f88bf71a4..e487148351fb7 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
@@ -15,27 +15,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 41ac5e41da24d..64b40f60f1d21 100644
--- a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs
+++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs
@@ -731,7 +731,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()
{
@@ -775,7 +775,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 c3e0e4e7372ab..1a44e76e3ae17 100644
--- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs
+++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs
@@ -44,6 +44,19 @@ 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())
+ {
+ System.Console.WriteLine("Setting AppContext switch to use OpenSSL version of MsQuic on Windows.");
+ 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 5125c72e0ca8d..7d44714f47d34 100644
--- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestCollection.cs
+++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestCollection.cs
@@ -25,8 +25,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)
{
@@ -84,6 +85,14 @@ void DumpCounter(QUIC_PERFORMANCE_COUNTERS counter)
System.Console.WriteLine(sb.ToString());
}
+ 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");
@@ -98,6 +107,13 @@ private static Version GetMsQuicVersion()
return (string)msQuicApiType.GetProperty("MsQuicLibraryVersion", BindingFlags.NonPublic | BindingFlags.Static).GetGetMethod(true).Invoke(null, Array.Empty