From 1daebd172229f490a470b551776d2e99156029d5 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Fri, 7 Feb 2020 10:43:09 -0800 Subject: [PATCH] Replat on System.Net.Quic (#18689) --- eng/ProjectReferences.props | 2 +- ...ore.Connections.Abstractions.netcoreapp.cs | 3 +- ...Connections.Abstractions.netstandard2.0.cs | 3 +- ...Connections.Abstractions.netstandard2.1.cs | 3 +- .../src/Features/IQuicStreamFeature.cs | 3 +- .../src/Internal/Http3/Http3Connection.cs | 2 +- .../src/Internal/Http3/Http3ControlStream.cs | 2 +- .../Core/src/Internal/Http3/Http3Stream.cs | 6 +- src/Servers/Kestrel/Kestrel.sln | 2 +- src/Servers/Kestrel/NuGet.config | 7 + .../Kestrel/Transport.MsQuic/README.md | 10 - .../src/Internal/MsQuicApi.cs | 251 -------- .../src/Internal/MsQuicConnection.cs | 336 ---------- .../src/Internal/MsQuicConnectionListener.cs | 197 ------ .../src/Internal/MsQuicConstants.cs | 151 ----- .../src/Internal/MsQuicEnums.cs | 178 ------ .../src/Internal/MsQuicNativeMethods.cs | 590 ------------------ .../src/Internal/MsQuicStatusException.cs | 42 -- .../src/Internal/MsQuicStatusHelper.cs | 25 - .../src/Internal/MsQuicStream.cs | 477 -------------- .../src/Internal/MsQuicTransportContext.cs | 21 - .../src/Internal/QuicSecConfig.cs | 44 -- .../src/Internal/QuicSession.cs | 141 ----- .../Internal/ResettableCompletionSource.cs | 58 -- .../src/Internal/UIntExtensions.cs | 25 - .../src/MsQuicConnectionFactory.cs | 64 -- .../src/MsQuicTransportFactory.cs | 47 -- src/Servers/Kestrel/Transport.Quic/README.md | 3 + ...Core.Server.Kestrel.Transport.Quic.csproj} | 2 +- ...rver.Kestrel.Transport.Quic.netcoreapp.cs} | 20 +- .../src/Internal/FakeTlsConnectionFeature.cs | 25 + .../src/Internal/IQuicTrace.cs} | 7 +- .../src/Internal/QuicConnectionContext.cs | 80 +++ .../src/Internal/QuicConnectionListener.cs | 64 ++ .../src/Internal/QuicStreamContext.cs | 316 ++++++++++ .../src/Internal/QuicTrace.cs} | 31 +- .../src/Internal/QuicTransportContext.cs | 19 + ...Core.Server.Kestrel.Transport.Quic.csproj} | 18 +- .../src/QuicConnectionFactory.cs | 50 ++ .../src/QuicTransportFactory.cs | 44 ++ .../src/QuicTransportOptions.cs} | 11 +- .../src/WebHostBuilderMsQuicExtensions.cs | 11 +- ...er.Kestrel.Transport.Sockets.netcoreapp.cs | 17 +- .../src/Client/SocketConnectionFactory.cs | 2 +- .../Http3SampleApp/Http3SampleApp.csproj | 2 +- .../Kestrel/samples/Http3SampleApp/Program.cs | 2 +- .../Kestrel/samples/QuicSampleApp/Program.cs | 7 +- .../QuicSampleApp/QuicSampleApp.csproj | 20 +- .../samples/QuicSampleClient/Program.cs | 24 +- .../QuicSampleClient/QuicSampleClient.csproj | 17 +- .../Http3/Http3TestBase.cs | 6 +- .../Http2CatIServiceCollectionExtensions.cs | 2 +- 52 files changed, 741 insertions(+), 2749 deletions(-) create mode 100644 src/Servers/Kestrel/NuGet.config delete mode 100644 src/Servers/Kestrel/Transport.MsQuic/README.md delete mode 100644 src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicApi.cs delete mode 100644 src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicConnection.cs delete mode 100644 src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicConnectionListener.cs delete mode 100644 src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicConstants.cs delete mode 100644 src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicEnums.cs delete mode 100644 src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicNativeMethods.cs delete mode 100644 src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicStatusException.cs delete mode 100644 src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicStatusHelper.cs delete mode 100644 src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicStream.cs delete mode 100644 src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicTransportContext.cs delete mode 100644 src/Servers/Kestrel/Transport.MsQuic/src/Internal/QuicSecConfig.cs delete mode 100644 src/Servers/Kestrel/Transport.MsQuic/src/Internal/QuicSession.cs delete mode 100644 src/Servers/Kestrel/Transport.MsQuic/src/Internal/ResettableCompletionSource.cs delete mode 100644 src/Servers/Kestrel/Transport.MsQuic/src/Internal/UIntExtensions.cs delete mode 100644 src/Servers/Kestrel/Transport.MsQuic/src/MsQuicConnectionFactory.cs delete mode 100644 src/Servers/Kestrel/Transport.MsQuic/src/MsQuicTransportFactory.cs create mode 100644 src/Servers/Kestrel/Transport.Quic/README.md rename src/Servers/Kestrel/{Transport.MsQuic/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj => Transport.Quic/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj} (95%) rename src/Servers/Kestrel/{Transport.MsQuic/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.netcoreapp.cs => Transport.Quic/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.netcoreapp.cs} (65%) create mode 100644 src/Servers/Kestrel/Transport.Quic/src/Internal/FakeTlsConnectionFeature.cs rename src/Servers/Kestrel/{Transport.MsQuic/src/Internal/IMsQuicTrace.cs => Transport.Quic/src/Internal/IQuicTrace.cs} (62%) create mode 100644 src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs create mode 100644 src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs create mode 100644 src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs rename src/Servers/Kestrel/{Transport.MsQuic/src/Internal/MsQuicTrace.cs => Transport.Quic/src/Internal/QuicTrace.cs} (57%) create mode 100644 src/Servers/Kestrel/Transport.Quic/src/Internal/QuicTransportContext.cs rename src/Servers/Kestrel/{Transport.MsQuic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj => Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj} (69%) create mode 100644 src/Servers/Kestrel/Transport.Quic/src/QuicConnectionFactory.cs create mode 100644 src/Servers/Kestrel/Transport.Quic/src/QuicTransportFactory.cs rename src/Servers/Kestrel/{Transport.MsQuic/src/MsQuicTransportOptions.cs => Transport.Quic/src/QuicTransportOptions.cs} (85%) rename src/Servers/Kestrel/{Transport.MsQuic => Transport.Quic}/src/WebHostBuilderMsQuicExtensions.cs (59%) diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props index 494a9a560a..8d1040b157 100644 --- a/eng/ProjectReferences.props +++ b/eng/ProjectReferences.props @@ -63,7 +63,7 @@ - + diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs index 1cf1fe4d3a..b454acd516 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netcoreapp.cs @@ -198,7 +198,8 @@ namespace Microsoft.AspNetCore.Connections.Features } public partial interface IQuicStreamFeature { - bool IsUnidirectional { get; } + bool CanRead { get; } + bool CanWrite { get; } long StreamId { get; } } public partial interface IQuicStreamListenerFeature diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs index 1cf1fe4d3a..b454acd516 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.0.cs @@ -198,7 +198,8 @@ namespace Microsoft.AspNetCore.Connections.Features } public partial interface IQuicStreamFeature { - bool IsUnidirectional { get; } + bool CanRead { get; } + bool CanWrite { get; } long StreamId { get; } } public partial interface IQuicStreamListenerFeature diff --git a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs index 1cf1fe4d3a..b454acd516 100644 --- a/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs +++ b/src/Servers/Connections.Abstractions/ref/Microsoft.AspNetCore.Connections.Abstractions.netstandard2.1.cs @@ -198,7 +198,8 @@ namespace Microsoft.AspNetCore.Connections.Features } public partial interface IQuicStreamFeature { - bool IsUnidirectional { get; } + bool CanRead { get; } + bool CanWrite { get; } long StreamId { get; } } public partial interface IQuicStreamListenerFeature diff --git a/src/Servers/Connections.Abstractions/src/Features/IQuicStreamFeature.cs b/src/Servers/Connections.Abstractions/src/Features/IQuicStreamFeature.cs index 98c71b7a08..802209c1ea 100644 --- a/src/Servers/Connections.Abstractions/src/Features/IQuicStreamFeature.cs +++ b/src/Servers/Connections.Abstractions/src/Features/IQuicStreamFeature.cs @@ -5,7 +5,8 @@ namespace Microsoft.AspNetCore.Connections.Features { public interface IQuicStreamFeature { - bool IsUnidirectional { get; } + bool CanRead { get; } + bool CanWrite { get; } long StreamId { get; } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs index ca0ac6bc49..070931ad42 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs @@ -93,7 +93,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 var streamId = streamFeature.StreamId; HighestStreamId = streamId; - if (streamFeature.IsUnidirectional) + if (!streamFeature.CanWrite) { var stream = new Http3ControlStream(application, this, httpConnectionContext); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs index 8163d65161..cca8f59837 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs @@ -80,7 +80,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 return true; } - // TODO make this actually close the Http3Stream by telling msquic to close the stream. + // TODO make this actually close the Http3Stream by telling quic to close the stream. return false; } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs index e858ddac06..e708a89967 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -107,7 +107,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 return true; } - // TODO make this actually close the Http3Stream by telling msquic to close the stream. + // TODO make this actually close the Http3Stream by telling quic to close the stream. return false; } @@ -141,7 +141,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 } catch (Http3StreamErrorException) { - // TODO + // TODO } finally { @@ -151,7 +151,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3 } catch (Exception) { - // TODO + // TODO } finally { diff --git a/src/Servers/Kestrel/Kestrel.sln b/src/Servers/Kestrel/Kestrel.sln index 2c80bcef71..37e7dc8ff8 100644 --- a/src/Servers/Kestrel/Kestrel.sln +++ b/src/Servers/Kestrel/Kestrel.sln @@ -86,7 +86,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "http2cat", "samples\http2ca EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuicSampleApp", "samples\QuicSampleApp\QuicSampleApp.csproj", "{53A8634C-DFC5-4A5B-8864-9EF1707E3F18}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic", "Transport.MsQuic\src\Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj", "{62CFF861-807E-43F6-9403-22AA7F06C9A6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic", "Transport.Quic\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj", "{62CFF861-807E-43F6-9403-22AA7F06C9A6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuicSampleClient", "samples\QuicSampleClient\QuicSampleClient.csproj", "{F39A942B-85A8-4C1B-A5BC-435555E79F20}" EndProject diff --git a/src/Servers/Kestrel/NuGet.config b/src/Servers/Kestrel/NuGet.config new file mode 100644 index 0000000000..cbdc0002cb --- /dev/null +++ b/src/Servers/Kestrel/NuGet.config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/Servers/Kestrel/Transport.MsQuic/README.md b/src/Servers/Kestrel/Transport.MsQuic/README.md deleted file mode 100644 index efacbcb9fa..0000000000 --- a/src/Servers/Kestrel/Transport.MsQuic/README.md +++ /dev/null @@ -1,10 +0,0 @@ -## Using MsQuic on Windows - -### Setup pre-requisites - -1. Update machine to the latest Windows Insiders build (build number 19010 or later). This is required for TLS 1.3 support. -2. Copy msquic.dll and msquic.pdb to this directory and uncomment the copy task in Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj. This will copy the msquic.dll into any built project. - -For external contributors, msquic.dll isn't available publicly yet. See https://github.com/aspnet/Announcements/issues/393. - -Credit to Diwakar Mantha and the Kaizala team for the MsQuic interop code. \ No newline at end of file diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicApi.cs b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicApi.cs deleted file mode 100644 index ae17ff5eb2..0000000000 --- a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicApi.cs +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Runtime.InteropServices; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal -{ - internal class MsQuicApi : IDisposable - { - private bool _disposed = false; - - private IntPtr _registrationContext; - - internal unsafe MsQuicApi() - { - var status = (uint)MsQuicNativeMethods.MsQuicOpen(version: 1, out var registration); - MsQuicStatusException.ThrowIfFailed(status); - - NativeRegistration = *registration; - - RegistrationOpenDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.RegistrationOpen); - RegistrationCloseDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.RegistrationClose); - - SecConfigCreateDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.SecConfigCreate); - SecConfigDeleteDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.SecConfigDelete); - - SessionOpenDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.SessionOpen); - SessionCloseDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.SessionClose); - SessionShutdownDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.SessionShutdown); - - ListenerOpenDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.ListenerOpen); - ListenerCloseDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.ListenerClose); - ListenerStartDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.ListenerStart); - ListenerStopDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.ListenerStop); - - ConnectionOpenDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.ConnectionOpen); - ConnectionCloseDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.ConnectionClose); - ConnectionShutdownDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.ConnectionShutdown); - ConnectionStartDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.ConnectionStart); - - StreamOpenDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.StreamOpen); - StreamCloseDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.StreamClose); - StreamStartDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.StreamStart); - StreamShutdownDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.StreamShutdown); - StreamSendDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.StreamSend); - - SetContextDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.SetContext); - GetContextDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.GetContext); - SetCallbackHandlerDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.SetCallbackHandler); - - SetParamDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.SetParam); - GetParamDelegate = - Marshal.GetDelegateForFunctionPointer( - NativeRegistration.GetParam); - } - - internal MsQuicNativeMethods.NativeApi NativeRegistration { get; private set; } - - internal MsQuicNativeMethods.RegistrationOpenDelegate RegistrationOpenDelegate { get; private set; } - internal MsQuicNativeMethods.RegistrationCloseDelegate RegistrationCloseDelegate { get; private set; } - - internal MsQuicNativeMethods.SecConfigCreateDelegate SecConfigCreateDelegate { get; private set; } - internal MsQuicNativeMethods.SecConfigCreateCompleteDelegate SecConfigCreateCompleteDelegate { get; private set; } - internal MsQuicNativeMethods.SecConfigDeleteDelegate SecConfigDeleteDelegate { get; private set; } - - internal MsQuicNativeMethods.SessionOpenDelegate SessionOpenDelegate { get; private set; } - internal MsQuicNativeMethods.SessionCloseDelegate SessionCloseDelegate { get; private set; } - internal MsQuicNativeMethods.SessionShutdownDelegate SessionShutdownDelegate { get; private set; } - - internal MsQuicNativeMethods.ListenerOpenDelegate ListenerOpenDelegate { get; private set; } - internal MsQuicNativeMethods.ListenerCloseDelegate ListenerCloseDelegate { get; private set; } - internal MsQuicNativeMethods.ListenerStartDelegate ListenerStartDelegate { get; private set; } - internal MsQuicNativeMethods.ListenerStopDelegate ListenerStopDelegate { get; private set; } - - internal MsQuicNativeMethods.ConnectionOpenDelegate ConnectionOpenDelegate { get; private set; } - internal MsQuicNativeMethods.ConnectionCloseDelegate ConnectionCloseDelegate { get; private set; } - internal MsQuicNativeMethods.ConnectionShutdownDelegate ConnectionShutdownDelegate { get; private set; } - internal MsQuicNativeMethods.ConnectionStartDelegate ConnectionStartDelegate { get; private set; } - - internal MsQuicNativeMethods.StreamOpenDelegate StreamOpenDelegate { get; private set; } - internal MsQuicNativeMethods.StreamCloseDelegate StreamCloseDelegate { get; private set; } - internal MsQuicNativeMethods.StreamStartDelegate StreamStartDelegate { get; private set; } - internal MsQuicNativeMethods.StreamShutdownDelegate StreamShutdownDelegate { get; private set; } - internal MsQuicNativeMethods.StreamSendDelegate StreamSendDelegate { get; private set; } - internal MsQuicNativeMethods.StreamReceiveCompleteDelegate StreamReceiveComplete { get; private set; } - - internal MsQuicNativeMethods.SetContextDelegate SetContextDelegate { get; private set; } - internal MsQuicNativeMethods.GetContextDelegate GetContextDelegate { get; private set; } - internal MsQuicNativeMethods.SetCallbackHandlerDelegate SetCallbackHandlerDelegate { get; private set; } - - internal MsQuicNativeMethods.SetParamDelegate SetParamDelegate { get; private set; } - internal MsQuicNativeMethods.GetParamDelegate GetParamDelegate { get; private set; } - - internal void RegistrationOpen(byte[] name) - { - MsQuicStatusException.ThrowIfFailed(RegistrationOpenDelegate(name, out var ctx)); - _registrationContext = ctx; - } - - internal unsafe uint UnsafeSetParam( - IntPtr Handle, - uint Level, - uint Param, - MsQuicNativeMethods.QuicBuffer Buffer) - { - return SetParamDelegate( - Handle, - Level, - Param, - Buffer.Length, - Buffer.Buffer); - } - - internal unsafe uint UnsafeGetParam( - IntPtr Handle, - uint Level, - uint Param, - ref MsQuicNativeMethods.QuicBuffer Buffer) - { - return GetParamDelegate( - Handle, - Level, - Param, - out Buffer.Length, - out Buffer.Buffer); - } - - public async ValueTask CreateSecurityConfig(X509Certificate2 certificate) - { - QuicSecConfig secConfig = null; - var tcs = new TaskCompletionSource(); - var secConfigCreateStatus = MsQuicConstants.InternalError; - - var status = SecConfigCreateDelegate( - _registrationContext, - (uint)QUIC_SEC_CONFIG_FLAG.CERT_CONTEXT, - certificate.Handle, - null, - IntPtr.Zero, - SecCfgCreateCallbackHandler); - - MsQuicStatusException.ThrowIfFailed(status); - - void SecCfgCreateCallbackHandler( - IntPtr context, - uint status, - IntPtr securityConfig) - { - secConfig = new QuicSecConfig(this, securityConfig); - secConfigCreateStatus = status; - tcs.SetResult(null); - } - - await tcs.Task; - - MsQuicStatusException.ThrowIfFailed(secConfigCreateStatus); - - return secConfig; - } - - public QuicSession SessionOpen( - string alpn) - { - var sessionPtr = IntPtr.Zero; - - var status = SessionOpenDelegate( - _registrationContext, - Encoding.UTF8.GetBytes(alpn), - IntPtr.Zero, - ref sessionPtr); - MsQuicStatusException.ThrowIfFailed(status); - - return new QuicSession(this, sessionPtr); - } - - public void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - - ~MsQuicApi() - { - Dispose(disposing: false); - } - - private void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - RegistrationCloseDelegate?.Invoke(_registrationContext); - - _disposed = true; - } - } -} diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicConnection.cs b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicConnection.cs deleted file mode 100644 index b1b10ae1dd..0000000000 --- a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicConnection.cs +++ /dev/null @@ -1,336 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Runtime.InteropServices; -using System.Threading.Channels; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Connections.Abstractions.Features; -using Microsoft.AspNetCore.Connections.Features; -using Microsoft.AspNetCore.Http.Features; -using static Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal.MsQuicNativeMethods; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal -{ - internal class MsQuicConnection : TransportConnection, IQuicStreamListenerFeature, IQuicCreateStreamFeature, IDisposable - { - public MsQuicApi _api; - private bool _disposed; - private readonly MsQuicTransportContext _context; - private readonly IMsQuicTrace _log; - private IntPtr _nativeObjPtr; - private static GCHandle _handle; - private ConnectionCallbackDelegate _connectionDelegate; - private readonly Channel _acceptQueue = Channel.CreateUnbounded(new UnboundedChannelOptions - { - SingleReader = true, - SingleWriter = true - }); - - public MsQuicConnection(MsQuicApi api, MsQuicTransportContext context, IntPtr nativeObjPtr) - { - _api = api; - _context = context; - _log = context.Log; - _nativeObjPtr = nativeObjPtr; - - SetCallbackHandler(); - - SetIdleTimeout(_context.Options.IdleTimeout); - - Features.Set(new FakeTlsConnectionFeature()); - Features.Set(this); - Features.Set(this); - - _log.NewConnection(ConnectionId); - } - - internal uint HandleEvent(ref ConnectionEvent connectionEvent) - { - var status = MsQuicConstants.Success; - switch (connectionEvent.Type) - { - case QUIC_CONNECTION_EVENT.CONNECTED: - { - status = HandleEventConnected( - connectionEvent); - } - break; - - case QUIC_CONNECTION_EVENT.SHUTDOWN_BEGIN: - { - status = HandleEventShutdownBegin( - connectionEvent); - } - break; - - case QUIC_CONNECTION_EVENT.SHUTDOWN_BEGIN_PEER: - { - status = HandleEventShutdownBeginPeer( - connectionEvent); - } - break; - - case QUIC_CONNECTION_EVENT.SHUTDOWN_COMPLETE: - { - status = HandleEventShutdownComplete( - connectionEvent); - } - break; - - case QUIC_CONNECTION_EVENT.NEW_STREAM: - { - status = HandleEventNewStream( - connectionEvent); - } - break; - - case QUIC_CONNECTION_EVENT.STREAMS_AVAILABLE: - { - status = HandleEventStreamsAvailable( - connectionEvent); - } - break; - - default: - break; - } - return status; - } - - protected virtual uint HandleEventConnected(ConnectionEvent connectionEvent) - { - return MsQuicConstants.Success; - } - - protected virtual uint HandleEventShutdownBegin(ConnectionEvent connectionEvent) - { - return MsQuicConstants.Success; - } - - protected virtual uint HandleEventShutdownBeginPeer(ConnectionEvent connectionEvent) - { - return MsQuicConstants.Success; - } - - protected virtual uint HandleEventShutdownComplete(ConnectionEvent connectionEvent) - { - return MsQuicConstants.Success; - } - - protected virtual uint HandleEventNewStream(ConnectionEvent connectionEvent) - { - var msQuicStream = new MsQuicStream(_api, this, _context, connectionEvent.StreamFlags, connectionEvent.Data.NewStream.Stream); - - _acceptQueue.Writer.TryWrite(msQuicStream); - - return MsQuicConstants.Success; - } - - protected virtual uint HandleEventStreamsAvailable(ConnectionEvent connectionEvent) - { - return MsQuicConstants.Success; - } - - public async ValueTask AcceptAsync() - { - if (await _acceptQueue.Reader.WaitToReadAsync()) - { - if (_acceptQueue.Reader.TryRead(out var stream)) - { - return stream; - } - } - - return null; - } - - public ValueTask StartUnidirectionalStreamAsync() - { - return StartStreamAsync(QUIC_STREAM_OPEN_FLAG.UNIDIRECTIONAL); - } - - public ValueTask StartBidirectionalStreamAsync() - { - return StartStreamAsync(QUIC_STREAM_OPEN_FLAG.NONE); - } - - private async ValueTask StartStreamAsync(QUIC_STREAM_OPEN_FLAG flags) - { - var stream = StreamOpen(flags); - await stream.StartAsync(); - return stream; - } - - public unsafe void SetIdleTimeout(TimeSpan timeout) - { - var msTime = (ulong)timeout.TotalMilliseconds; - var buffer = new QuicBuffer() - { - Length = sizeof(ulong), - Buffer = (byte*)&msTime - }; - SetParam(QUIC_PARAM_CONN.IDLE_TIMEOUT, buffer); - } - - public void SetPeerBiDirectionalStreamCount(ushort count) - { - SetUshortParamter(QUIC_PARAM_CONN.PEER_BIDI_STREAM_COUNT, count); - } - - public void SetPeerUnidirectionalStreamCount(ushort count) - { - SetUshortParamter(QUIC_PARAM_CONN.PEER_UNIDI_STREAM_COUNT, count); - } - - public void SetLocalBidirectionalStreamCount(ushort count) - { - SetUshortParamter(QUIC_PARAM_CONN.LOCAL_BIDI_STREAM_COUNT, count); - } - - public void SetLocalUnidirectionalStreamCount(ushort count) - { - SetUshortParamter(QUIC_PARAM_CONN.LOCAL_UNIDI_STREAM_COUNT, count); - } - - public unsafe void EnableBuffering() - { - var val = true; - var buffer = new QuicBuffer() - { - Length = sizeof(bool), - Buffer = (byte*)&val - }; - SetParam(QUIC_PARAM_CONN.USE_SEND_BUFFER, buffer); - } - - public unsafe void DisableBuffering() - { - var val = false; - var buffer = new QuicBuffer() - { - Length = sizeof(bool), - Buffer = (byte*)&val - }; - SetParam(QUIC_PARAM_CONN.USE_SEND_BUFFER, buffer); - } - - public ValueTask StartAsync( - ushort family, - string serverName, - ushort serverPort) - { - var status = _api.ConnectionStartDelegate( - _nativeObjPtr, - family, - serverName, - serverPort); - - MsQuicStatusException.ThrowIfFailed(status); - - return new ValueTask(); - } - - public MsQuicStream StreamOpen( - QUIC_STREAM_OPEN_FLAG flags) - { - var streamPtr = IntPtr.Zero; - var status = _api.StreamOpenDelegate( - _nativeObjPtr, - (uint)flags, - MsQuicStream.NativeCallbackHandler, - IntPtr.Zero, - out streamPtr); - MsQuicStatusException.ThrowIfFailed(status); - - return new MsQuicStream(_api, this, _context, flags, streamPtr); - } - - public void SetCallbackHandler() - { - _handle = GCHandle.Alloc(this); - _connectionDelegate = new ConnectionCallbackDelegate(NativeCallbackHandler); - _api.SetCallbackHandlerDelegate( - _nativeObjPtr, - _connectionDelegate, - GCHandle.ToIntPtr(_handle)); - } - - public void Shutdown( - QUIC_CONNECTION_SHUTDOWN_FLAG Flags, - ushort ErrorCode) - { - var status = _api.ConnectionShutdownDelegate( - _nativeObjPtr, - (uint)Flags, - ErrorCode); - MsQuicStatusException.ThrowIfFailed(status); - } - - internal static uint NativeCallbackHandler( - IntPtr connection, - IntPtr context, - ref ConnectionEvent connectionEventStruct) - { - var handle = GCHandle.FromIntPtr(context); - var quicConnection = (MsQuicConnection)handle.Target; - return quicConnection.HandleEvent(ref connectionEventStruct); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - ~MsQuicConnection() - { - Dispose(false); - } - - private void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (_nativeObjPtr != IntPtr.Zero) - { - _api.ConnectionCloseDelegate?.Invoke(_nativeObjPtr); - } - - _nativeObjPtr = IntPtr.Zero; - _api = null; - - _handle.Free(); - _disposed = true; - } - - private unsafe void SetUshortParamter(QUIC_PARAM_CONN param, ushort count) - { - var buffer = new QuicBuffer() - { - Length = sizeof(ushort), - Buffer = (byte*)&count - }; - SetParam(param, buffer); - } - - private void SetParam( - QUIC_PARAM_CONN param, - QuicBuffer buf) - { - MsQuicStatusException.ThrowIfFailed(_api.UnsafeSetParam( - _nativeObjPtr, - (uint)QUIC_PARAM_LEVEL.CONNECTION, - (uint)param, - buf)); - } - - public override void Abort(ConnectionAbortedException abortReason) - { - } - } -} diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicConnectionListener.cs b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicConnectionListener.cs deleted file mode 100644 index 27e2dd5912..0000000000 --- a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicConnectionListener.cs +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Net; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Channels; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using static Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal.MsQuicNativeMethods; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal -{ - /// - /// Listens for new Quic Connections. - /// - internal class MsQuicConnectionListener : IConnectionListener, IAsyncDisposable, IDisposable - { - private IMsQuicTrace _log; - private MsQuicApi _api; - private QuicSecConfig _secConfig; - private QuicSession _session; - private bool _disposed; - private bool _stopped; - private IntPtr _nativeObjPtr; - private GCHandle _handle; - private ListenerCallbackDelegate _listenerDelegate; - private MsQuicTransportContext _transportContext; - - private readonly Channel _acceptConnectionQueue = Channel.CreateUnbounded(new UnboundedChannelOptions - { - SingleReader = true, - SingleWriter = true - }); - - public MsQuicConnectionListener(MsQuicTransportOptions options, IHostApplicationLifetime lifetime, IMsQuicTrace log, EndPoint endpoint) - { - _api = new MsQuicApi(); - _log = log; - _transportContext = new MsQuicTransportContext(lifetime, _log, options); - EndPoint = endpoint; - } - - public EndPoint EndPoint { get; set; } - - public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) - { - if (await _acceptConnectionQueue.Reader.WaitToReadAsync()) - { - if (_acceptConnectionQueue.Reader.TryRead(out var connection)) - { - return connection; - } - } - - return null; - } - - internal async Task BindAsync() - { - await StartAsync(); - } - - public async ValueTask UnbindAsync(CancellationToken cancellationToken = default) - { - if (_stopped) - { - return; - } - - // TODO abort all streams and connections here? - _stopped = true; - - await DisposeAsync(); - } - - public async Task StartAsync(CancellationToken cancellationToken = default) - { - _api.RegistrationOpen(Encoding.ASCII.GetBytes(_transportContext.Options.RegistrationName)); - - _secConfig = await _api.CreateSecurityConfig(_transportContext.Options.Certificate); - - _session = _api.SessionOpen(_transportContext.Options.Alpn); - _log.LogDebug(0, "Started session"); - - _nativeObjPtr = _session.ListenerOpen(NativeCallbackHandler); - - SetCallbackHandler(); - - _session.SetIdleTimeout(_transportContext.Options.IdleTimeout); - _session.SetPeerBiDirectionalStreamCount(_transportContext.Options.MaxBidirectionalStreamCount); - _session.SetPeerUnidirectionalStreamCount(_transportContext.Options.MaxBidirectionalStreamCount); - - var address = MsQuicNativeMethods.Convert(EndPoint as IPEndPoint); - - MsQuicStatusException.ThrowIfFailed(_api.ListenerStartDelegate( - _nativeObjPtr, - ref address)); - } - - internal uint ListenerCallbackHandler( - ref ListenerEvent evt) - { - switch (evt.Type) - { - case QUIC_LISTENER_EVENT.NEW_CONNECTION: - { - - evt.Data.NewConnection.SecurityConfig = _secConfig.NativeObjPtr; - var msQuicConnection = new MsQuicConnection(_api, _transportContext, evt.Data.NewConnection.Connection); - _acceptConnectionQueue.Writer.TryWrite(msQuicConnection); - } - break; - default: - return MsQuicConstants.InternalError; - } - - return MsQuicConstants.Success; - } - - protected void StopAcceptingConnections() - { - _acceptConnectionQueue.Writer.TryComplete(); - } - - internal static uint NativeCallbackHandler( - IntPtr listener, - IntPtr context, - ref ListenerEvent connectionEventStruct) - { - var handle = GCHandle.FromIntPtr(context); - var quicListener = (MsQuicConnectionListener)handle.Target; - - return quicListener.ListenerCallbackHandler(ref connectionEventStruct); - } - - internal void SetCallbackHandler() - { - _handle = GCHandle.Alloc(this); - _listenerDelegate = new ListenerCallbackDelegate(NativeCallbackHandler); - _api.SetCallbackHandlerDelegate( - _nativeObjPtr, - _listenerDelegate, - GCHandle.ToIntPtr(_handle)); - } - - ~MsQuicConnectionListener() - { - Dispose(false); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - public ValueTask DisposeAsync() - { - if (_disposed) - { - return new ValueTask(); - } - - Dispose(true); - - return new ValueTask(); - } - - private void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - StopAcceptingConnections(); - - if (_nativeObjPtr != IntPtr.Zero) - { - _api.ListenerStopDelegate(_nativeObjPtr); - _api.ListenerCloseDelegate(_nativeObjPtr); - } - - _nativeObjPtr = IntPtr.Zero; - _api = null; - _disposed = true; - } - } -} diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicConstants.cs b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicConstants.cs deleted file mode 100644 index 69071d60a9..0000000000 --- a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicConstants.cs +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Runtime.InteropServices; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal -{ - internal static class MsQuicConstants - { - internal static uint InternalError = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Windows.InternalError : Linux.InternalError; - internal static uint Success = 0; - internal static uint Pending = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Windows.Pending : Linux.Pending; - private const uint SuccessConst = 0; - - internal static Func ErrorTypeFromErrorCode = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Windows.GetError : (Func)Linux.GetError; - - internal static class Windows - { - internal const uint Pending = 0x703E5; - internal const uint Continue = 0x704DE; - internal const uint OutOfMemory = 0x8007000E; - internal const uint InvalidParameter = 0x80070057; - internal const uint InvalidState = 0x8007139F; - internal const uint NotSupported = 0x80004002; - internal const uint NotFound = 0x80070490; - internal const uint BufferTooSmall = 0x8007007A; - internal const uint HandshakeFailure = 0x80410000; - internal const uint Aborted = 0x80004004; - internal const uint AddressInUse = 0x80072740; - internal const uint ConnectionTimeout = 0x800704CF; - internal const uint ConnectionIdle = 0x800704D4; - internal const uint InternalError = 0x80004005; - internal const uint ServerBusy = 0x800704C9; - internal const uint ProtocolError = 0x800704CD; - internal const uint VerNegError = 0x80410001; - - // TODO return better error messages here. - public static string GetError(uint status) - { - switch (status) - { - case SuccessConst: - return "SUCCESS"; - case Pending: - return "PENDING"; - case Continue: - return "CONTINUE"; - case OutOfMemory: - return "OUT_OF_MEMORY"; - case InvalidParameter: - return "INVALID_PARAMETER"; - case InvalidState: - return "INVALID_STATE"; - case NotSupported: - return "NOT_SUPPORTED"; - case NotFound: - return "NOT_FOUND"; - case BufferTooSmall: - return "BUFFER_TOO_SMALL"; - case HandshakeFailure: - return "HANDSHAKE_FAILURE"; - case Aborted: - return "ABORTED"; - case AddressInUse: - return "ADDRESS_IN_USE"; - case ConnectionTimeout: - return "CONNECTION_TIMEOUT"; - case ConnectionIdle: - return "CONNECTION_IDLE"; - case InternalError: - return "INTERNAL_ERROR"; - case ServerBusy: - return "SERVER_BUSY"; - case ProtocolError: - return "PROTOCOL_ERROR"; - case VerNegError: - return "VER_NEG_ERROR"; - } - return status.ToString(); - } - } - - internal static class Linux - { - internal const uint Pending = unchecked((uint)-2); - internal const uint Continue = unchecked((uint)-1); - internal const uint OutOfMemory = 12; - internal const uint InvalidParameter = 22; - internal const uint InvalidState = 200000002; - internal const uint NotSupported = 95; - internal const uint NotFound = 2; - internal const uint BufferTooSmall = 75; - internal const uint HandshakeFailure = 200000009; - internal const uint Aborted = 200000008; - internal const uint AddressInUse = 98; - internal const uint ConnectionTimeout = 110; - internal const uint ConnectionIdle = 200000011; - internal const uint InternalError = 200000012; - internal const uint ServerBusy = 200000007; - internal const uint ProtocolError = 200000013; - internal const uint VerNegError = 200000014; - - - public static string GetError(uint status) - { - switch (status) - { - case SuccessConst: - return "SUCCESS"; - case Pending: - return "PENDING"; - case Continue: - return "CONTINUE"; - case OutOfMemory: - return "OUT_OF_MEMORY"; - case InvalidParameter: - return "INVALID_PARAMETER"; - case InvalidState: - return "INVALID_STATE"; - case NotSupported: - return "NOT_SUPPORTED"; - case NotFound: - return "NOT_FOUND"; - case BufferTooSmall: - return "BUFFER_TOO_SMALL"; - case HandshakeFailure: - return "HANDSHAKE_FAILURE"; - case Aborted: - return "ABORTED"; - case AddressInUse: - return "ADDRESS_IN_USE"; - case ConnectionTimeout: - return "CONNECTION_TIMEOUT"; - case ConnectionIdle: - return "CONNECTION_IDLE"; - case InternalError: - return "INTERNAL_ERROR"; - case ServerBusy: - return "SERVER_BUSY"; - case ProtocolError: - return "PROTOCOL_ERROR"; - case VerNegError: - return "VER_NEG_ERROR"; - } - - return status.ToString(); - } - } - } -} diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicEnums.cs b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicEnums.cs deleted file mode 100644 index 37174ba4ec..0000000000 --- a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicEnums.cs +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal -{ - /// - /// Flags to pass when creating a certificate hash store. - /// - [Flags] - internal enum QUIC_CERT_HASH_STORE_FLAG : uint - { - NONE = 0, - MACHINE_CERT = 0x0001, - } - - /// - /// Flags to pass when creating a security config. - /// - [Flags] - internal enum QUIC_SEC_CONFIG_FLAG : uint - { - NONE = 0, - CERT_HASH = 0x00000001, - CERT_HASH_STORE = 0x00000002, - CERT_CONTEXT = 0x00000004, - CERT_FILE = 0x00000008, - ENABL_OCSP = 0x00000010, - CERT_NULL = 0xF0000000, - } - - internal enum QUIC_LISTENER_EVENT : byte - { - NEW_CONNECTION = 0 - } - - internal enum QUIC_CONNECTION_EVENT : byte - { - CONNECTED = 0, - SHUTDOWN_BEGIN = 1, - SHUTDOWN_BEGIN_PEER = 2, - SHUTDOWN_COMPLETE = 3, - LOCAL_ADDR_CHANGED = 4, - PEER_ADDR_CHANGED = 5, - NEW_STREAM = 6, - STREAMS_AVAILABLE = 7, - PEER_NEEDS_STREAMS = 8, - IDEAL_SEND_BUFFER = 9, - } - - [Flags] - internal enum QUIC_CONNECTION_SHUTDOWN_FLAG : uint - { - NONE = 0x0, - SILENT = 0x1 - } - - internal enum QUIC_PARAM_LEVEL : uint - { - REGISTRATION = 0, - SESSION = 1, - LISTENER = 2, - CONNECTION = 3, - TLS = 4, - STREAM = 5, - } - - internal enum QUIC_PARAM_REGISTRATION : uint - { - RETRY_MEMORY_PERCENT = 0, - CID_PREFIX = 1 - } - - internal enum QUIC_PARAM_SESSION : uint - { - TLS_TICKET_KEY = 0, - PEER_BIDI_STREAM_COUNT = 1, - PEER_UNIDI_STREAM_COUNT = 2, - IDLE_TIMEOUT = 3, - DISCONNECT_TIMEOUT = 4, - MAX_BYTES_PER_KEY = 5 - } - - internal enum QUIC_PARAM_LISTENER : uint - { - LOCAL_ADDRESS = 0 - } - - internal enum QUIC_PARAM_CONN : uint - { - QUIC_VERSION = 0, - LOCAL_ADDRESS = 1, - REMOTE_ADDRESS = 2, - IDLE_TIMEOUT = 3, - PEER_BIDI_STREAM_COUNT = 4, - PEER_UNIDI_STREAM_COUNT = 5, - LOCAL_BIDI_STREAM_COUNT = 6, - LOCAL_UNIDI_STREAM_COUNT = 7, - CLOSE_REASON_PHRASE = 8, - STATISTICS = 9, - STATISTICS_PLAT = 10, - CERT_VALIDATION_FLAGS = 11, - KEEP_ALIVE_ENABLED = 12, - DISCONNECT_TIMEOUT = 13, - SEC_CONFIG = 14, - USE_SEND_BUFFER = 15, - USE_PACING = 16, - SHARE_UDP_BINDING = 17, - IDEAL_PROCESSOR = 18, - MAX_STREAM_IDS = 19 - } - - internal enum QUIC_PARAM_STREAM : uint - { - ID = 0, - RECEIVE_ENABLED = 1, - ZERORTT_LENGTH = 2, - IDEAL_SEND_BUFFER = 3 - } - - internal enum QUIC_STREAM_EVENT : byte - { - START_COMPLETE = 0, - RECV = 1, - SEND_COMPLETE = 2, - PEER_SEND_CLOSE = 3, - PEER_SEND_ABORT = 4, - PEER_RECV_ABORT = 5, - SEND_SHUTDOWN_COMPLETE = 6, - SHUTDOWN_COMPLETE = 7, - IDEAL_SEND_BUFFER_SIZE = 8, - } - - [Flags] - internal enum QUIC_STREAM_OPEN_FLAG : uint - { - NONE = 0, - UNIDIRECTIONAL = 0x1, - ZERO_RTT = 0x2, - } - - [Flags] - internal enum QUIC_STREAM_START_FLAG : uint - { - NONE = 0, - FAIL_BLOCKED = 0x1, - IMMEDIATE = 0x2, - ASYNC = 0x4, - } - - [Flags] - internal enum QUIC_STREAM_SHUTDOWN_FLAG : uint - { - NONE = 0, - GRACEFUL = 0x1, - ABORT_SEND = 0x2, - ABORT_RECV = 0x4, - ABORT = 0x6, - IMMEDIATE = 0x8 - } - - [Flags] - internal enum QUIC_SEND_FLAG : uint - { - NONE = 0, - ALLOW_0_RTT = 0x00000001, - FIN = 0x00000002, - } - - [Flags] - internal enum QUIC_RECV_FLAG : byte - { - NONE = 0, - ZERO_RTT = 0x1, - FIN = 0x02 - } -} diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicNativeMethods.cs b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicNativeMethods.cs deleted file mode 100644 index d5512c5687..0000000000 --- a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicNativeMethods.cs +++ /dev/null @@ -1,590 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Net; -using System.Net.Sockets; -using System.Runtime.InteropServices; -using System.Text; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal -{ - /// - /// Contains all native delegates and structs that are used with MsQuic. - /// - internal unsafe static class MsQuicNativeMethods - { - internal const string dllName = "msquic"; - - [DllImport(dllName)] - internal static extern int MsQuicOpen(int version, out NativeApi* registration); - - [StructLayout(LayoutKind.Sequential)] - internal struct NativeApi - { - internal uint Version; - - internal IntPtr SetContext; - internal IntPtr GetContext; - internal IntPtr SetCallbackHandler; - - internal IntPtr SetParam; - internal IntPtr GetParam; - - internal IntPtr RegistrationOpen; - internal IntPtr RegistrationClose; - - internal IntPtr SecConfigCreate; - internal IntPtr SecConfigDelete; - - internal IntPtr SessionOpen; - internal IntPtr SessionClose; - internal IntPtr SessionShutdown; - - internal IntPtr ListenerOpen; - internal IntPtr ListenerClose; - internal IntPtr ListenerStart; - internal IntPtr ListenerStop; - - internal IntPtr ConnectionOpen; - internal IntPtr ConnectionClose; - internal IntPtr ConnectionShutdown; - internal IntPtr ConnectionStart; - - internal IntPtr StreamOpen; - internal IntPtr StreamClose; - internal IntPtr StreamStart; - internal IntPtr StreamShutdown; - internal IntPtr StreamSend; - internal IntPtr StreamReceiveComplete; - } - - internal delegate uint SetContextDelegate( - IntPtr Handle, - IntPtr Context); - - internal delegate IntPtr GetContextDelegate( - IntPtr Handle); - - internal delegate void SetCallbackHandlerDelegate( - IntPtr Handle, - Delegate del, - IntPtr Context); - - internal delegate uint SetParamDelegate( - IntPtr Handle, - uint Level, - uint Param, - uint BufferLength, - byte* Buffer); - - internal delegate uint GetParamDelegate( - IntPtr Handle, - uint Level, - uint Param, - out uint BufferLength, - out byte* Buffer); - - internal delegate uint RegistrationOpenDelegate(byte[] appName, out IntPtr RegistrationContext); - - internal delegate void RegistrationCloseDelegate(IntPtr RegistrationContext); - - [StructLayout(LayoutKind.Sequential)] - internal struct CertHash - { - internal const int ShaHashLength = 20; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = ShaHashLength)] - internal byte[] ShaHash; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct CertHashStore - { - internal const int ShaHashLength = 20; - internal const int StoreNameLength = 128; - - internal uint Flags; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = ShaHashLength)] - internal byte[] ShaHash; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = StoreNameLength)] - internal byte[] StoreName; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct CertFile - { - [MarshalAs(UnmanagedType.ByValArray)] - internal byte[] ShaHashUtf8; - [MarshalAs(UnmanagedType.ByValArray)] - internal byte[] StoreNameUtf8; - } - - internal delegate void SecConfigCreateCompleteDelegate(IntPtr Context, uint Status, IntPtr SecurityConfig); - - internal delegate uint SecConfigCreateDelegate( - IntPtr RegistrationContext, - uint Flags, - IntPtr Certificate, - [MarshalAs(UnmanagedType.LPStr)]string Principal, - IntPtr Context, - SecConfigCreateCompleteDelegate CompletionHandler); - - internal delegate void SecConfigDeleteDelegate( - IntPtr SecurityConfig); - - internal delegate uint SessionOpenDelegate( - IntPtr RegistrationContext, - byte[] utf8String, - IntPtr Context, - ref IntPtr Session); - - internal delegate void SessionCloseDelegate( - IntPtr Session); - - internal delegate void SessionShutdownDelegate( - IntPtr Session, - uint Flags, - ushort ErrorCode); - - [StructLayout(LayoutKind.Sequential)] - internal struct ListenerEvent - { - internal QUIC_LISTENER_EVENT Type; - internal ListenerEventDataUnion Data; - } - - [StructLayout(LayoutKind.Explicit)] - internal struct ListenerEventDataUnion - { - [FieldOffset(0)] - internal ListenerEventDataNewConnection NewConnection; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct ListenerEventDataNewConnection - { - internal IntPtr Info; - internal IntPtr Connection; - internal IntPtr SecurityConfig; - - internal static string BufferToString(IntPtr buffer, ushort bufferLength) - { - if (bufferLength == 0) - { - return ""; - } - - var utf8Bytes = new byte[bufferLength]; // TODO: Avoid extra alloc and copy. - Marshal.Copy(buffer, utf8Bytes, 0, bufferLength); - var str = Encoding.UTF8.GetString(utf8Bytes); - return str; - } - } - - [StructLayout(LayoutKind.Sequential)] - internal struct NewConnectionInfo - { - internal uint QuicVersion; - internal IntPtr LocalAddress; - internal IntPtr RemoteAddress; - internal ushort CryptoBufferLength; - internal ushort AlpnListLength; - internal ushort ServerNameLength; - internal IntPtr CryptoBuffer; - internal IntPtr AlpnList; - internal IntPtr ServerName; - } - - internal delegate uint ListenerCallbackDelegate( - IntPtr listener, - IntPtr context, - ref ListenerEvent evt); - - internal delegate uint ListenerOpenDelegate( - IntPtr session, - ListenerCallbackDelegate handler, - IntPtr context, - out IntPtr listener); - - internal delegate uint ListenerCloseDelegate( - IntPtr listener); - - internal delegate uint ListenerStartDelegate( - IntPtr listener, - ref SOCKADDR_INET localAddress); - - internal delegate uint ListenerStopDelegate( - IntPtr listener); - - [StructLayout(LayoutKind.Sequential)] - internal struct ConnectionEventDataConnected - { - internal bool EarlyDataAccepted; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct ConnectionEventDataShutdownBegin - { - internal uint Status; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct ConnectionEventDataShutdownBeginPeer - { - internal ushort ErrorCode; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct ConnectionEventDataShutdownComplete - { - internal bool TimedOut; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct ConnectionEventDataLocalAddrChanged - { - internal IntPtr Address; // TODO this needs to be IPV4 and IPV6 - } - - [StructLayout(LayoutKind.Sequential)] - internal struct ConnectionEventDataPeerAddrChanged - { - internal IntPtr Address; // TODO this needs to be IPV4 and IPV6 - } - - [StructLayout(LayoutKind.Sequential)] - internal struct ConnectionEventDataNewStream - { - internal IntPtr Stream; - internal QUIC_STREAM_OPEN_FLAG Flags; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct ConnectionEventDataStreamsAvailable - { - internal ushort BiDirectionalCount; - internal ushort UniDirectionalCount; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct ConnectionEventDataIdealSendBuffer - { - internal ulong NumBytes; - } - - [StructLayout(LayoutKind.Explicit)] - internal struct ConnectionEventDataUnion - { - [FieldOffset(0)] - internal ConnectionEventDataConnected Connected; - - [FieldOffset(0)] - internal ConnectionEventDataShutdownBegin ShutdownBegin; - - [FieldOffset(0)] - internal ConnectionEventDataShutdownBeginPeer ShutdownBeginPeer; - - [FieldOffset(0)] - internal ConnectionEventDataShutdownComplete ShutdownComplete; - - [FieldOffset(0)] - internal ConnectionEventDataLocalAddrChanged LocalAddrChanged; - - [FieldOffset(0)] - internal ConnectionEventDataPeerAddrChanged PeerAddrChanged; - - [FieldOffset(0)] - internal ConnectionEventDataNewStream NewStream; - - [FieldOffset(0)] - internal ConnectionEventDataStreamsAvailable StreamsAvailable; - - [FieldOffset(0)] - internal ConnectionEventDataIdealSendBuffer IdealSendBuffer; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct ConnectionEvent - { - internal QUIC_CONNECTION_EVENT Type; - internal ConnectionEventDataUnion Data; - - internal bool EarlyDataAccepted => Data.Connected.EarlyDataAccepted; - internal ulong NumBytes => Data.IdealSendBuffer.NumBytes; - internal IPEndPoint LocalAddress => null; // TODO - internal IPEndPoint PeerAddress => null; // TODO - internal uint ShutdownBeginStatus => Data.ShutdownBegin.Status; - internal ushort ShutdownBeginPeerStatus => Data.ShutdownBeginPeer.ErrorCode; - internal bool ShutdownTimedOut => Data.ShutdownComplete.TimedOut; - internal ushort BiDirectionalCount => Data.StreamsAvailable.BiDirectionalCount; - internal ushort UniDirectionalCount => Data.StreamsAvailable.UniDirectionalCount; - internal QUIC_STREAM_OPEN_FLAG StreamFlags => Data.NewStream.Flags; - } - - internal delegate uint ConnectionCallbackDelegate( - IntPtr Connection, - IntPtr Context, - ref ConnectionEvent Event); - - internal delegate uint ConnectionOpenDelegate( - IntPtr Session, - ConnectionCallbackDelegate Handler, - IntPtr Context, - out IntPtr Connection); - - internal delegate uint ConnectionCloseDelegate( - IntPtr Connection); - - internal delegate uint ConnectionStartDelegate( - IntPtr Connection, - ushort Family, - [MarshalAs(UnmanagedType.LPStr)] - string ServerName, - ushort ServerPort); - - internal delegate uint ConnectionShutdownDelegate( - IntPtr Connection, - uint Flags, - ushort ErrorCode); - - [StructLayout(LayoutKind.Sequential)] - internal struct StreamEventDataRecv - { - internal ulong AbsoluteOffset; - internal ulong TotalBufferLength; - internal QuicBuffer* Buffers; - internal uint BufferCount; - internal byte Flags; - } - - [StructLayout(LayoutKind.Explicit)] - internal struct StreamEventDataSendComplete - { - [FieldOffset(7)] - internal byte Canceled; - [FieldOffset(8)] - internal IntPtr ClientContext; - - internal bool IsCanceled() - { - return Canceled != 0; - } - } - - [StructLayout(LayoutKind.Sequential)] - internal struct StreamEventDataPeerSendAbort - { - internal ushort ErrorCode; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct StreamEventDataPeerRecvAbort - { - internal ushort ErrorCode; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct StreamEventDataSendShutdownComplete - { - internal bool Graceful; - } - - [StructLayout(LayoutKind.Explicit)] - internal struct StreamEventDataUnion - { - [FieldOffset(0)] - internal StreamEventDataRecv Recv; - - [FieldOffset(0)] - internal StreamEventDataSendComplete SendComplete; - - [FieldOffset(0)] - internal StreamEventDataPeerSendAbort PeerSendAbort; - - [FieldOffset(0)] - internal StreamEventDataPeerRecvAbort PeerRecvAbort; - - [FieldOffset(0)] - internal StreamEventDataSendShutdownComplete SendShutdownComplete; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct StreamEvent - { - internal QUIC_STREAM_EVENT Type; - internal StreamEventDataUnion Data; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct SOCKADDR_IN - { - internal ushort sin_family; - internal ushort sin_port; - internal byte sin_addr0; - internal byte sin_addr1; - internal byte sin_addr2; - internal byte sin_addr3; - - internal byte[] Address - { - get - { - return new byte[] { sin_addr0, sin_addr1, sin_addr2, sin_addr3 }; - } - } - } - - [StructLayout(LayoutKind.Sequential)] - internal struct SOCKADDR_IN6 - { - internal ushort sin6_family; - internal ushort sin6_port; - internal uint sin6_flowinfo; - internal byte sin6_addr0; - internal byte sin6_addr1; - internal byte sin6_addr2; - internal byte sin6_addr3; - internal byte sin6_addr4; - internal byte sin6_addr5; - internal byte sin6_addr6; - internal byte sin6_addr7; - internal byte sin6_addr8; - internal byte sin6_addr9; - internal byte sin6_addr10; - internal byte sin6_addr11; - internal byte sin6_addr12; - internal byte sin6_addr13; - internal byte sin6_addr14; - internal byte sin6_addr15; - internal uint sin6_scope_id; - - internal byte[] Address - { - get - { - return new byte[] { - sin6_addr0, sin6_addr1, sin6_addr2, sin6_addr3 , - sin6_addr4, sin6_addr5, sin6_addr6, sin6_addr7 , - sin6_addr8, sin6_addr9, sin6_addr10, sin6_addr11 , - sin6_addr12, sin6_addr13, sin6_addr14, sin6_addr15 }; - } - } - } - - [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)] - internal struct SOCKADDR_INET - { - [FieldOffset(0)] - internal SOCKADDR_IN Ipv4; - [FieldOffset(0)] - internal SOCKADDR_IN6 Ipv6; - [FieldOffset(0)] - internal ushort si_family; - } - - internal delegate uint StreamCallbackDelegate( - IntPtr Stream, - IntPtr Context, - StreamEvent Event); - - internal delegate uint StreamOpenDelegate( - IntPtr Connection, - uint Flags, - StreamCallbackDelegate Handler, - IntPtr Context, - out IntPtr Stream); - - internal delegate uint StreamStartDelegate( - IntPtr Stream, - uint Flags - ); - - internal delegate uint StreamCloseDelegate( - IntPtr Stream); - - internal delegate uint StreamShutdownDelegate( - IntPtr Stream, - uint Flags, - ushort ErrorCode); - - internal delegate uint StreamSendDelegate( - IntPtr Stream, - QuicBuffer* Buffers, - uint BufferCount, - uint Flags, - IntPtr ClientSendContext); - - internal delegate uint StreamReceiveCompleteDelegate( - IntPtr Stream, - ulong BufferLength); - - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct QuicBuffer - { - internal uint Length; - internal byte* Buffer; - } - - private const ushort IPv4 = 2; - private const ushort IPv6 = 23; - - public static SOCKADDR_INET Convert(IPEndPoint endpoint) - { - var socketAddress = new SOCKADDR_INET(); - var buffer = endpoint.Address.GetAddressBytes(); - if (endpoint.Address != IPAddress.Any && endpoint.Address != IPAddress.IPv6Any) - { - switch (endpoint.Address.AddressFamily) - { - case AddressFamily.InterNetwork: - socketAddress.Ipv4.sin_addr0 = buffer[0]; - socketAddress.Ipv4.sin_addr1 = buffer[1]; - socketAddress.Ipv4.sin_addr2 = buffer[2]; - socketAddress.Ipv4.sin_addr3 = buffer[3]; - socketAddress.Ipv4.sin_family = IPv4; - break; - case AddressFamily.InterNetworkV6: - socketAddress.Ipv6.sin6_addr0 = buffer[0]; - socketAddress.Ipv6.sin6_addr1 = buffer[1]; - socketAddress.Ipv6.sin6_addr2 = buffer[2]; - socketAddress.Ipv6.sin6_addr3 = buffer[3]; - socketAddress.Ipv6.sin6_addr4 = buffer[4]; - socketAddress.Ipv6.sin6_addr5 = buffer[5]; - socketAddress.Ipv6.sin6_addr6 = buffer[6]; - socketAddress.Ipv6.sin6_addr7 = buffer[7]; - socketAddress.Ipv6.sin6_addr8 = buffer[8]; - socketAddress.Ipv6.sin6_addr9 = buffer[9]; - socketAddress.Ipv6.sin6_addr10 = buffer[10]; - socketAddress.Ipv6.sin6_addr11 = buffer[11]; - socketAddress.Ipv6.sin6_addr12 = buffer[12]; - socketAddress.Ipv6.sin6_addr13 = buffer[13]; - socketAddress.Ipv6.sin6_addr14 = buffer[14]; - socketAddress.Ipv6.sin6_addr15 = buffer[15]; - socketAddress.Ipv6.sin6_family = IPv6; - break; - default: - throw new ArgumentException("Only IPv4 or IPv6 are supported"); - } - } - - SetPort(endpoint.Address.AddressFamily, ref socketAddress, endpoint.Port); - return socketAddress; - } - - private static void SetPort(AddressFamily addressFamily, ref SOCKADDR_INET socketAddrInet, int originalPort) - { - var convertedPort = (ushort)IPAddress.HostToNetworkOrder((short)originalPort); - switch (addressFamily) - { - case AddressFamily.InterNetwork: - socketAddrInet.Ipv4.sin_port = convertedPort; - break; - case AddressFamily.InterNetworkV6: - default: - socketAddrInet.Ipv6.sin6_port = convertedPort; - break; - } - } - } -} diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicStatusException.cs b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicStatusException.cs deleted file mode 100644 index c58b976482..0000000000 --- a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicStatusException.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal -{ - internal class MsQuicStatusException : Exception - { - internal MsQuicStatusException(uint status) - : this(status, null) - { - } - - internal MsQuicStatusException(uint status, string message) - : this(status, message, null) - { - } - - internal MsQuicStatusException(uint status, string message, Exception innerException) - : base(GetMessage(status, message), innerException) - { - Status = status; - } - - internal uint Status { get; } - - private static string GetMessage(uint status, string message) - { - var errorCode = MsQuicConstants.ErrorTypeFromErrorCode(status); - return $"Quic Error: {errorCode}. " + message; - } - - internal static void ThrowIfFailed(uint status, string message = null, Exception innerException = null) - { - if (!MsQuicStatusHelper.Succeeded(status)) - { - throw new MsQuicStatusException(status, message, innerException); - } - } - } -} diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicStatusHelper.cs b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicStatusHelper.cs deleted file mode 100644 index 23d15965a9..0000000000 --- a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicStatusHelper.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Runtime.InteropServices; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal -{ - internal static class MsQuicStatusHelper - { - internal static bool Succeeded(uint status) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return status < 0x80000000; - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return (int)status <= 0; - } - - return false; - } - } -} diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicStream.cs b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicStream.cs deleted file mode 100644 index a384455f45..0000000000 --- a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicStream.cs +++ /dev/null @@ -1,477 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Buffers; -using System.Diagnostics; -using System.IO.Pipelines; -using System.Runtime.InteropServices; -using System.Security.Cryptography.X509Certificates; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Connections.Features; -using Microsoft.AspNetCore.Http.Features; -using Microsoft.Extensions.Logging; -using static Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal.MsQuicNativeMethods; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal -{ - internal class MsQuicStream : TransportConnection, IQuicStreamFeature - { - private Task _processingTask; - private MsQuicConnection _connection; - private readonly CancellationTokenSource _streamClosedTokenSource = new CancellationTokenSource(); - private IMsQuicTrace _log; - private bool _disposed; - private IntPtr _nativeObjPtr; - private GCHandle _handle; - private StreamCallbackDelegate _delegate; - private string _connectionId; - private long _streamId = -1; - - internal ResettableCompletionSource _resettableCompletion; - private MemoryHandle[] _bufferArrays; - private GCHandle _sendBuffer; - - public MsQuicStream(MsQuicApi api, MsQuicConnection connection, MsQuicTransportContext context, QUIC_STREAM_OPEN_FLAG flags, IntPtr nativeObjPtr) - { - Debug.Assert(connection != null); - - Api = api; - _nativeObjPtr = nativeObjPtr; - - _connection = connection; - MemoryPool = context.Options.MemoryPoolFactory(); - _log = context.Log; - - ConnectionClosed = _streamClosedTokenSource.Token; - - var maxReadBufferSize = context.Options.MaxReadBufferSize.Value; - var maxWriteBufferSize = context.Options.MaxWriteBufferSize.Value; - _resettableCompletion = new ResettableCompletionSource(this); - - // TODO should we allow these PipeScheduler to be configurable here? - var inputOptions = new PipeOptions(MemoryPool, PipeScheduler.ThreadPool, PipeScheduler.Inline, maxReadBufferSize, maxReadBufferSize / 2, useSynchronizationContext: false); - var outputOptions = new PipeOptions(MemoryPool, PipeScheduler.Inline, PipeScheduler.ThreadPool, maxWriteBufferSize, maxWriteBufferSize / 2, useSynchronizationContext: false); - - var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions); - - Features.Set(this); - - // TODO populate the ITlsConnectionFeature (requires client certs). - Features.Set(new FakeTlsConnectionFeature()); - if (flags.HasFlag(QUIC_STREAM_OPEN_FLAG.UNIDIRECTIONAL)) - { - IsUnidirectional = true; - } - - Transport = pair.Transport; - Application = pair.Application; - - SetCallbackHandler(); - - _processingTask = ProcessSends(); - } - - public override MemoryPool MemoryPool { get; } - public PipeWriter Input => Application.Output; - public PipeReader Output => Application.Input; - - public bool IsUnidirectional { get; } - - public long StreamId - { - get - { - if (_streamId == -1) - { - _streamId = GetStreamId(); - } - - return _streamId; - } - } - - public override string ConnectionId { - get - { - if (_connectionId == null) - { - _connectionId = $"{_connection.ConnectionId}:{StreamId}"; - } - return _connectionId; - } - set - { - _connectionId = value; - } - } - - private async Task ProcessSends() - { - var output = Output; - try - { - while (true) - { - var result = await output.ReadAsync(); - _log.LogDebug(0, "Handling send event"); - - if (result.IsCanceled) - { - // TODO how to get abort codepath sync'd - ShutDown(QUIC_STREAM_SHUTDOWN_FLAG.ABORT, 0); - break; - } - - var buffer = result.Buffer; - - var end = buffer.End; - var isCompleted = result.IsCompleted; - if (!buffer.IsEmpty) - { - await SendAsync(buffer, QUIC_SEND_FLAG.NONE); - } - - output.AdvanceTo(end); - - if (isCompleted) - { - // Once the stream pipe is closed, shutdown the stream. - ShutDown(QUIC_STREAM_SHUTDOWN_FLAG.GRACEFUL, 0); - break; - } - } - } - catch (Exception) - { - ShutDown(QUIC_STREAM_SHUTDOWN_FLAG.ABORT, 0); - } - } - - internal uint HandleEvent(ref MsQuicNativeMethods.StreamEvent evt) - { - var status = MsQuicConstants.Success; - - switch (evt.Type) - { - case QUIC_STREAM_EVENT.START_COMPLETE: - status = HandleStartComplete(); - break; - case QUIC_STREAM_EVENT.RECV: - { - HandleEventRecv( - ref evt); - } - break; - case QUIC_STREAM_EVENT.SEND_COMPLETE: - { - status = HandleEventSendComplete(ref evt); - } - break; - case QUIC_STREAM_EVENT.PEER_SEND_CLOSE: - { - status = HandleEventPeerSendClose(); - } - break; - // TODO figure out difference between SEND_ABORT and RECEIVE_ABORT - case QUIC_STREAM_EVENT.PEER_SEND_ABORT: - { - _streamClosedTokenSource.Cancel(); - status = HandleEventPeerSendAbort(); - } - break; - case QUIC_STREAM_EVENT.PEER_RECV_ABORT: - { - _streamClosedTokenSource.Cancel(); - status = HandleEventPeerRecvAbort(); - } - break; - case QUIC_STREAM_EVENT.SEND_SHUTDOWN_COMPLETE: - { - status = HandleEventSendShutdownComplete(ref evt); - } - break; - case QUIC_STREAM_EVENT.SHUTDOWN_COMPLETE: - { - Close(); - return MsQuicConstants.Success; - } - - default: - break; - } - return status; - } - - private uint HandleEventPeerRecvAbort() - { - return MsQuicConstants.Success; - } - - private uint HandleEventPeerSendAbort() - { - return MsQuicConstants.Success; - } - - private uint HandleStartComplete() - { - _resettableCompletion.Complete(MsQuicConstants.Success); - return MsQuicConstants.Success; - } - - private uint HandleEventSendShutdownComplete(ref MsQuicNativeMethods.StreamEvent evt) - { - return MsQuicConstants.Success; - } - - private uint HandleEventPeerSendClose() - { - Input.Complete(); - return MsQuicConstants.Success; - } - - public uint HandleEventSendComplete(ref MsQuicNativeMethods.StreamEvent evt) - { - _sendBuffer.Free(); - foreach (var gchBufferArray in _bufferArrays) - { - gchBufferArray.Dispose(); - } - _resettableCompletion.Complete(evt.Data.PeerRecvAbort.ErrorCode); - return MsQuicConstants.Success; - } - - protected void HandleEventRecv(ref MsQuicNativeMethods.StreamEvent evt) - { - static unsafe void CopyToBuffer(Span buffer, StreamEvent evt) - { - var length = (int)evt.Data.Recv.Buffers[0].Length; - new Span(evt.Data.Recv.Buffers[0].Buffer, length).CopyTo(buffer); - } - - _log.LogDebug(0, "Handling receive event"); - var input = Input; - var length = (int)evt.Data.Recv.TotalBufferLength; - var result = input.GetSpan(length); - CopyToBuffer(result, evt); - - input.Advance(length); - - var flushTask = input.FlushAsync(); - - if (!flushTask.IsCompletedSuccessfully) - { - _ = AwaitFlush(flushTask); - - return; - } - - async Task AwaitFlush(ValueTask ft) - { - await ft; - // TODO figure out when to call these for receive. - EnableReceive(); - ReceiveComplete(length); - } - } - - public override void Abort(ConnectionAbortedException abortReason) - { - Shutdown(abortReason); - - // Cancel ProcessSends loop after calling shutdown to ensure the correct _shutdownReason gets set. - Output.CancelPendingRead(); - } - - private void Shutdown(Exception shutdownReason) - { - } - - public MsQuicApi Api { get; set; } - - internal static uint NativeCallbackHandler( - IntPtr stream, - IntPtr context, - StreamEvent connectionEventStruct) - { - var handle = GCHandle.FromIntPtr(context); - var quicStream = (MsQuicStream)handle.Target; - - return quicStream.HandleEvent(ref connectionEventStruct); - } - - public void SetCallbackHandler() - { - _handle = GCHandle.Alloc(this); - - _delegate = new StreamCallbackDelegate(NativeCallbackHandler); - Api.SetCallbackHandlerDelegate( - _nativeObjPtr, - _delegate, - GCHandle.ToIntPtr(_handle)); - } - - public unsafe ValueTask SendAsync( - ReadOnlySequence buffers, - QUIC_SEND_FLAG flags) - { - var bufferCount = 0; - foreach (var memory in buffers) - { - bufferCount++; - } - - var quicBufferArray = new QuicBuffer[bufferCount]; - _bufferArrays = new MemoryHandle[bufferCount]; - - var i = 0; - foreach (var memory in buffers) - { - var handle = memory.Pin(); - _bufferArrays[i] = handle; - quicBufferArray[i].Length = (uint)memory.Length; - quicBufferArray[i].Buffer = (byte*)handle.Pointer; - i++; - } - - _sendBuffer = GCHandle.Alloc(quicBufferArray, GCHandleType.Pinned); - - var quicBufferPointer = (QuicBuffer*)Marshal.UnsafeAddrOfPinnedArrayElement(quicBufferArray, 0); - - var status = Api.StreamSendDelegate( - _nativeObjPtr, - quicBufferPointer, - (uint)bufferCount, - (uint)flags, - _nativeObjPtr); - - MsQuicStatusException.ThrowIfFailed(status); - - return _resettableCompletion.GetValueTask(); - } - - public ValueTask StartAsync() - { - var status = Api.StreamStartDelegate( - _nativeObjPtr, - (uint)QUIC_STREAM_START_FLAG.ASYNC); - - MsQuicStatusException.ThrowIfFailed(status); - return _resettableCompletion.GetValueTask(); - } - - public void ReceiveComplete(int bufferLength) - { - var status = (uint)Api.StreamReceiveComplete(_nativeObjPtr, (ulong)bufferLength); - MsQuicStatusException.ThrowIfFailed(status); - } - - public void ShutDown( - QUIC_STREAM_SHUTDOWN_FLAG flags, - ushort errorCode) - { - var status = (uint)Api.StreamShutdownDelegate( - _nativeObjPtr, - (uint)flags, - errorCode); - MsQuicStatusException.ThrowIfFailed(status); - } - - public void Close() - { - var status = (uint)Api.StreamCloseDelegate?.Invoke(_nativeObjPtr); - MsQuicStatusException.ThrowIfFailed(status); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - public unsafe void EnableReceive() - { - var val = true; - var buffer = new QuicBuffer() - { - Length = sizeof(bool), - Buffer = (byte*)&val - }; - SetParam(QUIC_PARAM_STREAM.RECEIVE_ENABLED, buffer); - } - - private unsafe long GetStreamId() - { - byte* ptr = stackalloc byte[sizeof(long)]; - var buffer = new QuicBuffer - { - Length = sizeof(long), - Buffer = ptr - }; - GetParam(QUIC_PARAM_STREAM.ID, buffer); - return *(long*)ptr; - } - - private void GetParam( - QUIC_PARAM_STREAM param, - QuicBuffer buf) - { - MsQuicStatusException.ThrowIfFailed(Api.UnsafeGetParam( - _nativeObjPtr, - (uint)QUIC_PARAM_LEVEL.STREAM, - (uint)param, - ref buf)); - } - - private void SetParam( - QUIC_PARAM_STREAM param, - QuicBuffer buf) - { - MsQuicStatusException.ThrowIfFailed(Api.UnsafeSetParam( - _nativeObjPtr, - (uint)QUIC_PARAM_LEVEL.STREAM, - (uint)param, - buf)); - } - - ~MsQuicStream() - { - Dispose(false); - } - - private void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (_nativeObjPtr != IntPtr.Zero) - { - Api.StreamCloseDelegate?.Invoke(_nativeObjPtr); - } - - _handle.Free(); - _nativeObjPtr = IntPtr.Zero; - Api = null; - - _disposed = true; - } - } - - internal class FakeTlsConnectionFeature : ITlsConnectionFeature - { - public FakeTlsConnectionFeature() - { - } - - public X509Certificate2 ClientCertificate { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - - public Task GetClientCertificateAsync(CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicTransportContext.cs b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicTransportContext.cs deleted file mode 100644 index 42612c591b..0000000000 --- a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicTransportContext.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.Extensions.Hosting; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal -{ - internal class MsQuicTransportContext - { - public MsQuicTransportContext(IHostApplicationLifetime appLifetime, IMsQuicTrace log, MsQuicTransportOptions options) - { - AppLifetime = appLifetime; - Log = log; - Options = options; - } - - public IHostApplicationLifetime AppLifetime { get; } - public IMsQuicTrace Log { get; } - public MsQuicTransportOptions Options { get; } - } -} diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/QuicSecConfig.cs b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/QuicSecConfig.cs deleted file mode 100644 index 670efa71b2..0000000000 --- a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/QuicSecConfig.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal -{ - internal class QuicSecConfig : IDisposable - { - private bool _disposed; - private MsQuicApi _registration; - - public QuicSecConfig(MsQuicApi registration, IntPtr nativeObjPtr) - { - _registration = registration; - NativeObjPtr = nativeObjPtr; - } - - public IntPtr NativeObjPtr { get; private set; } - - public void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - _registration.SecConfigDeleteDelegate?.Invoke(NativeObjPtr); - NativeObjPtr = IntPtr.Zero; - _disposed = true; - } - - ~QuicSecConfig() - { - Dispose(disposing: false); - } - } -} diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/QuicSession.cs b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/QuicSession.cs deleted file mode 100644 index ba2bf8c868..0000000000 --- a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/QuicSession.cs +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Net; -using System.Threading.Tasks; -using static Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal.MsQuicNativeMethods; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal -{ - internal sealed class QuicSession : IDisposable - { - private bool _disposed = false; - private IntPtr _nativeObjPtr; - private MsQuicApi _registration; - - internal QuicSession(MsQuicApi registration, IntPtr nativeObjPtr) - { - _registration = registration; - _nativeObjPtr = nativeObjPtr; - } - - public async ValueTask ConnectionOpenAsync(IPEndPoint endpoint, MsQuicTransportContext context) - { - var status = _registration.ConnectionOpenDelegate( - _nativeObjPtr, - MsQuicConnection.NativeCallbackHandler, - IntPtr.Zero, - out var connectionPtr); - - MsQuicStatusException.ThrowIfFailed(status); - - var msQuicConnection = new MsQuicConnection(_registration, context, connectionPtr); - - await msQuicConnection.StartAsync((ushort)endpoint.AddressFamily, endpoint.Address.ToString(), (ushort)endpoint.Port); - - return msQuicConnection; - } - - internal IntPtr ListenerOpen(ListenerCallbackDelegate callback) - { - var status = _registration.ListenerOpenDelegate( - _nativeObjPtr, - callback, - IntPtr.Zero, - out var listenerPointer - ); - - MsQuicStatusException.ThrowIfFailed(status); - - return listenerPointer; - } - - public void ShutDown( - QUIC_CONNECTION_SHUTDOWN_FLAG Flags, - ushort ErrorCode) - { - _registration.SessionShutdownDelegate( - _nativeObjPtr, - (uint)Flags, - ErrorCode); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - public void SetPeerBiDirectionalStreamCount(ushort count) - { - SetUshortParamter(QUIC_PARAM_SESSION.PEER_BIDI_STREAM_COUNT, count); - } - - public void SetPeerUnidirectionalStreamCount(ushort count) - { - SetUshortParamter(QUIC_PARAM_SESSION.PEER_UNIDI_STREAM_COUNT, count); - } - - private unsafe void SetUshortParamter(QUIC_PARAM_SESSION param, ushort count) - { - var buffer = new MsQuicNativeMethods.QuicBuffer() - { - Length = sizeof(ushort), - Buffer = (byte*)&count - }; - - SetParam(param, buffer); - } - - public void SetDisconnectTimeout(TimeSpan timeout) - { - SetULongParamter(QUIC_PARAM_SESSION.DISCONNECT_TIMEOUT, (ulong)timeout.TotalMilliseconds); - } - - public void SetIdleTimeout(TimeSpan timeout) - { - SetULongParamter(QUIC_PARAM_SESSION.IDLE_TIMEOUT, (ulong)timeout.TotalMilliseconds); - - } - private unsafe void SetULongParamter(QUIC_PARAM_SESSION param, ulong count) - { - var buffer = new MsQuicNativeMethods.QuicBuffer() - { - Length = sizeof(ulong), - Buffer = (byte*)&count - }; - SetParam(param, buffer); - } - - private void SetParam( - QUIC_PARAM_SESSION param, - MsQuicNativeMethods.QuicBuffer buf) - { - MsQuicStatusException.ThrowIfFailed(_registration.UnsafeSetParam( - _nativeObjPtr, - (uint)QUIC_PARAM_LEVEL.SESSION, - (uint)param, - buf)); - } - - ~QuicSession() - { - Dispose(false); - } - - private void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - _registration.SessionCloseDelegate?.Invoke(_nativeObjPtr); - _nativeObjPtr = IntPtr.Zero; - _registration = null; - - _disposed = true; - } - } -} diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/ResettableCompletionSource.cs b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/ResettableCompletionSource.cs deleted file mode 100644 index 9c71211f96..0000000000 --- a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/ResettableCompletionSource.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Threading.Tasks; -using System.Threading.Tasks.Sources; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal -{ - internal class ResettableCompletionSource : IValueTaskSource - { - private ManualResetValueTaskSourceCore _valueTaskSource; - private readonly MsQuicStream _stream; - - public ResettableCompletionSource(MsQuicStream stream) - { - _stream = stream; - _valueTaskSource.RunContinuationsAsynchronously = true; - } - - public ValueTask GetValueTask() - { - return new ValueTask(this, _valueTaskSource.Version); - } - - public uint GetResult(short token) - { - var isValid = token == _valueTaskSource.Version; - try - { - return _valueTaskSource.GetResult(token); - } - finally - { - if (isValid) - { - _valueTaskSource.Reset(); - _stream._resettableCompletion = this; - } - } - } - - public ValueTaskSourceStatus GetStatus(short token) - { - return _valueTaskSource.GetStatus(token); - } - - public void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) - { - _valueTaskSource.OnCompleted(continuation, state, token, flags); - } - - public void Complete(uint result) - { - _valueTaskSource.SetResult(result); - } - } -} diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/UIntExtensions.cs b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/UIntExtensions.cs deleted file mode 100644 index 31d2b41fb6..0000000000 --- a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/UIntExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Runtime.InteropServices; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal -{ - internal static class UIntExtensions - { - internal static bool Succeeded(this uint status) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return status >= 0x80000000; - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return (int)status <= 0; - } - - return false; - } - } -} diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/MsQuicConnectionFactory.cs b/src/Servers/Kestrel/Transport.MsQuic/src/MsQuicConnectionFactory.cs deleted file mode 100644 index 98d585935f..0000000000 --- a/src/Servers/Kestrel/Transport.MsQuic/src/MsQuicConnectionFactory.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic -{ - public class MsQuicConnectionFactory : IConnectionFactory - { - private MsQuicApi _api; - private QuicSession _session; - private bool _started; - private MsQuicTransportContext _transportContext; - - public MsQuicConnectionFactory(IOptions options, IHostApplicationLifetime lifetime, ILoggerFactory loggerFactory) - { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - _api = new MsQuicApi(); - var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Client"); - var trace = new MsQuicTrace(logger); - - _transportContext = new MsQuicTransportContext(lifetime, trace, options.Value); - } - - public async ValueTask ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken = default) - { - if (!(endPoint is IPEndPoint ipEndPoint)) - { - throw new NotSupportedException($"{endPoint} is not supported"); - } - - if (!_started) - { - _started = true; - await StartAsync(); - } - - var connection = await _session.ConnectionOpenAsync(ipEndPoint, _transportContext); - return connection; - } - - private ValueTask StartAsync() - { - _api.RegistrationOpen(Encoding.ASCII.GetBytes(_transportContext.Options.RegistrationName)); - _session = _api.SessionOpen(_transportContext.Options.Alpn); - _session.SetIdleTimeout(_transportContext.Options.IdleTimeout); - _session.SetPeerBiDirectionalStreamCount(_transportContext.Options.MaxBidirectionalStreamCount); - _session.SetPeerUnidirectionalStreamCount(_transportContext.Options.MaxBidirectionalStreamCount); - return new ValueTask(); - } - } -} diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/MsQuicTransportFactory.cs b/src/Servers/Kestrel/Transport.MsQuic/src/MsQuicTransportFactory.cs deleted file mode 100644 index c3200982c5..0000000000 --- a/src/Servers/Kestrel/Transport.MsQuic/src/MsQuicTransportFactory.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic -{ - public class MsQuicTransportFactory : IMultiplexedConnectionListenerFactory - { - private MsQuicTrace _log; - private IHostApplicationLifetime _applicationLifetime; - private MsQuicTransportOptions _options; - - public MsQuicTransportFactory(IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions options) - { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - if (loggerFactory == null) - { - throw new ArgumentNullException(nameof(loggerFactory)); - } - - var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic"); - _log = new MsQuicTrace(logger); - _applicationLifetime = applicationLifetime; - _options = options.Value; - } - - public async ValueTask BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default) - { - var transport = new MsQuicConnectionListener(_options, _applicationLifetime, _log, endpoint); - await transport.BindAsync(); - return transport; - } - } -} diff --git a/src/Servers/Kestrel/Transport.Quic/README.md b/src/Servers/Kestrel/Transport.Quic/README.md new file mode 100644 index 0000000000..1c0f135cdb --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/README.md @@ -0,0 +1,3 @@ +For external contributors, msquic.dll isn't available publicly yet. See https://github.com/aspnet/Announcements/issues/393. + +Credit to Diwakar Mantha and the Kaizala team for the MsQuic interop code. \ No newline at end of file diff --git a/src/Servers/Kestrel/Transport.MsQuic/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj b/src/Servers/Kestrel/Transport.Quic/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj similarity index 95% rename from src/Servers/Kestrel/Transport.MsQuic/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj rename to src/Servers/Kestrel/Transport.Quic/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj index d75fa659ea..d242680513 100644 --- a/src/Servers/Kestrel/Transport.MsQuic/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj +++ b/src/Servers/Kestrel/Transport.Quic/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj @@ -4,7 +4,7 @@ $(DefaultNetCoreTargetFramework) - + diff --git a/src/Servers/Kestrel/Transport.MsQuic/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.netcoreapp.cs b/src/Servers/Kestrel/Transport.Quic/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.netcoreapp.cs similarity index 65% rename from src/Servers/Kestrel/Transport.MsQuic/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.netcoreapp.cs rename to src/Servers/Kestrel/Transport.Quic/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.netcoreapp.cs index 979742fbd3..bb1157951d 100644 --- a/src/Servers/Kestrel/Transport.MsQuic/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.netcoreapp.cs +++ b/src/Servers/Kestrel/Transport.Quic/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.netcoreapp.cs @@ -5,27 +5,27 @@ namespace Microsoft.AspNetCore.Hosting { public static partial class WebHostBuilderMsQuicExtensions { - public static Microsoft.AspNetCore.Hosting.IWebHostBuilder UseMsQuic(this Microsoft.AspNetCore.Hosting.IWebHostBuilder hostBuilder) { throw null; } - public static Microsoft.AspNetCore.Hosting.IWebHostBuilder UseMsQuic(this Microsoft.AspNetCore.Hosting.IWebHostBuilder hostBuilder, System.Action configureOptions) { throw null; } + public static Microsoft.AspNetCore.Hosting.IWebHostBuilder UseQuic(this Microsoft.AspNetCore.Hosting.IWebHostBuilder hostBuilder) { throw null; } + public static Microsoft.AspNetCore.Hosting.IWebHostBuilder UseQuic(this Microsoft.AspNetCore.Hosting.IWebHostBuilder hostBuilder, System.Action configureOptions) { throw null; } } } -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic { - public partial class MsQuicConnectionFactory : Microsoft.AspNetCore.Connections.IConnectionFactory + public partial class QuicConnectionFactory : Microsoft.AspNetCore.Connections.IConnectionFactory { - public MsQuicConnectionFactory(Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Hosting.IHostApplicationLifetime lifetime, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } + public QuicConnectionFactory(Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.ValueTask ConnectAsync(System.Net.EndPoint endPoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } - public partial class MsQuicTransportFactory : Microsoft.AspNetCore.Connections.IConnectionListenerFactory, Microsoft.AspNetCore.Connections.IMultiplexedConnectionListenerFactory + public partial class QuicTransportFactory : Microsoft.AspNetCore.Connections.IConnectionListenerFactory, Microsoft.AspNetCore.Connections.IMultiplexedConnectionListenerFactory { - public MsQuicTransportFactory(Microsoft.Extensions.Hosting.IHostApplicationLifetime applicationLifetime, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.Extensions.Options.IOptions options) { } - [System.Diagnostics.DebuggerStepThroughAttribute] + public QuicTransportFactory(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.Extensions.Options.IOptions options) { } public System.Threading.Tasks.ValueTask BindAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } - public partial class MsQuicTransportOptions + public partial class QuicTransportOptions { - public MsQuicTransportOptions() { } + public QuicTransportOptions() { } + public long AbortErrorCode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public string Alpn { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public System.Security.Cryptography.X509Certificates.X509Certificate2 Certificate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public System.TimeSpan IdleTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/FakeTlsConnectionFeature.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/FakeTlsConnectionFeature.cs new file mode 100644 index 0000000000..06f87a37ab --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/FakeTlsConnectionFeature.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal +{ + internal class FakeTlsConnectionFeature : ITlsConnectionFeature + { + public FakeTlsConnectionFeature() + { + } + + public X509Certificate2 ClientCertificate { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public Task GetClientCertificateAsync(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/IMsQuicTrace.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/IQuicTrace.cs similarity index 62% rename from src/Servers/Kestrel/Transport.MsQuic/src/Internal/IMsQuicTrace.cs rename to src/Servers/Kestrel/Transport.Quic/src/Internal/IQuicTrace.cs index 07c2cf0436..c70d461430 100644 --- a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/IMsQuicTrace.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/IQuicTrace.cs @@ -4,13 +4,16 @@ using System; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal { - internal interface IMsQuicTrace : ILogger + internal interface IQuicTrace : ILogger { void NewConnection(string connectionId); void NewStream(string streamId); void ConnectionError(string connectionId, Exception ex); void StreamError(string streamId, Exception ex); + void StreamPause(string streamId); + void StreamResume(string streamId); + void StreamShutdownWrite(string streamId, Exception ex); } } diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs new file mode 100644 index 0000000000..3eb982991f --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionContext.cs @@ -0,0 +1,80 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net.Quic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Abstractions.Features; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal +{ + internal class QuicConnectionContext : TransportConnection, IQuicStreamListenerFeature, IQuicCreateStreamFeature + { + private QuicConnection _connection; + private readonly QuicTransportContext _context; + private readonly IQuicTrace _log; + + private ValueTask _closeTask; + + public QuicConnectionContext(QuicConnection connection, QuicTransportContext context) + { + _log = context.Log; + _context = context; + _connection = connection; + Features.Set(new FakeTlsConnectionFeature()); + Features.Set(this); + Features.Set(this); + + _log.NewConnection(ConnectionId); + } + + public ValueTask StartUnidirectionalStreamAsync() + { + var stream = _connection.OpenUnidirectionalStream(); + return new ValueTask(new QuicStreamContext(stream, this, _context)); + } + + public ValueTask StartBidirectionalStreamAsync() + { + var stream = _connection.OpenBidirectionalStream(); + return new ValueTask(new QuicStreamContext(stream, this, _context)); + } + + public override async ValueTask DisposeAsync() + { + try + { + if (_closeTask != default) + { + _closeTask = _connection.CloseAsync(errorCode: 0); + await _closeTask; + } + else + { + await _closeTask; + } + } + catch (Exception ex) + { + _log.LogWarning(ex, "Failed to gracefully shutdown connection."); + } + + _connection.Dispose(); + } + + public override void Abort(ConnectionAbortedException abortReason) + { + _closeTask = _connection.CloseAsync(errorCode: _context.Options.AbortErrorCode); + } + + public async ValueTask AcceptAsync() + { + var stream = await _connection.AcceptStreamAsync(); + return new QuicStreamContext(stream, this, _context); + } + } +} diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs new file mode 100644 index 0000000000..89d8d2e576 --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicConnectionListener.cs @@ -0,0 +1,64 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Quic; +using System.Net.Security; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal +{ + /// + /// Listens for new Quic Connections. + /// + internal class QuicConnectionListener : IConnectionListener, IAsyncDisposable + { + private readonly IQuicTrace _log; + private bool _disposed; + private readonly QuicTransportContext _context; + private readonly QuicListener _listener; + + public QuicConnectionListener(QuicTransportOptions options, IQuicTrace log, EndPoint endpoint) + { + _log = log; + _context = new QuicTransportContext(_log, options); + EndPoint = endpoint; + var sslConfig = new SslServerAuthenticationOptions(); + sslConfig.ServerCertificate = options.Certificate; + sslConfig.ApplicationProtocols = new List() { new SslApplicationProtocol(options.Alpn) }; + _listener = new QuicListener(QuicImplementationProviders.MsQuic, endpoint as IPEndPoint, sslConfig); + _listener.Start(); + } + + public EndPoint EndPoint { get; set; } + + public async ValueTask AcceptAsync(CancellationToken cancellationToken = default) + { + var quicConnection = await _listener.AcceptConnectionAsync(cancellationToken); + return new QuicConnectionContext(quicConnection, _context); + } + + public async ValueTask UnbindAsync(CancellationToken cancellationToken = default) + { + await DisposeAsync(); + } + + public ValueTask DisposeAsync() + { + if (_disposed) + { + return new ValueTask(); + } + + _disposed = true; + + _listener.Dispose(); + + return new ValueTask(); + } + } +} diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs new file mode 100644 index 0000000000..625259c473 --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicStreamContext.cs @@ -0,0 +1,316 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Buffers; +using System.IO.Pipelines; +using System.Net.Quic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal +{ + internal class QuicStreamContext : TransportConnection, IQuicStreamFeature + { + private readonly Task _processingTask; + private readonly QuicStream _stream; + private readonly QuicConnectionContext _connection; + private readonly QuicTransportContext _context; + private readonly CancellationTokenSource _streamClosedTokenSource = new CancellationTokenSource(); + private readonly IQuicTrace _log; + private string _connectionId; + private const int MinAllocBufferSize = 4096; + private volatile Exception _shutdownReason; + private readonly TaskCompletionSource _waitForConnectionClosedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + private readonly object _shutdownLock = new object(); + + public QuicStreamContext(QuicStream stream, QuicConnectionContext connection, QuicTransportContext context) + { + _stream = stream; + _connection = connection; + _context = context; + _log = context.Log; + + ConnectionClosed = _streamClosedTokenSource.Token; + + var maxReadBufferSize = context.Options.MaxReadBufferSize.Value; + var maxWriteBufferSize = context.Options.MaxWriteBufferSize.Value; + + // TODO should we allow these PipeScheduler to be configurable here? + var inputOptions = new PipeOptions(MemoryPool, PipeScheduler.ThreadPool, PipeScheduler.Inline, maxReadBufferSize, maxReadBufferSize / 2, useSynchronizationContext: false); + var outputOptions = new PipeOptions(MemoryPool, PipeScheduler.Inline, PipeScheduler.ThreadPool, maxWriteBufferSize, maxWriteBufferSize / 2, useSynchronizationContext: false); + + var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions); + + Features.Set(this); + + // TODO populate the ITlsConnectionFeature (requires client certs). + Features.Set(new FakeTlsConnectionFeature()); + CanRead = stream.CanRead; + CanWrite = stream.CanWrite; + + Transport = pair.Transport; + Application = pair.Application; + + _processingTask = StartAsync(); + } + + public override MemoryPool MemoryPool { get; } + public PipeWriter Input => Application.Output; + public PipeReader Output => Application.Input; + + public bool CanRead { get; } + public bool CanWrite { get; } + + public long StreamId + { + get + { + return _stream.StreamId; + } + } + + public override string ConnectionId + { + get + { + if (_connectionId == null) + { + _connectionId = $"{_connection.ConnectionId}:{StreamId}"; + } + return _connectionId; + } + set + { + _connectionId = value; + } + } + + private async Task StartAsync() + { + try + { + // Spawn send and receive logic + var receiveTask = DoReceive(); + var sendTask = DoSend(); + + // Now wait for both to complete + await receiveTask; + await sendTask; + + } + catch (Exception ex) + { + _log.LogError(0, ex, $"Unexpected exception in {nameof(QuicStreamContext)}.{nameof(StartAsync)}."); + } + } + + private async Task DoReceive() + { + Exception error = null; + + try + { + await ProcessReceives(); + } + catch (QuicException ex) + { + // This could be ignored if _shutdownReason is already set. + error = new ConnectionResetException(ex.Message, ex); + } + catch (Exception ex) + { + // This is unexpected. + error = ex; + _log.StreamError(ConnectionId, error); + } + finally + { + // If Shutdown() has already bee called, assume that was the reason ProcessReceives() exited. + Input.Complete(_shutdownReason ?? error); + + FireStreamClosed(); + + await _waitForConnectionClosedTcs.Task; + } + } + + private async Task ProcessReceives() + { + var input = Input; + while (true) + { + var buffer = Input.GetMemory(MinAllocBufferSize); + var bytesReceived = await _stream.ReadAsync(buffer); + + if (bytesReceived == 0) + { + // Read completed. + break; + } + + input.Advance(bytesReceived); + + var flushTask = input.FlushAsync(); + + var paused = !flushTask.IsCompleted; + + if (paused) + { + _log.StreamPause(ConnectionId); + } + + var result = await flushTask; + + if (paused) + { + _log.StreamResume(ConnectionId); + } + + if (result.IsCompleted || result.IsCanceled) + { + // Pipe consumer is shut down, do we stop writing + break; + } + } + } + + private void FireStreamClosed() + { + ThreadPool.UnsafeQueueUserWorkItem(state => + { + state.CancelConnectionClosedToken(); + + state._waitForConnectionClosedTcs.TrySetResult(null); + }, + this, + preferLocal: false); + } + + private void CancelConnectionClosedToken() + { + try + { + _streamClosedTokenSource.Cancel(); + } + catch (Exception ex) + { + _log.LogError(0, ex, $"Unexpected exception in {nameof(QuicStreamContext)}.{nameof(CancelConnectionClosedToken)}."); + } + } + + + private async Task DoSend() + { + Exception shutdownReason = null; + Exception unexpectedError = null; + + try + { + await ProcessSends(); + } + catch (QuicException ex) + { + shutdownReason = new ConnectionResetException(ex.Message, ex); + } + catch (Exception ex) + { + shutdownReason = ex; + unexpectedError = ex; + _log.ConnectionError(ConnectionId, unexpectedError); + } + finally + { + await ShutdownWrite(shutdownReason); + + // Complete the output after disposing the stream + Output.Complete(unexpectedError); + + // Cancel any pending flushes so that the input loop is un-paused + Input.CancelPendingFlush(); + } + } + + private async Task ProcessSends() + { + // Resolve `output` PipeReader via the IDuplexPipe interface prior to loop start for performance. + var output = Output; + while (true) + { + var result = await output.ReadAsync(); + + if (result.IsCanceled) + { + break; + } + + var buffer = result.Buffer; + + var end = buffer.End; + var isCompleted = result.IsCompleted; + if (!buffer.IsEmpty) + { + await _stream.WriteAsync(buffer, endStream: isCompleted); + } + + output.AdvanceTo(end); + + if (isCompleted) + { + // Once the stream pipe is closed, shutdown the stream. + break; + } + } + } + + public override void Abort(ConnectionAbortedException abortReason) + { + // Don't call _stream.Shutdown and _stream.Abort at the same time. + lock (_shutdownLock) + { + _stream.AbortRead(_context.Options.AbortErrorCode); + _stream.AbortWrite(_context.Options.AbortErrorCode); + } + + // Cancel ProcessSends loop after calling shutdown to ensure the correct _shutdownReason gets set. + Output.CancelPendingRead(); + } + + private async ValueTask ShutdownWrite(Exception shutdownReason) + { + try + { + lock (_shutdownLock) + { + _shutdownReason = shutdownReason ?? new ConnectionAbortedException("The Quic transport's send loop completed gracefully."); + + _log.StreamShutdownWrite(ConnectionId, _shutdownReason); + _stream.Shutdown(); + } + + await _stream.ShutdownWriteCompleted(); + } + catch (Exception ex) + { + _log.LogWarning(ex, "Stream failed to gracefully shutdown."); + // Ignore any errors from Shutdown() since we're tearing down the stream anyway. + } + } + + public override async ValueTask DisposeAsync() + { + Transport.Input.Complete(); + Transport.Output.Complete(); + + await _processingTask; + + _stream.Dispose(); + + _streamClosedTokenSource.Dispose(); + } + } +} diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicTrace.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicTrace.cs similarity index 57% rename from src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicTrace.cs rename to src/Servers/Kestrel/Transport.Quic/src/Internal/QuicTrace.cs index 318e1fddb8..18e5d8dc6d 100644 --- a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicTrace.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicTrace.cs @@ -4,22 +4,28 @@ using System; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal { - internal class MsQuicTrace : IMsQuicTrace + internal class QuicTrace : IQuicTrace { private static readonly Action _acceptedConnection = LoggerMessage.Define(LogLevel.Debug, new EventId(4, nameof(NewConnection)), @"Connection id ""{ConnectionId}"" accepted."); private static readonly Action _acceptedStream = LoggerMessage.Define(LogLevel.Debug, new EventId(5, nameof(NewStream)), @"Stream id ""{ConnectionId}"" accepted."); private static readonly Action _connectionError = - LoggerMessage.Define(LogLevel.Debug, new EventId(6, nameof(NewStream)), @"Connection id ""{ConnectionId}"" hit an exception: ""{Reason}""."); + LoggerMessage.Define(LogLevel.Debug, new EventId(6, nameof(ConnectionError)), @"Connection id ""{ConnectionId}"" hit an exception: ""{Reason}""."); private static readonly Action _streamError = - LoggerMessage.Define(LogLevel.Debug, new EventId(7, nameof(NewStream)), @"Connection id ""{ConnectionId}"" hit an exception: ""{Reason}""."); + LoggerMessage.Define(LogLevel.Debug, new EventId(7, nameof(StreamError)), @"Connection id ""{ConnectionId}"" hit an exception: ""{Reason}""."); + private static readonly Action _streamPause = + LoggerMessage.Define(LogLevel.Debug, new EventId(7, nameof(StreamPause)), @"Stream id ""{ConnectionId}"" paused."); + private static readonly Action _streamResume = + LoggerMessage.Define(LogLevel.Debug, new EventId(7, nameof(StreamResume)), @"Stream id ""{ConnectionId}"" resumed."); + private static readonly Action _streamShutdownWrite = + LoggerMessage.Define(LogLevel.Debug, new EventId(7, nameof(StreamShutdownWrite)), @"Connection id ""{ConnectionId}"" shutting down writes, exception: ""{Reason}""."); private ILogger _logger; - public MsQuicTrace(ILogger logger) + public QuicTrace(ILogger logger) { _logger = logger; } @@ -49,5 +55,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal { _streamError(_logger, streamId, ex.Message, ex); } + + public void StreamPause(string streamId) + { + _streamPause(_logger, streamId, null); + } + + public void StreamResume(string streamId) + { + _streamResume(_logger, streamId, null); + } + + public void StreamShutdownWrite(string streamId, Exception ex) + { + _streamShutdownWrite(_logger, streamId, ex.Message, ex); + } } } diff --git a/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicTransportContext.cs b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicTransportContext.cs new file mode 100644 index 0000000000..f26c23d9d5 --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/src/Internal/QuicTransportContext.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Extensions.Hosting; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal +{ + internal class QuicTransportContext + { + public QuicTransportContext(IQuicTrace log, QuicTransportOptions options) + { + Log = log; + Options = options; + } + + public IQuicTrace Log { get; } + public QuicTransportOptions Options { get; } + } +} diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj b/src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj similarity index 69% rename from src/Servers/Kestrel/Transport.MsQuic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj rename to src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj index c777d59b89..a530b892ee 100644 --- a/src/Servers/Kestrel/Transport.MsQuic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj +++ b/src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj @@ -18,26 +18,12 @@ - + - - - - PreserveNewest - PreserveNewest - - - PreserveNewest - PreserveNewest - - - PreserveNewest - PreserveNewest - - + diff --git a/src/Servers/Kestrel/Transport.Quic/src/QuicConnectionFactory.cs b/src/Servers/Kestrel/Transport.Quic/src/QuicConnectionFactory.cs new file mode 100644 index 0000000000..d2e7c1bcb3 --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/src/QuicConnectionFactory.cs @@ -0,0 +1,50 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Quic; +using System.Net.Security; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic +{ + public class QuicConnectionFactory : IConnectionFactory + { + private QuicTransportContext _transportContext; + + public QuicConnectionFactory(IOptions options, ILoggerFactory loggerFactory) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Client"); + var trace = new QuicTrace(logger); + + _transportContext = new QuicTransportContext(trace, options.Value); + } + + public async ValueTask ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken = default) + { + if (!(endPoint is IPEndPoint ipEndPoint)) + { + throw new NotSupportedException($"{endPoint} is not supported"); + } + + var sslOptions = new SslClientAuthenticationOptions(); + sslOptions.ApplicationProtocols = new List() { new SslApplicationProtocol(_transportContext.Options.Alpn) }; + var connection = new QuicConnection(QuicImplementationProviders.MsQuic, endPoint as IPEndPoint, sslOptions); + + await connection.ConnectAsync(cancellationToken); + return new QuicConnectionContext(connection, _transportContext); + } + } +} diff --git a/src/Servers/Kestrel/Transport.Quic/src/QuicTransportFactory.cs b/src/Servers/Kestrel/Transport.Quic/src/QuicTransportFactory.cs new file mode 100644 index 0000000000..5962df595e --- /dev/null +++ b/src/Servers/Kestrel/Transport.Quic/src/QuicTransportFactory.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic +{ + public class QuicTransportFactory : IMultiplexedConnectionListenerFactory + { + private QuicTrace _log; + private QuicTransportOptions _options; + + public QuicTransportFactory(ILoggerFactory loggerFactory, IOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic"); + _log = new QuicTrace(logger); + _options = options.Value; + } + + public ValueTask BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default) + { + var transport = new QuicConnectionListener(_options, _log, endpoint); + return new ValueTask(transport); + } + } +} diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/MsQuicTransportOptions.cs b/src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs similarity index 85% rename from src/Servers/Kestrel/Transport.MsQuic/src/MsQuicTransportOptions.cs rename to src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs index 17e3c32f1a..09cfbb73b7 100644 --- a/src/Servers/Kestrel/Transport.MsQuic/src/MsQuicTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/QuicTransportOptions.cs @@ -5,9 +5,9 @@ using System; using System.Buffers; using System.Security.Cryptography.X509Certificates; -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic { - public class MsQuicTransportOptions + public class QuicTransportOptions { /// /// The maximum number of concurrent bi-directional streams per connection. @@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic public string Alpn { get; set; } /// - /// The registration name to use in MsQuic. + /// The registration name to use in Quic. /// public string RegistrationName { get; set; } @@ -49,6 +49,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic /// public long? MaxWriteBufferSize { get; set; } = 64 * 1024; + /// + /// The error code to abort with + /// + public long AbortErrorCode { get; set; } = 0; + internal Func> MemoryPoolFactory { get; set; } = System.Buffers.SlabMemoryPoolFactory.Create; } diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/WebHostBuilderMsQuicExtensions.cs b/src/Servers/Kestrel/Transport.Quic/src/WebHostBuilderMsQuicExtensions.cs similarity index 59% rename from src/Servers/Kestrel/Transport.MsQuic/src/WebHostBuilderMsQuicExtensions.cs rename to src/Servers/Kestrel/Transport.Quic/src/WebHostBuilderMsQuicExtensions.cs index 15fffe52a6..25fb0b581a 100644 --- a/src/Servers/Kestrel/Transport.MsQuic/src/WebHostBuilderMsQuicExtensions.cs +++ b/src/Servers/Kestrel/Transport.Quic/src/WebHostBuilderMsQuicExtensions.cs @@ -3,25 +3,24 @@ using System; using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic; -using Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Hosting { public static class WebHostBuilderMsQuicExtensions { - public static IWebHostBuilder UseMsQuic(this IWebHostBuilder hostBuilder) + public static IWebHostBuilder UseQuic(this IWebHostBuilder hostBuilder) { return hostBuilder.ConfigureServices(services => { - services.AddSingleton(); + services.AddSingleton(); }); } - public static IWebHostBuilder UseMsQuic(this IWebHostBuilder hostBuilder, Action configureOptions) + public static IWebHostBuilder UseQuic(this IWebHostBuilder hostBuilder, Action configureOptions) { - return hostBuilder.UseMsQuic().ConfigureServices(services => + return hostBuilder.UseQuic().ConfigureServices(services => { services.Configure(configureOptions); }); diff --git a/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp.cs b/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp.cs index a08dc70324..622010aa63 100644 --- a/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp.cs +++ b/src/Servers/Kestrel/Transport.Sockets/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.netcoreapp.cs @@ -11,6 +11,13 @@ namespace Microsoft.AspNetCore.Hosting } namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets { + public partial class SocketConnectionFactory : Microsoft.AspNetCore.Connections.IConnectionFactory, System.IAsyncDisposable + { + public SocketConnectionFactory(Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } + [System.Diagnostics.DebuggerStepThroughAttribute] + public System.Threading.Tasks.ValueTask ConnectAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + } public sealed partial class SocketTransportFactory : Microsoft.AspNetCore.Connections.IConnectionListenerFactory { public SocketTransportFactory(Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } @@ -26,13 +33,3 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets public bool NoDelay { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } } } -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Client -{ - public partial class SocketConnectionFactory : Microsoft.AspNetCore.Connections.IConnectionFactory, System.IAsyncDisposable - { - public SocketConnectionFactory(Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } - [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.ValueTask ConnectAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } - } -} diff --git a/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs b/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs index 20d5b97018..1a3f0ed601 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/Client/SocketConnectionFactory.cs @@ -13,7 +13,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Client +namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets { public class SocketConnectionFactory : IConnectionFactory, IAsyncDisposable { diff --git a/src/Servers/Kestrel/samples/Http3SampleApp/Http3SampleApp.csproj b/src/Servers/Kestrel/samples/Http3SampleApp/Http3SampleApp.csproj index 4bc61b8465..eb811e1dcb 100644 --- a/src/Servers/Kestrel/samples/Http3SampleApp/Http3SampleApp.csproj +++ b/src/Servers/Kestrel/samples/Http3SampleApp/Http3SampleApp.csproj @@ -9,6 +9,6 @@ - + diff --git a/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs b/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs index 9b39054948..db60249ab3 100644 --- a/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/Http3SampleApp/Program.cs @@ -26,7 +26,7 @@ namespace Http3SampleApp { webHost.UseKestrel() // Things like APLN and cert should be able to be passed from corefx into bedrock - .UseMsQuic(options => + .UseQuic(options => { options.Certificate = cert; options.RegistrationName = "Quic"; diff --git a/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs index e5600d06ff..4766bc8bfe 100644 --- a/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs +++ b/src/Servers/Kestrel/samples/QuicSampleApp/Program.cs @@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.Extensions.Logging; -using System.Diagnostics; using Microsoft.AspNetCore.Server.Kestrel.Core; namespace QuicSampleApp @@ -26,7 +25,7 @@ namespace QuicSampleApp public static void Main(string[] args) { - var cert = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, true); + //var cert = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, true); var hostBuilder = new WebHostBuilder() .ConfigureLogging((_, factory) => { @@ -34,9 +33,9 @@ namespace QuicSampleApp factory.AddConsole(); }) .UseKestrel() - .UseMsQuic(options => + .UseQuic(options => { - options.Certificate = cert; + options.Certificate = null; options.RegistrationName = "AspNetCore-MsQuic"; options.Alpn = "QuicTest"; options.IdleTimeout = TimeSpan.FromHours(1); diff --git a/src/Servers/Kestrel/samples/QuicSampleApp/QuicSampleApp.csproj b/src/Servers/Kestrel/samples/QuicSampleApp/QuicSampleApp.csproj index 4a5ee8de15..9e973b1b39 100644 --- a/src/Servers/Kestrel/samples/QuicSampleApp/QuicSampleApp.csproj +++ b/src/Servers/Kestrel/samples/QuicSampleApp/QuicSampleApp.csproj @@ -1,16 +1,28 @@ - - + + $(DefaultNetCoreTargetFramework) false true false - + - + + + + + + true + + + + + + true + diff --git a/src/Servers/Kestrel/samples/QuicSampleClient/Program.cs b/src/Servers/Kestrel/samples/QuicSampleClient/Program.cs index b1b656cc4e..3ad16e0dd6 100644 --- a/src/Servers/Kestrel/samples/QuicSampleClient/Program.cs +++ b/src/Servers/Kestrel/samples/QuicSampleClient/Program.cs @@ -5,7 +5,7 @@ using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel.Https; -using Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic; using Microsoft.AspNetCore.Connections.Abstractions.Features; using Microsoft.Extensions.Hosting; using Microsoft.AspNetCore.Connections; @@ -26,26 +26,26 @@ namespace QuicSampleClient }) .ConfigureServices(services => { - services.AddSingleton(); - services.AddSingleton(); - services.AddOptions(); - services.Configure((options) => + services.AddSingleton(); + services.AddSingleton(); + services.AddOptions(); + services.Configure((options) => { options.Alpn = "QuicTest"; options.RegistrationName = "Quic-AspNetCore-client"; - options.Certificate = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, true); + options.Certificate = null; options.IdleTimeout = TimeSpan.FromHours(1); }); }) .Build(); - await host.Services.GetService().RunAsync(); + await host.Services.GetService().RunAsync(); } - private class MsQuicClientService + private class QuicClientService { private readonly IConnectionFactory _connectionFactory; - private readonly ILogger _logger; - public MsQuicClientService(IConnectionFactory connectionFactory, ILogger logger) + private readonly ILogger _logger; + public QuicClientService(IConnectionFactory connectionFactory, ILogger logger) { _connectionFactory = connectionFactory; _logger = logger; @@ -53,7 +53,6 @@ namespace QuicSampleClient public async Task RunAsync() { - var start = Console.ReadLine(); Console.WriteLine("Starting"); var connectionContext = await _connectionFactory.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 5555)); var createStreamFeature = connectionContext.Features.Get(); @@ -82,10 +81,11 @@ namespace QuicSampleClient } var readResult = await streamContext.Transport.Input.ReadAsync(); - if (readResult.IsCanceled) + if (readResult.IsCanceled || readResult.IsCompleted) { break; } + if (readResult.Buffer.Length > 0) { Console.WriteLine(Encoding.ASCII.GetString(readResult.Buffer.ToArray())); diff --git a/src/Servers/Kestrel/samples/QuicSampleClient/QuicSampleClient.csproj b/src/Servers/Kestrel/samples/QuicSampleClient/QuicSampleClient.csproj index 6477f0dcb3..59fd148bed 100644 --- a/src/Servers/Kestrel/samples/QuicSampleClient/QuicSampleClient.csproj +++ b/src/Servers/Kestrel/samples/QuicSampleClient/QuicSampleClient.csproj @@ -1,4 +1,4 @@ - + Exe @@ -7,9 +7,22 @@ - + + + + + true + + + + + + true + + + diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs index aab3b504d9..aa0e7fc9ea 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3TestBase.cs @@ -219,7 +219,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { internal ConnectionContext ConnectionContext { get; } - public bool IsUnidirectional => false; + public bool CanRead => true; + public bool CanWrite => true; public long StreamId => 0; @@ -347,7 +348,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { internal ConnectionContext ConnectionContext { get; } - public bool IsUnidirectional => true; + public bool CanRead => true; + public bool CanWrite => false; // TODO public long StreamId => 0; diff --git a/src/Shared/Http2cat/Http2CatIServiceCollectionExtensions.cs b/src/Shared/Http2cat/Http2CatIServiceCollectionExtensions.cs index 52c6449225..c4c2c5ca4e 100644 --- a/src/Shared/Http2cat/Http2CatIServiceCollectionExtensions.cs +++ b/src/Shared/Http2cat/Http2CatIServiceCollectionExtensions.cs @@ -4,7 +4,7 @@ using System; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http2Cat; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Client; +using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; namespace Microsoft.Extensions.DependencyInjection {