Add rest of MsQuic transport. (#16812)
This commit is contained in:
parent
90923ab8fd
commit
845e6d5512
|
|
@ -29,6 +29,7 @@ modules/
|
|||
*.psess
|
||||
*.res
|
||||
*.snk
|
||||
*.so
|
||||
*.suo
|
||||
*.tlog
|
||||
*.user
|
||||
|
|
|
|||
|
|
@ -136,6 +136,14 @@ namespace Microsoft.AspNetCore.Connections
|
|||
public override string ToString() { throw null; }
|
||||
}
|
||||
}
|
||||
namespace Microsoft.AspNetCore.Connections.Abstractions.Features
|
||||
{
|
||||
public partial interface IQuicCreateStreamFeature
|
||||
{
|
||||
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> StartBidirectionalStreamAsync();
|
||||
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> StartUnidirectionalStreamAsync();
|
||||
}
|
||||
}
|
||||
namespace Microsoft.AspNetCore.Connections.Features
|
||||
{
|
||||
public partial interface IConnectionCompleteFeature
|
||||
|
|
@ -185,6 +193,10 @@ namespace Microsoft.AspNetCore.Connections.Features
|
|||
{
|
||||
System.Buffers.MemoryPool<byte> MemoryPool { get; }
|
||||
}
|
||||
public partial interface IQuicStreamListenerFeature
|
||||
{
|
||||
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> AcceptAsync();
|
||||
}
|
||||
public partial interface ITlsHandshakeFeature
|
||||
{
|
||||
System.Security.Authentication.CipherAlgorithmType CipherAlgorithm { get; }
|
||||
|
|
@ -200,4 +212,7 @@ namespace Microsoft.AspNetCore.Connections.Features
|
|||
Microsoft.AspNetCore.Connections.TransferFormat ActiveFormat { get; set; }
|
||||
Microsoft.AspNetCore.Connections.TransferFormat SupportedFormats { get; }
|
||||
}
|
||||
public partial interface IUnidirectionalStreamFeature
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,6 +136,14 @@ namespace Microsoft.AspNetCore.Connections
|
|||
public override string ToString() { throw null; }
|
||||
}
|
||||
}
|
||||
namespace Microsoft.AspNetCore.Connections.Abstractions.Features
|
||||
{
|
||||
public partial interface IQuicCreateStreamFeature
|
||||
{
|
||||
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> StartBidirectionalStreamAsync();
|
||||
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> StartUnidirectionalStreamAsync();
|
||||
}
|
||||
}
|
||||
namespace Microsoft.AspNetCore.Connections.Features
|
||||
{
|
||||
public partial interface IConnectionCompleteFeature
|
||||
|
|
@ -185,6 +193,10 @@ namespace Microsoft.AspNetCore.Connections.Features
|
|||
{
|
||||
System.Buffers.MemoryPool<byte> MemoryPool { get; }
|
||||
}
|
||||
public partial interface IQuicStreamListenerFeature
|
||||
{
|
||||
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> AcceptAsync();
|
||||
}
|
||||
public partial interface ITlsHandshakeFeature
|
||||
{
|
||||
System.Security.Authentication.CipherAlgorithmType CipherAlgorithm { get; }
|
||||
|
|
@ -200,4 +212,7 @@ namespace Microsoft.AspNetCore.Connections.Features
|
|||
Microsoft.AspNetCore.Connections.TransferFormat ActiveFormat { get; set; }
|
||||
Microsoft.AspNetCore.Connections.TransferFormat SupportedFormats { get; }
|
||||
}
|
||||
public partial interface IUnidirectionalStreamFeature
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,6 +136,14 @@ namespace Microsoft.AspNetCore.Connections
|
|||
public override string ToString() { throw null; }
|
||||
}
|
||||
}
|
||||
namespace Microsoft.AspNetCore.Connections.Abstractions.Features
|
||||
{
|
||||
public partial interface IQuicCreateStreamFeature
|
||||
{
|
||||
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> StartBidirectionalStreamAsync();
|
||||
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> StartUnidirectionalStreamAsync();
|
||||
}
|
||||
}
|
||||
namespace Microsoft.AspNetCore.Connections.Features
|
||||
{
|
||||
public partial interface IConnectionCompleteFeature
|
||||
|
|
@ -185,6 +193,10 @@ namespace Microsoft.AspNetCore.Connections.Features
|
|||
{
|
||||
System.Buffers.MemoryPool<byte> MemoryPool { get; }
|
||||
}
|
||||
public partial interface IQuicStreamListenerFeature
|
||||
{
|
||||
System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> AcceptAsync();
|
||||
}
|
||||
public partial interface ITlsHandshakeFeature
|
||||
{
|
||||
System.Security.Authentication.CipherAlgorithmType CipherAlgorithm { get; }
|
||||
|
|
@ -200,4 +212,7 @@ namespace Microsoft.AspNetCore.Connections.Features
|
|||
Microsoft.AspNetCore.Connections.TransferFormat ActiveFormat { get; set; }
|
||||
Microsoft.AspNetCore.Connections.TransferFormat SupportedFormats { get; }
|
||||
}
|
||||
public partial interface IUnidirectionalStreamFeature
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
// 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.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Connections.Abstractions.Features
|
||||
{
|
||||
public interface IQuicCreateStreamFeature
|
||||
{
|
||||
ValueTask<ConnectionContext> StartUnidirectionalStreamAsync();
|
||||
ValueTask<ConnectionContext> StartBidirectionalStreamAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// 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.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Connections.Features
|
||||
{
|
||||
public interface IQuicStreamListenerFeature
|
||||
{
|
||||
ValueTask<ConnectionContext> AcceptAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Connections.Features
|
||||
{
|
||||
public interface IUnidirectionalStreamFeature
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -84,7 +84,11 @@ 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}"
|
||||
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}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuicSampleClient", "samples\QuicSampleClient\QuicSampleClient.csproj", "{F39A942B-85A8-4C1B-A5BC-435555E79F20}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
|
@ -468,18 +472,42 @@ 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
|
||||
{53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Release|x64.Build.0 = Release|Any CPU
|
||||
{53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{53A8634C-DFC5-4A5B-8864-9EF1707E3F18}.Release|x86.Build.0 = Release|Any CPU
|
||||
{62CFF861-807E-43F6-9403-22AA7F06C9A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{62CFF861-807E-43F6-9403-22AA7F06C9A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{62CFF861-807E-43F6-9403-22AA7F06C9A6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{62CFF861-807E-43F6-9403-22AA7F06C9A6}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{62CFF861-807E-43F6-9403-22AA7F06C9A6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{62CFF861-807E-43F6-9403-22AA7F06C9A6}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{62CFF861-807E-43F6-9403-22AA7F06C9A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{62CFF861-807E-43F6-9403-22AA7F06C9A6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{62CFF861-807E-43F6-9403-22AA7F06C9A6}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{62CFF861-807E-43F6-9403-22AA7F06C9A6}.Release|x64.Build.0 = Release|Any CPU
|
||||
{62CFF861-807E-43F6-9403-22AA7F06C9A6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{62CFF861-807E-43F6-9403-22AA7F06C9A6}.Release|x86.Build.0 = Release|Any CPU
|
||||
{F39A942B-85A8-4C1B-A5BC-435555E79F20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F39A942B-85A8-4C1B-A5BC-435555E79F20}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F39A942B-85A8-4C1B-A5BC-435555E79F20}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{F39A942B-85A8-4C1B-A5BC-435555E79F20}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{F39A942B-85A8-4C1B-A5BC-435555E79F20}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{F39A942B-85A8-4C1B-A5BC-435555E79F20}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{F39A942B-85A8-4C1B-A5BC-435555E79F20}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F39A942B-85A8-4C1B-A5BC-435555E79F20}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F39A942B-85A8-4C1B-A5BC-435555E79F20}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{F39A942B-85A8-4C1B-A5BC-435555E79F20}.Release|x64.Build.0 = Release|Any CPU
|
||||
{F39A942B-85A8-4C1B-A5BC-435555E79F20}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{F39A942B-85A8-4C1B-A5BC-435555E79F20}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -517,7 +545,9 @@ 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}
|
||||
{53A8634C-DFC5-4A5B-8864-9EF1707E3F18} = {F826BA61-60A9-45B6-AF29-FD1A6E313EF0}
|
||||
{62CFF861-807E-43F6-9403-22AA7F06C9A6} = {2B456D08-F72B-4EB8-B663-B6D78FC04BF8}
|
||||
{F39A942B-85A8-4C1B-A5BC-435555E79F20} = {F826BA61-60A9-45B6-AF29-FD1A6E313EF0}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {48207B50-7D05-4B10-B585-890FE0F4FCE1}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,38 @@
|
|||
// 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.
|
||||
|
||||
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<Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.MsQuicTransportOptions> configureOptions) { throw null; }
|
||||
}
|
||||
}
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic
|
||||
{
|
||||
public partial class MsQuicConnectionFactory : Microsoft.AspNetCore.Connections.IConnectionFactory
|
||||
{
|
||||
public MsQuicConnectionFactory(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.MsQuicTransportOptions> options, Microsoft.Extensions.Hosting.IHostApplicationLifetime lifetime, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { }
|
||||
[System.Diagnostics.DebuggerStepThroughAttribute]
|
||||
public System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.ConnectionContext> ConnectAsync(System.Net.EndPoint endPoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
|
||||
}
|
||||
public partial class MsQuicTransportFactory : Microsoft.AspNetCore.Connections.IConnectionListenerFactory
|
||||
{
|
||||
public MsQuicTransportFactory(Microsoft.Extensions.Hosting.IHostApplicationLifetime applicationLifetime, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.MsQuicTransportOptions> options) { }
|
||||
[System.Diagnostics.DebuggerStepThroughAttribute]
|
||||
public System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.IConnectionListener> BindAsync(System.Net.EndPoint endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
|
||||
}
|
||||
public partial class MsQuicTransportOptions
|
||||
{
|
||||
public MsQuicTransportOptions() { }
|
||||
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 { } }
|
||||
public ushort MaxBidirectionalStreamCount { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||
public long? MaxReadBufferSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||
public ushort MaxUnidirectionalStreamCount { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||
public long? MaxWriteBufferSize { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||
public string RegistrationName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
// 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 Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
|
||||
{
|
||||
internal interface IMsQuicTrace : ILogger
|
||||
{
|
||||
void NewConnection(string connectionId);
|
||||
void NewStream(string streamId);
|
||||
void ConnectionError(string connectionId, Exception ex);
|
||||
void StreamError(string streamId, Exception ex);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
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
|
||||
{
|
||||
|
|
@ -160,6 +163,54 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
|
|||
Buffer.Buffer);
|
||||
}
|
||||
|
||||
public async ValueTask<QuicSecConfig> CreateSecurityConfig(X509Certificate2 certificate)
|
||||
{
|
||||
QuicSecConfig secConfig = null;
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,334 @@
|
|||
// 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 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<MsQuicStream> _acceptQueue = Channel.CreateUnbounded<MsQuicStream>(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<IQuicStreamListenerFeature>(this);
|
||||
Features.Set<IQuicCreateStreamFeature>(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<ConnectionContext> AcceptAsync()
|
||||
{
|
||||
if (await _acceptQueue.Reader.WaitToReadAsync())
|
||||
{
|
||||
if (_acceptQueue.Reader.TryRead(out var stream))
|
||||
{
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ValueTask<ConnectionContext> StartUnidirectionalStreamAsync()
|
||||
{
|
||||
return StartStreamAsync(QUIC_STREAM_OPEN_FLAG.UNIDIRECTIONAL);
|
||||
}
|
||||
|
||||
public ValueTask<ConnectionContext> StartBidirectionalStreamAsync()
|
||||
{
|
||||
return StartStreamAsync(QUIC_STREAM_OPEN_FLAG.NONE);
|
||||
}
|
||||
|
||||
private async ValueTask<ConnectionContext> 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Listens for new Quic Connections.
|
||||
/// </summary>
|
||||
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<MsQuicConnection> _acceptConnectionQueue = Channel.CreateUnbounded<MsQuicConnection>(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<ConnectionContext> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
|
|||
{
|
||||
internal static class MsQuicConstants
|
||||
{
|
||||
private const uint Success = 0;
|
||||
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<uint, string> ErrorTypeFromErrorCode = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Windows.GetError : (Func<uint, string>)Linux.GetError;
|
||||
|
||||
|
|
@ -37,7 +40,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
|
|||
{
|
||||
switch (status)
|
||||
{
|
||||
case Success:
|
||||
case SuccessConst:
|
||||
return "SUCCESS";
|
||||
case Pending:
|
||||
return "PENDING";
|
||||
|
|
@ -103,7 +106,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
|
|||
{
|
||||
switch (status)
|
||||
{
|
||||
case Success:
|
||||
case SuccessConst:
|
||||
return "SUCCESS";
|
||||
case Pending:
|
||||
return "PENDING";
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
|
|
@ -13,7 +14,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
|
|||
/// </summary>
|
||||
internal unsafe static class MsQuicNativeMethods
|
||||
{
|
||||
internal const string dllName = "msquic.dll";
|
||||
internal const string dllName = "msquic";
|
||||
|
||||
[DllImport(dllName)]
|
||||
internal static extern int MsQuicOpen(int version, out NativeApi* registration);
|
||||
|
|
@ -67,7 +68,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
|
|||
|
||||
internal delegate void SetCallbackHandlerDelegate(
|
||||
IntPtr Handle,
|
||||
IntPtr Handler,
|
||||
Delegate del,
|
||||
IntPtr Context);
|
||||
|
||||
internal delegate uint SetParamDelegate(
|
||||
|
|
@ -413,61 +414,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
|
|||
{
|
||||
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<byte> buffer)
|
||||
{
|
||||
var length = (int)Data.Recv.Buffers[0].Length;
|
||||
new Span<byte>(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)]
|
||||
|
|
@ -536,5 +482,106 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
|
|||
[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();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,443 @@
|
|||
// 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, IUnidirectionalStreamFeature
|
||||
{
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
// TODO when stream is unidirectional, don't create an output pipe.
|
||||
if (flags.HasFlag(QUIC_STREAM_OPEN_FLAG.UNIDIRECTIONAL))
|
||||
{
|
||||
Features.Set<IUnidirectionalStreamFeature>(this);
|
||||
}
|
||||
|
||||
// TODO populate the ITlsConnectionFeature (requires client certs).
|
||||
var feature = new FakeTlsConnectionFeature();
|
||||
Features.Set<ITlsConnectionFeature>(feature);
|
||||
|
||||
Transport = pair.Transport;
|
||||
Application = pair.Application;
|
||||
|
||||
SetCallbackHandler();
|
||||
|
||||
_processingTask = ProcessSends();
|
||||
|
||||
// Concatenate stream id with ConnectionId.
|
||||
_log.NewStream(ConnectionId);
|
||||
}
|
||||
|
||||
public override MemoryPool<byte> MemoryPool { get; }
|
||||
public PipeWriter Input => Application.Output;
|
||||
public PipeReader Output => Application.Input;
|
||||
|
||||
public override string ConnectionId {
|
||||
get
|
||||
{
|
||||
if (_connectionId == null)
|
||||
{
|
||||
_connectionId = $"{_connection.ConnectionId}:{base.ConnectionId}";
|
||||
}
|
||||
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<byte> buffer, StreamEvent evt)
|
||||
{
|
||||
var length = (int)evt.Data.Recv.Buffers[0].Length;
|
||||
new Span<byte>(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<FlushResult> 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<uint> SendAsync(
|
||||
ReadOnlySequence<byte> 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<uint> 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 void SetParam(
|
||||
QUIC_PARAM_STREAM param,
|
||||
QuicBuffer buf)
|
||||
{
|
||||
MsQuicStatusException.ThrowIfFailed(Api.UnsafeSetParam(
|
||||
_nativeObjPtr,
|
||||
(uint)QUIC_PARAM_LEVEL.SESSION,
|
||||
(uint)param,
|
||||
buf));
|
||||
}
|
||||
|
||||
~MsQuicStream()
|
||||
{
|
||||
_log.LogDebug("Destructor");
|
||||
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<X509Certificate2> GetClientCertificateAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// 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 Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
|
||||
{
|
||||
internal class MsQuicTrace : IMsQuicTrace
|
||||
{
|
||||
private static readonly Action<ILogger, string, Exception> _acceptedConnection =
|
||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(4, nameof(NewConnection)), @"Connection id ""{ConnectionId}"" accepted.");
|
||||
private static readonly Action<ILogger, string, Exception> _acceptedStream =
|
||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(5, nameof(NewStream)), @"Stream id ""{ConnectionId}"" accepted.");
|
||||
private static readonly Action<ILogger, string, string, Exception> _connectionError =
|
||||
LoggerMessage.Define<string, string>(LogLevel.Debug, new EventId(6, nameof(NewStream)), @"Connection id ""{ConnectionId}"" hit an exception: ""{Reason}"".");
|
||||
private static readonly Action<ILogger, string, string, Exception> _streamError =
|
||||
LoggerMessage.Define<string, string>(LogLevel.Debug, new EventId(7, nameof(NewStream)), @"Connection id ""{ConnectionId}"" hit an exception: ""{Reason}"".");
|
||||
|
||||
private ILogger _logger;
|
||||
|
||||
public MsQuicTrace(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state) => _logger.BeginScope(state);
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel) => _logger.IsEnabled(logLevel);
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||
=> _logger.Log(logLevel, eventId, state, exception, formatter);
|
||||
|
||||
public void NewConnection(string connectionId)
|
||||
{
|
||||
_acceptedConnection(_logger, connectionId, null);
|
||||
}
|
||||
|
||||
public void NewStream(string streamId)
|
||||
{
|
||||
_acceptedStream(_logger, streamId, null);
|
||||
}
|
||||
public void ConnectionError(string connectionId, Exception ex)
|
||||
{
|
||||
_connectionError(_logger, connectionId, ex.Message, ex);
|
||||
}
|
||||
|
||||
public void StreamError(string streamId, Exception ex)
|
||||
{
|
||||
_streamError(_logger, streamId, ex.Message, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// 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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
// 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<MsQuicConnection> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
// 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<uint>
|
||||
{
|
||||
private ManualResetValueTaskSourceCore<uint> _valueTaskSource;
|
||||
private readonly MsQuicStream _stream;
|
||||
|
||||
public ResettableCompletionSource(MsQuicStream stream)
|
||||
{
|
||||
_stream = stream;
|
||||
_valueTaskSource.RunContinuationsAsynchronously = true;
|
||||
}
|
||||
|
||||
public ValueTask<uint> GetValueTask()
|
||||
{
|
||||
return new ValueTask<uint>(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<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags)
|
||||
{
|
||||
_valueTaskSource.OnCompleted(continuation, state, token, flags);
|
||||
}
|
||||
|
||||
public void Complete(uint result)
|
||||
{
|
||||
_valueTaskSource.SetResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,11 +10,34 @@
|
|||
<IsShippingPackage>false</IsShippingPackage>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(RepoRoot)src\Shared\Buffers.MemoryPool\*.cs" LinkBase="MemoryPool" />
|
||||
<Compile Include="$(KestrelSharedSourceRoot)\CorrelationIdGenerator.cs" Link="Internal\CorrelationIdGenerator.cs" />
|
||||
<Compile Include="$(KestrelSharedSourceRoot)\DuplexPipe.cs" Link="Internal\DuplexPipe.cs" />
|
||||
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.cs" Link="Internal\TransportConnection.cs" />
|
||||
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.Generated.cs" Link="Internal\TransportConnection.Generated.cs" />
|
||||
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.FeatureCollection.cs" Link="Internal\TransportConnection.FeatureCollection.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
|
||||
<Reference Include="Microsoft.AspNetCore.Connections.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.Options" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="msquic.dll" Condition="Exists('msquic.dll')">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="libmsquic.so" Condition="Exists('libmsquic.so')">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="msquic.pdb" Condition="Exists('msquic.pdb')">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -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.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<MsQuicTransportOptions> 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<ConnectionContext> 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(endPoint as 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
// 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 : IConnectionListenerFactory
|
||||
{
|
||||
private MsQuicTrace _log;
|
||||
private IHostApplicationLifetime _applicationLifetime;
|
||||
private MsQuicTransportOptions _options;
|
||||
|
||||
public MsQuicTransportFactory(IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions<MsQuicTransportOptions> 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<IConnectionListener> BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var transport = new MsQuicConnectionListener(_options, _applicationLifetime, _log, endpoint);
|
||||
await transport.BindAsync();
|
||||
return transport;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
// 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.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic
|
||||
{
|
||||
public class MsQuicTransportOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// The maximum number of concurrent bi-directional streams per connection.
|
||||
/// </summary>
|
||||
public ushort MaxBidirectionalStreamCount { get; set; } = 100;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of concurrent inbound uni-directional streams per connection.
|
||||
/// </summary>
|
||||
public ushort MaxUnidirectionalStreamCount { get; set; } = 10;
|
||||
|
||||
/// <summary>
|
||||
/// The Application Layer Protocol Negotiation string.
|
||||
/// </summary>
|
||||
public string Alpn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The registration name to use in MsQuic.
|
||||
/// </summary>
|
||||
public string RegistrationName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The certificate that MsQuic will use.
|
||||
/// </summary>
|
||||
public X509Certificate2 Certificate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the idle timeout for connections and streams.
|
||||
/// </summary>
|
||||
public TimeSpan IdleTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The maximum read size.
|
||||
/// </summary>
|
||||
public long? MaxReadBufferSize { get; set; } = 1024 * 1024;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum write size.
|
||||
/// </summary>
|
||||
public long? MaxWriteBufferSize { get; set; } = 64 * 1024;
|
||||
|
||||
internal Func<MemoryPool<byte>> MemoryPoolFactory { get; set; } = System.Buffers.SlabMemoryPoolFactory.Create;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// 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 Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Hosting
|
||||
{
|
||||
public static class WebHostBuilderMsQuicExtensions
|
||||
{
|
||||
public static IWebHostBuilder UseMsQuic(this IWebHostBuilder hostBuilder)
|
||||
{
|
||||
return hostBuilder.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton<IConnectionListenerFactory, MsQuicTransportFactory>();
|
||||
});
|
||||
}
|
||||
|
||||
public static IWebHostBuilder UseMsQuic(this IWebHostBuilder hostBuilder, Action<MsQuicTransportOptions> configureOptions)
|
||||
{
|
||||
return hostBuilder.UseMsQuic().ConfigureServices(services =>
|
||||
{
|
||||
services.Configure(configureOptions);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Connections.Features;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace QuicSampleApp
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.Run((httpContext) =>
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var cert = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, true);
|
||||
var hostBuilder = new WebHostBuilder()
|
||||
.ConfigureLogging((_, factory) =>
|
||||
{
|
||||
factory.SetMinimumLevel(LogLevel.Debug);
|
||||
factory.AddConsole();
|
||||
})
|
||||
.UseKestrel()
|
||||
.UseMsQuic(options =>
|
||||
{
|
||||
options.Certificate = cert;
|
||||
options.RegistrationName = "AspNetCore-MsQuic";
|
||||
options.Alpn = "QuicTest";
|
||||
options.IdleTimeout = TimeSpan.FromHours(1);
|
||||
})
|
||||
.ConfigureKestrel((context, options) =>
|
||||
{
|
||||
var basePort = 5555;
|
||||
|
||||
options.Listen(IPAddress.Any, basePort, listenOptions =>
|
||||
{
|
||||
listenOptions.Use((next) =>
|
||||
{
|
||||
return async connection =>
|
||||
{
|
||||
var streamFeature = connection.Features.Get<IQuicStreamListenerFeature>();
|
||||
if (streamFeature != null)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var connectionContext = await streamFeature.AcceptAsync();
|
||||
if (connectionContext == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_ = next(connectionContext);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await next(connection);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
async Task EchoServer(ConnectionContext connection)
|
||||
{
|
||||
// For graceful shutdown
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var result = await connection.Transport.Input.ReadAsync();
|
||||
|
||||
if (result.IsCompleted)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
await connection.Transport.Output.WriteAsync(result.Buffer.ToArray());
|
||||
|
||||
connection.Transport.Input.AdvanceTo(result.Buffer.End);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
listenOptions.Run(EchoServer);
|
||||
});
|
||||
})
|
||||
.UseStartup<Startup>();
|
||||
|
||||
hostBuilder.Build().Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
|
||||
<GenerateRazorAssemblyInfo>false</GenerateRazorAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
|
||||
<Reference Include="Microsoft.Extensions.Logging.Console" />
|
||||
<Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.Net;
|
||||
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.Connections.Abstractions.Features;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace QuicSampleClient
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
var host = new HostBuilder()
|
||||
.ConfigureLogging(loggingBuilder =>
|
||||
{
|
||||
loggingBuilder.AddConsole();
|
||||
loggingBuilder.SetMinimumLevel(LogLevel.Error);
|
||||
})
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton<IConnectionFactory, MsQuicConnectionFactory>();
|
||||
services.AddSingleton<MsQuicClientService>();
|
||||
services.AddOptions<MsQuicTransportOptions>();
|
||||
services.Configure<MsQuicTransportOptions>((options) =>
|
||||
{
|
||||
options.Alpn = "QuicTest";
|
||||
options.RegistrationName = "Quic-AspNetCore-client";
|
||||
options.Certificate = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, true);
|
||||
options.IdleTimeout = TimeSpan.FromHours(1);
|
||||
});
|
||||
})
|
||||
.Build();
|
||||
await host.Services.GetService<MsQuicClientService>().RunAsync();
|
||||
}
|
||||
|
||||
private class MsQuicClientService
|
||||
{
|
||||
private readonly IConnectionFactory _connectionFactory;
|
||||
private readonly ILogger<MsQuicClientService> _logger;
|
||||
public MsQuicClientService(IConnectionFactory connectionFactory, ILogger<MsQuicClientService> logger)
|
||||
{
|
||||
_connectionFactory = connectionFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task RunAsync()
|
||||
{
|
||||
var connectionContext = await _connectionFactory.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 5555));
|
||||
var createStreamFeature = connectionContext.Features.Get<IQuicCreateStreamFeature>();
|
||||
var streamContext = await createStreamFeature.StartBidirectionalStreamAsync();
|
||||
|
||||
Console.CancelKeyPress += new ConsoleCancelEventHandler((sender, args) =>
|
||||
{
|
||||
streamContext.Transport.Input.CancelPendingRead();
|
||||
streamContext.Transport.Output.CancelPendingFlush();
|
||||
});
|
||||
|
||||
var input = "asdf";
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
//var input = Console.ReadLine();
|
||||
if (input.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var flushResult = await streamContext.Transport.Output.WriteAsync(Encoding.ASCII.GetBytes(input));
|
||||
if (flushResult.IsCanceled)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var readResult = await streamContext.Transport.Input.ReadAsync();
|
||||
if (readResult.IsCanceled)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (readResult.Buffer.Length > 0)
|
||||
{
|
||||
Console.WriteLine(Encoding.ASCII.GetString(readResult.Buffer.ToArray()));
|
||||
}
|
||||
|
||||
streamContext.Transport.Input.AdvanceTo(readResult.Buffer.End);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await streamContext.Transport.Input.CompleteAsync();
|
||||
await streamContext.Transport.Output.CompleteAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic" />
|
||||
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
|
||||
<Reference Include="Microsoft.Extensions.Hosting" />
|
||||
<Reference Include="Microsoft.Extensions.Logging.Console" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace System.IO.Pipelines
|
||||
|
|
|
|||
|
|
@ -22,8 +22,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
|
|||
_app = new KitchenSinkApp(logger);
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/8267")]
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/16912")]
|
||||
public async Task RunsWithDotnetWatchEnvVariable()
|
||||
{
|
||||
Assert.True(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DOTNET_WATCH")), "DOTNET_WATCH cannot be set already when this test is running");
|
||||
|
|
|
|||
|
|
@ -85,8 +85,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
|
|||
await _app.HasRestarted();
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/8267")]
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/16912")]
|
||||
public async Task ChangeExcludedFile()
|
||||
{
|
||||
await _app.StartWatcherAsync();
|
||||
|
|
|
|||
|
|
@ -42,8 +42,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
|
|||
Assert.NotEqual(processIdentifier, processIdentifier2);
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/8267")]
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/16912")]
|
||||
public async Task RestartProcessThatTerminatesAfterFileChange()
|
||||
{
|
||||
await _app.StartWatcherAsync();
|
||||
|
|
|
|||
Loading…
Reference in New Issue