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
+
+
+
+
+
+
+
+
+
+