From 796d36a5ce080a94ef62bf3670146653ab8f4c50 Mon Sep 17 00:00:00 2001 From: Cory Nelson Date: Thu, 6 Feb 2020 16:12:04 -0800 Subject: [PATCH] Add QuicConnection.IsQuicSupported for runtime feature detection (#31689) Add QuicConnection.IsQuicSupported for runtime feature detection, and update tests to use it. --- .../System.Net.Quic/ref/System.Net.Quic.cs | 1 + .../System.Net.Quic/src/Resources/Strings.resx | 68 +++++++++++++++++++++- .../Implementations/MsQuic/Internal/MsQuicApi.cs | 54 +++++++++++++++-- .../MsQuic/Internal/MsQuicSession.cs | 4 ++ .../src/System/Net/Quic/QuicConnection.cs | 3 + .../tests/FunctionalTests/MsQuicTests.cs | 19 +++--- 6 files changed, 133 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs b/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs index 36820e21..c014ea4 100644 --- a/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs +++ b/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs @@ -27,6 +27,7 @@ namespace System.Net.Quic public System.Net.Security.SslApplicationProtocol NegotiatedApplicationProtocol => throw null; public ValueTask CloseAsync(long errorCode, System.Threading.CancellationToken cancellationToken = default) => throw null; public void Dispose() => throw null; + public static bool IsQuicSupported => throw null; } public sealed partial class QuicListener : IDisposable { diff --git a/src/libraries/System.Net.Quic/src/Resources/Strings.resx b/src/libraries/System.Net.Quic/src/Resources/Strings.resx index 8c5c479..4a9f862 100644 --- a/src/libraries/System.Net.Quic/src/Resources/Strings.resx +++ b/src/libraries/System.Net.Quic/src/Resources/Strings.resx @@ -1,4 +1,64 @@ - + + + @@ -57,8 +117,10 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + + QUIC is not supported on this platform. See http://aka.ms/dotnetquic + Placeholder text - + \ No newline at end of file diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs index 17b05c5..653e16a 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.IO; +using System.Net.Security; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; using System.Text; @@ -17,9 +19,20 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal private unsafe MsQuicApi() { - QuicExceptionHelpers.ThrowIfFailed( - Interop.MsQuic.MsQuicOpen(version: 1, out MsQuicNativeMethods.NativeApi* registration), - "Could not open MsQuic."); + MsQuicNativeMethods.NativeApi* registration; + + try + { + uint status = Interop.MsQuic.MsQuicOpen(version: 1, out registration); + if (!MsQuicStatusHelper.SuccessfulStatusCode(status)) + { + throw new NotSupportedException(SR.net_quic_notsupported); + } + } + catch (DllNotFoundException) + { + throw new NotSupportedException(SR.net_quic_notsupported); + } MsQuicNativeMethods.NativeApi nativeRegistration = *registration; @@ -114,7 +127,40 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal _registrationContext = ctx; } - internal static MsQuicApi Api { get; } = new MsQuicApi(); + internal static MsQuicApi Api { get; } + + internal static bool IsQuicSupported { get; } + + static MsQuicApi() + { + // MsQuicOpen will succeed even if the platform will not support it. It will then fail with unspecified + // platform-specific errors in subsequent callbacks. For now, check for the minimum build we've tested it on. + + // TODO: + // - Hopefully, MsQuicOpen will perform this check for us and give us a consistent error code. + // - Otherwise, dial this in to reflect actual minimum requirements and add some sort of platform + // error code mapping when creating exceptions. + + OperatingSystem ver = Environment.OSVersion; + + if (ver.Platform == PlatformID.Win32NT && ver.Version < new Version(10, 0, 19041, 0)) + { + IsQuicSupported = false; + return; + } + + // TODO: try to initialize TLS 1.3 in SslStream. + + try + { + Api = new MsQuicApi(); + IsQuicSupported = true; + } + catch (NotSupportedException) + { + IsQuicSupported = false; + } + } internal MsQuicNativeMethods.RegistrationOpenDelegate RegistrationOpenDelegate { get; } internal MsQuicNativeMethods.RegistrationCloseDelegate RegistrationCloseDelegate { get; } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs index 7754623..89dd99f 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs @@ -12,6 +12,10 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal internal MsQuicSession() { + if (!MsQuicApi.IsQuicSupported) + { + throw new NotSupportedException(SR.net_quic_notsupported); + } } public IntPtr ConnectionOpen(QuicClientConnectionOptions options) diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs index 6ac1a97..20a63b2 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Net.Quic.Implementations; +using System.Net.Quic.Implementations.MsQuic.Internal; using System.Net.Security; using System.Threading; using System.Threading.Tasks; @@ -13,6 +14,8 @@ namespace System.Net.Quic { private readonly QuicConnectionProvider _provider; + public static bool IsQuicSupported => MsQuicApi.IsQuicSupported; + /// /// Create an outbound QUIC connection. /// diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs index 5c1720b6..7599fbb 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs @@ -13,11 +13,12 @@ using Xunit; namespace System.Net.Quic.Tests { + [ConditionalClass(typeof(QuicConnection), nameof(QuicConnection.IsQuicSupported))] public class MsQuicTests : MsQuicTestBase { private static ReadOnlyMemory s_data = Encoding.UTF8.GetBytes("Hello world!"); - [Fact(Skip = "MsQuic not available")] + [Fact] public async Task BasicTest() { for (int i = 0; i < 100; i++) @@ -62,7 +63,7 @@ namespace System.Net.Quic.Tests } } - [Fact(Skip = "MsQuic not available")] + [Fact] public async Task MultipleReadsAndWrites() { for (int j = 0; j < 100; j++) @@ -127,7 +128,7 @@ namespace System.Net.Quic.Tests } } - [Fact(Skip = "MsQuic not available")] + [Fact] public async Task MultipleStreamsOnSingleConnection() { Task listenTask = Task.Run(async () => @@ -209,7 +210,7 @@ namespace System.Net.Quic.Tests await (new[] { listenTask, clientTask }).WhenAllOrAnyFailed(millisecondsTimeout: 60000); } - [Fact(Skip = "MsQuic not available")] + [Fact] public async Task AbortiveConnectionFromClient() { using QuicConnection clientConnection = CreateQuicConnection(DefaultListener.ListenEndPoint); @@ -226,7 +227,7 @@ namespace System.Net.Quic.Tests Assert.Throws(() => stream.CanRead); } - [Fact(Skip = "MsQuic not available")] + [Fact] public async Task TestStreams() { using (QuicListener listener = new QuicListener( @@ -264,7 +265,7 @@ namespace System.Net.Quic.Tests } } - [Fact(Skip = "MsQuic not available")] + [Fact] public async Task UnidirectionalAndBidirectionalStreamCountsWork() { using QuicConnection clientConnection = CreateQuicConnection(DefaultListener.ListenEndPoint); @@ -276,7 +277,7 @@ namespace System.Net.Quic.Tests Assert.Equal(100, serverConnection.GetRemoteAvailableUnidirectionalStreamCount()); } - [Fact(Skip = "MsQuic not available")] + [Fact] public async Task UnidirectionalAndBidirectionalChangeValues() { QuicClientConnectionOptions options = new QuicClientConnectionOptions() @@ -298,7 +299,7 @@ namespace System.Net.Quic.Tests Assert.Equal(100, serverConnection.GetRemoteAvailableUnidirectionalStreamCount()); } - [Fact(Skip = "MsQuic not available")] + [Fact] public async Task CallDifferentWriteMethodsWorks() { using QuicConnection clientConnection = CreateQuicConnection(DefaultListener.ListenEndPoint); @@ -327,7 +328,7 @@ namespace System.Net.Quic.Tests Assert.Equal(24, res); } - [Theory(Skip = "MsQuic not available")] + [Theory] [MemberData(nameof(QuicStream_ReadWrite_Random_Success_Data))] public async Task QuicStream_ReadWrite_Random_Success(int readSize, int writeSize) { -- 2.7.4