Add rest of MsQuic transport. (#16812)

This commit is contained in:
Justin Kotalik 2019-11-07 20:53:33 -08:00 committed by GitHub
parent 90923ab8fd
commit 845e6d5512
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 2115 additions and 82 deletions

1
.gitignore vendored
View File

@ -29,6 +29,7 @@ modules/
*.psess
*.res
*.snk
*.so
*.suo
*.tlog
*.user

View File

@ -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
{
}
}

View File

@ -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
{
}
}

View File

@ -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
{
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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
{
}
}

View File

@ -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}

View File

@ -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 { } }
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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)
{
}
}
}

View File

@ -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;
}
}
}

View File

@ -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";

View File

@ -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;
}
}
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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; }
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

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

View File

@ -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();
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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);
});
}
}
}

View File

@ -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();
}
}
}

View File

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

View File

@ -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();
}
}
}
}

View File

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

View File

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

View File

@ -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");

View File

@ -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();

View File

@ -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();