diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props index 3ac3e8dbf6..778f9b7620 100644 --- a/eng/ProjectReferences.props +++ b/eng/ProjectReferences.props @@ -60,6 +60,7 @@ + diff --git a/src/Servers/Kestrel/Kestrel.sln b/src/Servers/Kestrel/Kestrel.sln index 8ff2ba3287..6ed4f64fd5 100644 --- a/src/Servers/Kestrel/Kestrel.sln +++ b/src/Servers/Kestrel/Kestrel.sln @@ -84,6 +84,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.WebUti EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "http2cat", "samples\http2cat\http2cat.csproj", "{3D6821F5-F242-4828-8DDE-89488E85512D}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic", "Transport.MsQuic\src\Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj", "{1BC94F37-AF61-4641-A80A-EC32A15C5344}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -466,6 +468,18 @@ Global {3D6821F5-F242-4828-8DDE-89488E85512D}.Release|x64.Build.0 = Release|Any CPU {3D6821F5-F242-4828-8DDE-89488E85512D}.Release|x86.ActiveCfg = Release|Any CPU {3D6821F5-F242-4828-8DDE-89488E85512D}.Release|x86.Build.0 = Release|Any CPU + {1BC94F37-AF61-4641-A80A-EC32A15C5344}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1BC94F37-AF61-4641-A80A-EC32A15C5344}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1BC94F37-AF61-4641-A80A-EC32A15C5344}.Debug|x64.ActiveCfg = Debug|Any CPU + {1BC94F37-AF61-4641-A80A-EC32A15C5344}.Debug|x64.Build.0 = Debug|Any CPU + {1BC94F37-AF61-4641-A80A-EC32A15C5344}.Debug|x86.ActiveCfg = Debug|Any CPU + {1BC94F37-AF61-4641-A80A-EC32A15C5344}.Debug|x86.Build.0 = Debug|Any CPU + {1BC94F37-AF61-4641-A80A-EC32A15C5344}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1BC94F37-AF61-4641-A80A-EC32A15C5344}.Release|Any CPU.Build.0 = Release|Any CPU + {1BC94F37-AF61-4641-A80A-EC32A15C5344}.Release|x64.ActiveCfg = Release|Any CPU + {1BC94F37-AF61-4641-A80A-EC32A15C5344}.Release|x64.Build.0 = Release|Any CPU + {1BC94F37-AF61-4641-A80A-EC32A15C5344}.Release|x86.ActiveCfg = Release|Any CPU + {1BC94F37-AF61-4641-A80A-EC32A15C5344}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -503,6 +517,7 @@ Global {E0AD50A3-2518-4060-8BB9-5649B04B3A6D} = {F0A1281A-B512-49D2-8362-21EE32B3674F} {EE45763C-753D-4228-8E5D-A71F8BDB3D89} = {F0A1281A-B512-49D2-8362-21EE32B3674F} {3D6821F5-F242-4828-8DDE-89488E85512D} = {F826BA61-60A9-45B6-AF29-FD1A6E313EF0} + {1BC94F37-AF61-4641-A80A-EC32A15C5344} = {2B456D08-F72B-4EB8-B663-B6D78FC04BF8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {48207B50-7D05-4B10-B585-890FE0F4FCE1} diff --git a/src/Servers/Kestrel/Transport.MsQuic/README.md b/src/Servers/Kestrel/Transport.MsQuic/README.md new file mode 100644 index 0000000000..efacbcb9fa --- /dev/null +++ b/src/Servers/Kestrel/Transport.MsQuic/README.md @@ -0,0 +1,10 @@ +## 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/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj b/src/Servers/Kestrel/Transport.MsQuic/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj new file mode 100644 index 0000000000..d75fa659ea --- /dev/null +++ b/src/Servers/Kestrel/Transport.MsQuic/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj @@ -0,0 +1,13 @@ + + + + $(DefaultNetCoreTargetFramework) + + + + + + + + + diff --git a/src/Servers/Kestrel/Transport.MsQuic/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.netcoreapp.cs b/src/Servers/Kestrel/Transport.MsQuic/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.netcoreapp.cs new file mode 100644 index 0000000000..618082bc4a --- /dev/null +++ b/src/Servers/Kestrel/Transport.MsQuic/ref/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.netcoreapp.cs @@ -0,0 +1,3 @@ +// 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. + diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicApi.cs b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicApi.cs new file mode 100644 index 0000000000..811b2a5dfe --- /dev/null +++ b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicApi.cs @@ -0,0 +1,186 @@ +// 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 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); + } + + 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/MsQuicConstants.cs b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicConstants.cs new file mode 100644 index 0000000000..13aad975ca --- /dev/null +++ b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicConstants.cs @@ -0,0 +1,148 @@ +// 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 + { + private const uint Success = 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 Success: + 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 Success: + 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 new file mode 100644 index 0000000000..37174ba4ec --- /dev/null +++ b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicEnums.cs @@ -0,0 +1,178 @@ +// 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 new file mode 100644 index 0000000000..cc10abfccf --- /dev/null +++ b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicNativeMethods.cs @@ -0,0 +1,540 @@ +// 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.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.dll"; + + [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, + IntPtr Handler, + 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, + IntPtr BufferLength, + IntPtr 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; + internal uint ReceiveAbortError => Data.PeerRecvAbort.ErrorCode; + internal uint SendAbortError => Data.PeerSendAbort.ErrorCode; + internal ulong AbsoluteOffset => Data.Recv.AbsoluteOffset; + internal ulong TotalBufferLength => Data.Recv.TotalBufferLength; + internal void CopyToBuffer(Span buffer) + { + var length = (int)Data.Recv.Buffers[0].Length; + new Span(Data.Recv.Buffers[0].Buffer, length).CopyTo(buffer); + } + internal bool Canceled => Data.SendComplete.IsCanceled(); + internal IntPtr ClientContext => Data.SendComplete.ClientContext; + internal bool GracefulShutdown => Data.SendShutdownComplete.Graceful; + } + + internal delegate uint StreamCallbackDelegate( + IntPtr Stream, + IntPtr Context, + ref 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; + } + + [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; + } + } +} diff --git a/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicStatusException.cs b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicStatusException.cs new file mode 100644 index 0000000000..c58b976482 --- /dev/null +++ b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicStatusException.cs @@ -0,0 +1,42 @@ +// 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 new file mode 100644 index 0000000000..23d15965a9 --- /dev/null +++ b/src/Servers/Kestrel/Transport.MsQuic/src/Internal/MsQuicStatusHelper.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.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/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj b/src/Servers/Kestrel/Transport.MsQuic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj new file mode 100644 index 0000000000..9cdcb4e919 --- /dev/null +++ b/src/Servers/Kestrel/Transport.MsQuic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj @@ -0,0 +1,20 @@ + + + + Libuv transport for the ASP.NET Core Kestrel cross-platform web server. + $(DefaultNetCoreTargetFramework) + true + aspnetcore;kestrel + true + CS1591;$(NoWarn) + false + + + + + + + + + +