Replat on System.Net.Quic (#18689)

This commit is contained in:
Justin Kotalik 2020-02-07 10:43:09 -08:00 committed by GitHub
parent 962db1fecc
commit 1daebd1722
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 741 additions and 2749 deletions

View File

@ -63,7 +63,7 @@
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel.Core" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Core\src\Microsoft.AspNetCore.Server.Kestrel.Core.csproj" RefProjectPath="$(RepoRoot)src\Servers\Kestrel\Core\ref\Microsoft.AspNetCore.Server.Kestrel.Core.csproj" /> <ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel.Core" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Core\src\Microsoft.AspNetCore.Server.Kestrel.Core.csproj" RefProjectPath="$(RepoRoot)src\Servers\Kestrel\Core\ref\Microsoft.AspNetCore.Server.Kestrel.Core.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Kestrel\src\Microsoft.AspNetCore.Server.Kestrel.csproj" RefProjectPath="$(RepoRoot)src\Servers\Kestrel\Kestrel\ref\Microsoft.AspNetCore.Server.Kestrel.csproj" /> <ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Kestrel\src\Microsoft.AspNetCore.Server.Kestrel.csproj" RefProjectPath="$(RepoRoot)src\Servers\Kestrel\Kestrel\ref\Microsoft.AspNetCore.Server.Kestrel.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.Libuv\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj" RefProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.Libuv\ref\Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj" /> <ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.Libuv\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj" RefProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.Libuv\ref\Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.MsQuic\src\Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj" RefProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.MsQuic\ref\Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj" /> <ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Quic" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.Quic\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj" RefProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.Quic\ref\Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.Sockets\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj" RefProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.Sockets\ref\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj" /> <ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.Sockets\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj" RefProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.Sockets\ref\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Authentication.Certificate" ProjectPath="$(RepoRoot)src\Security\Authentication\Certificate\src\Microsoft.AspNetCore.Authentication.Certificate.csproj" RefProjectPath="$(RepoRoot)src\Security\Authentication\Certificate\ref\Microsoft.AspNetCore.Authentication.Certificate.csproj" /> <ProjectReferenceProvider Include="Microsoft.AspNetCore.Authentication.Certificate" ProjectPath="$(RepoRoot)src\Security\Authentication\Certificate\src\Microsoft.AspNetCore.Authentication.Certificate.csproj" RefProjectPath="$(RepoRoot)src\Security\Authentication\Certificate\ref\Microsoft.AspNetCore.Authentication.Certificate.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Authentication.Cookies" ProjectPath="$(RepoRoot)src\Security\Authentication\Cookies\src\Microsoft.AspNetCore.Authentication.Cookies.csproj" RefProjectPath="$(RepoRoot)src\Security\Authentication\Cookies\ref\Microsoft.AspNetCore.Authentication.Cookies.csproj" /> <ProjectReferenceProvider Include="Microsoft.AspNetCore.Authentication.Cookies" ProjectPath="$(RepoRoot)src\Security\Authentication\Cookies\src\Microsoft.AspNetCore.Authentication.Cookies.csproj" RefProjectPath="$(RepoRoot)src\Security\Authentication\Cookies\ref\Microsoft.AspNetCore.Authentication.Cookies.csproj" />

View File

@ -198,7 +198,8 @@ namespace Microsoft.AspNetCore.Connections.Features
} }
public partial interface IQuicStreamFeature public partial interface IQuicStreamFeature
{ {
bool IsUnidirectional { get; } bool CanRead { get; }
bool CanWrite { get; }
long StreamId { get; } long StreamId { get; }
} }
public partial interface IQuicStreamListenerFeature public partial interface IQuicStreamListenerFeature

View File

@ -198,7 +198,8 @@ namespace Microsoft.AspNetCore.Connections.Features
} }
public partial interface IQuicStreamFeature public partial interface IQuicStreamFeature
{ {
bool IsUnidirectional { get; } bool CanRead { get; }
bool CanWrite { get; }
long StreamId { get; } long StreamId { get; }
} }
public partial interface IQuicStreamListenerFeature public partial interface IQuicStreamListenerFeature

View File

@ -198,7 +198,8 @@ namespace Microsoft.AspNetCore.Connections.Features
} }
public partial interface IQuicStreamFeature public partial interface IQuicStreamFeature
{ {
bool IsUnidirectional { get; } bool CanRead { get; }
bool CanWrite { get; }
long StreamId { get; } long StreamId { get; }
} }
public partial interface IQuicStreamListenerFeature public partial interface IQuicStreamListenerFeature

View File

@ -5,7 +5,8 @@ namespace Microsoft.AspNetCore.Connections.Features
{ {
public interface IQuicStreamFeature public interface IQuicStreamFeature
{ {
bool IsUnidirectional { get; } bool CanRead { get; }
bool CanWrite { get; }
long StreamId { get; } long StreamId { get; }
} }
} }

View File

@ -93,7 +93,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
var streamId = streamFeature.StreamId; var streamId = streamFeature.StreamId;
HighestStreamId = streamId; HighestStreamId = streamId;
if (streamFeature.IsUnidirectional) if (!streamFeature.CanWrite)
{ {
var stream = new Http3ControlStream<TContext>(application, this, httpConnectionContext); var stream = new Http3ControlStream<TContext>(application, this, httpConnectionContext);
ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false);

View File

@ -80,7 +80,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
return true; return true;
} }
// TODO make this actually close the Http3Stream by telling msquic to close the stream. // TODO make this actually close the Http3Stream by telling quic to close the stream.
return false; return false;
} }

View File

@ -107,7 +107,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
return true; return true;
} }
// TODO make this actually close the Http3Stream by telling msquic to close the stream. // TODO make this actually close the Http3Stream by telling quic to close the stream.
return false; return false;
} }
@ -141,7 +141,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
} }
catch (Http3StreamErrorException) catch (Http3StreamErrorException)
{ {
// TODO // TODO
} }
finally finally
{ {
@ -151,7 +151,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
} }
catch (Exception) catch (Exception)
{ {
// TODO // TODO
} }
finally finally
{ {

View File

@ -86,7 +86,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "http2cat", "samples\http2ca
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuicSampleApp", "samples\QuicSampleApp\QuicSampleApp.csproj", "{53A8634C-DFC5-4A5B-8864-9EF1707E3F18}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuicSampleApp", "samples\QuicSampleApp\QuicSampleApp.csproj", "{53A8634C-DFC5-4A5B-8864-9EF1707E3F18}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic", "Transport.MsQuic\src\Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj", "{62CFF861-807E-43F6-9403-22AA7F06C9A6}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic", "Transport.Quic\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj", "{62CFF861-807E-43F6-9403-22AA7F06C9A6}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuicSampleClient", "samples\QuicSampleClient\QuicSampleClient.csproj", "{F39A942B-85A8-4C1B-A5BC-435555E79F20}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuicSampleClient", "samples\QuicSampleClient\QuicSampleClient.csproj", "{F39A942B-85A8-4C1B-A5BC-435555E79F20}"
EndProject EndProject

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<add key="general-testing" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/general-testing/nuget/v3/index.json" />
</packageSources>
</configuration>

View File

@ -1,10 +0,0 @@
## Using MsQuic on Windows
### Setup pre-requisites
1. Update machine to the latest Windows Insiders build (build number 19010 or later). This is required for TLS 1.3 support.
2. Copy msquic.dll and msquic.pdb to this directory and uncomment the copy task in Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj. This will copy the msquic.dll into any built project.
For external contributors, msquic.dll isn't available publicly yet. See https://github.com/aspnet/Announcements/issues/393.
Credit to Diwakar Mantha and the Kaizala team for the MsQuic interop code.

View File

@ -1,251 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
{
internal class MsQuicApi : IDisposable
{
private bool _disposed = false;
private IntPtr _registrationContext;
internal unsafe MsQuicApi()
{
var status = (uint)MsQuicNativeMethods.MsQuicOpen(version: 1, out var registration);
MsQuicStatusException.ThrowIfFailed(status);
NativeRegistration = *registration;
RegistrationOpenDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.RegistrationOpenDelegate>(
NativeRegistration.RegistrationOpen);
RegistrationCloseDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.RegistrationCloseDelegate>(
NativeRegistration.RegistrationClose);
SecConfigCreateDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SecConfigCreateDelegate>(
NativeRegistration.SecConfigCreate);
SecConfigDeleteDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SecConfigDeleteDelegate>(
NativeRegistration.SecConfigDelete);
SessionOpenDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SessionOpenDelegate>(
NativeRegistration.SessionOpen);
SessionCloseDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SessionCloseDelegate>(
NativeRegistration.SessionClose);
SessionShutdownDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SessionShutdownDelegate>(
NativeRegistration.SessionShutdown);
ListenerOpenDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ListenerOpenDelegate>(
NativeRegistration.ListenerOpen);
ListenerCloseDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ListenerCloseDelegate>(
NativeRegistration.ListenerClose);
ListenerStartDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ListenerStartDelegate>(
NativeRegistration.ListenerStart);
ListenerStopDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ListenerStopDelegate>(
NativeRegistration.ListenerStop);
ConnectionOpenDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ConnectionOpenDelegate>(
NativeRegistration.ConnectionOpen);
ConnectionCloseDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ConnectionCloseDelegate>(
NativeRegistration.ConnectionClose);
ConnectionShutdownDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ConnectionShutdownDelegate>(
NativeRegistration.ConnectionShutdown);
ConnectionStartDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ConnectionStartDelegate>(
NativeRegistration.ConnectionStart);
StreamOpenDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.StreamOpenDelegate>(
NativeRegistration.StreamOpen);
StreamCloseDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.StreamCloseDelegate>(
NativeRegistration.StreamClose);
StreamStartDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.StreamStartDelegate>(
NativeRegistration.StreamStart);
StreamShutdownDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.StreamShutdownDelegate>(
NativeRegistration.StreamShutdown);
StreamSendDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.StreamSendDelegate>(
NativeRegistration.StreamSend);
SetContextDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SetContextDelegate>(
NativeRegistration.SetContext);
GetContextDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.GetContextDelegate>(
NativeRegistration.GetContext);
SetCallbackHandlerDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SetCallbackHandlerDelegate>(
NativeRegistration.SetCallbackHandler);
SetParamDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SetParamDelegate>(
NativeRegistration.SetParam);
GetParamDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.GetParamDelegate>(
NativeRegistration.GetParam);
}
internal MsQuicNativeMethods.NativeApi NativeRegistration { get; private set; }
internal MsQuicNativeMethods.RegistrationOpenDelegate RegistrationOpenDelegate { get; private set; }
internal MsQuicNativeMethods.RegistrationCloseDelegate RegistrationCloseDelegate { get; private set; }
internal MsQuicNativeMethods.SecConfigCreateDelegate SecConfigCreateDelegate { get; private set; }
internal MsQuicNativeMethods.SecConfigCreateCompleteDelegate SecConfigCreateCompleteDelegate { get; private set; }
internal MsQuicNativeMethods.SecConfigDeleteDelegate SecConfigDeleteDelegate { get; private set; }
internal MsQuicNativeMethods.SessionOpenDelegate SessionOpenDelegate { get; private set; }
internal MsQuicNativeMethods.SessionCloseDelegate SessionCloseDelegate { get; private set; }
internal MsQuicNativeMethods.SessionShutdownDelegate SessionShutdownDelegate { get; private set; }
internal MsQuicNativeMethods.ListenerOpenDelegate ListenerOpenDelegate { get; private set; }
internal MsQuicNativeMethods.ListenerCloseDelegate ListenerCloseDelegate { get; private set; }
internal MsQuicNativeMethods.ListenerStartDelegate ListenerStartDelegate { get; private set; }
internal MsQuicNativeMethods.ListenerStopDelegate ListenerStopDelegate { get; private set; }
internal MsQuicNativeMethods.ConnectionOpenDelegate ConnectionOpenDelegate { get; private set; }
internal MsQuicNativeMethods.ConnectionCloseDelegate ConnectionCloseDelegate { get; private set; }
internal MsQuicNativeMethods.ConnectionShutdownDelegate ConnectionShutdownDelegate { get; private set; }
internal MsQuicNativeMethods.ConnectionStartDelegate ConnectionStartDelegate { get; private set; }
internal MsQuicNativeMethods.StreamOpenDelegate StreamOpenDelegate { get; private set; }
internal MsQuicNativeMethods.StreamCloseDelegate StreamCloseDelegate { get; private set; }
internal MsQuicNativeMethods.StreamStartDelegate StreamStartDelegate { get; private set; }
internal MsQuicNativeMethods.StreamShutdownDelegate StreamShutdownDelegate { get; private set; }
internal MsQuicNativeMethods.StreamSendDelegate StreamSendDelegate { get; private set; }
internal MsQuicNativeMethods.StreamReceiveCompleteDelegate StreamReceiveComplete { get; private set; }
internal MsQuicNativeMethods.SetContextDelegate SetContextDelegate { get; private set; }
internal MsQuicNativeMethods.GetContextDelegate GetContextDelegate { get; private set; }
internal MsQuicNativeMethods.SetCallbackHandlerDelegate SetCallbackHandlerDelegate { get; private set; }
internal MsQuicNativeMethods.SetParamDelegate SetParamDelegate { get; private set; }
internal MsQuicNativeMethods.GetParamDelegate GetParamDelegate { get; private set; }
internal void RegistrationOpen(byte[] name)
{
MsQuicStatusException.ThrowIfFailed(RegistrationOpenDelegate(name, out var ctx));
_registrationContext = ctx;
}
internal unsafe uint UnsafeSetParam(
IntPtr Handle,
uint Level,
uint Param,
MsQuicNativeMethods.QuicBuffer Buffer)
{
return SetParamDelegate(
Handle,
Level,
Param,
Buffer.Length,
Buffer.Buffer);
}
internal unsafe uint UnsafeGetParam(
IntPtr Handle,
uint Level,
uint Param,
ref MsQuicNativeMethods.QuicBuffer Buffer)
{
return GetParamDelegate(
Handle,
Level,
Param,
out Buffer.Length,
out Buffer.Buffer);
}
public async ValueTask<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);
GC.SuppressFinalize(this);
}
~MsQuicApi()
{
Dispose(disposing: false);
}
private void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
RegistrationCloseDelegate?.Invoke(_registrationContext);
_disposed = true;
}
}
}

View File

@ -1,336 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.InteropServices;
using System.Threading.Channels;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Abstractions.Features;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
using static Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal.MsQuicNativeMethods;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
{
internal class MsQuicConnection : TransportConnection, IQuicStreamListenerFeature, IQuicCreateStreamFeature, IDisposable
{
public MsQuicApi _api;
private bool _disposed;
private readonly MsQuicTransportContext _context;
private readonly IMsQuicTrace _log;
private IntPtr _nativeObjPtr;
private static GCHandle _handle;
private ConnectionCallbackDelegate _connectionDelegate;
private readonly Channel<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<ITlsConnectionFeature>(new FakeTlsConnectionFeature());
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

@ -1,197 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using static Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal.MsQuicNativeMethods;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
{
/// <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

@ -1,151 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.InteropServices;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
{
internal static class MsQuicConstants
{
internal static uint InternalError = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Windows.InternalError : Linux.InternalError;
internal static uint Success = 0;
internal static uint Pending = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Windows.Pending : Linux.Pending;
private const uint SuccessConst = 0;
internal static Func<uint, string> ErrorTypeFromErrorCode = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Windows.GetError : (Func<uint, string>)Linux.GetError;
internal static class Windows
{
internal const uint Pending = 0x703E5;
internal const uint Continue = 0x704DE;
internal const uint OutOfMemory = 0x8007000E;
internal const uint InvalidParameter = 0x80070057;
internal const uint InvalidState = 0x8007139F;
internal const uint NotSupported = 0x80004002;
internal const uint NotFound = 0x80070490;
internal const uint BufferTooSmall = 0x8007007A;
internal const uint HandshakeFailure = 0x80410000;
internal const uint Aborted = 0x80004004;
internal const uint AddressInUse = 0x80072740;
internal const uint ConnectionTimeout = 0x800704CF;
internal const uint ConnectionIdle = 0x800704D4;
internal const uint InternalError = 0x80004005;
internal const uint ServerBusy = 0x800704C9;
internal const uint ProtocolError = 0x800704CD;
internal const uint VerNegError = 0x80410001;
// TODO return better error messages here.
public static string GetError(uint status)
{
switch (status)
{
case SuccessConst:
return "SUCCESS";
case Pending:
return "PENDING";
case Continue:
return "CONTINUE";
case OutOfMemory:
return "OUT_OF_MEMORY";
case InvalidParameter:
return "INVALID_PARAMETER";
case InvalidState:
return "INVALID_STATE";
case NotSupported:
return "NOT_SUPPORTED";
case NotFound:
return "NOT_FOUND";
case BufferTooSmall:
return "BUFFER_TOO_SMALL";
case HandshakeFailure:
return "HANDSHAKE_FAILURE";
case Aborted:
return "ABORTED";
case AddressInUse:
return "ADDRESS_IN_USE";
case ConnectionTimeout:
return "CONNECTION_TIMEOUT";
case ConnectionIdle:
return "CONNECTION_IDLE";
case InternalError:
return "INTERNAL_ERROR";
case ServerBusy:
return "SERVER_BUSY";
case ProtocolError:
return "PROTOCOL_ERROR";
case VerNegError:
return "VER_NEG_ERROR";
}
return status.ToString();
}
}
internal static class Linux
{
internal const uint Pending = unchecked((uint)-2);
internal const uint Continue = unchecked((uint)-1);
internal const uint OutOfMemory = 12;
internal const uint InvalidParameter = 22;
internal const uint InvalidState = 200000002;
internal const uint NotSupported = 95;
internal const uint NotFound = 2;
internal const uint BufferTooSmall = 75;
internal const uint HandshakeFailure = 200000009;
internal const uint Aborted = 200000008;
internal const uint AddressInUse = 98;
internal const uint ConnectionTimeout = 110;
internal const uint ConnectionIdle = 200000011;
internal const uint InternalError = 200000012;
internal const uint ServerBusy = 200000007;
internal const uint ProtocolError = 200000013;
internal const uint VerNegError = 200000014;
public static string GetError(uint status)
{
switch (status)
{
case SuccessConst:
return "SUCCESS";
case Pending:
return "PENDING";
case Continue:
return "CONTINUE";
case OutOfMemory:
return "OUT_OF_MEMORY";
case InvalidParameter:
return "INVALID_PARAMETER";
case InvalidState:
return "INVALID_STATE";
case NotSupported:
return "NOT_SUPPORTED";
case NotFound:
return "NOT_FOUND";
case BufferTooSmall:
return "BUFFER_TOO_SMALL";
case HandshakeFailure:
return "HANDSHAKE_FAILURE";
case Aborted:
return "ABORTED";
case AddressInUse:
return "ADDRESS_IN_USE";
case ConnectionTimeout:
return "CONNECTION_TIMEOUT";
case ConnectionIdle:
return "CONNECTION_IDLE";
case InternalError:
return "INTERNAL_ERROR";
case ServerBusy:
return "SERVER_BUSY";
case ProtocolError:
return "PROTOCOL_ERROR";
case VerNegError:
return "VER_NEG_ERROR";
}
return status.ToString();
}
}
}
}

View File

@ -1,178 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
{
/// <summary>
/// Flags to pass when creating a certificate hash store.
/// </summary>
[Flags]
internal enum QUIC_CERT_HASH_STORE_FLAG : uint
{
NONE = 0,
MACHINE_CERT = 0x0001,
}
/// <summary>
/// Flags to pass when creating a security config.
/// </summary>
[Flags]
internal enum QUIC_SEC_CONFIG_FLAG : uint
{
NONE = 0,
CERT_HASH = 0x00000001,
CERT_HASH_STORE = 0x00000002,
CERT_CONTEXT = 0x00000004,
CERT_FILE = 0x00000008,
ENABL_OCSP = 0x00000010,
CERT_NULL = 0xF0000000,
}
internal enum QUIC_LISTENER_EVENT : byte
{
NEW_CONNECTION = 0
}
internal enum QUIC_CONNECTION_EVENT : byte
{
CONNECTED = 0,
SHUTDOWN_BEGIN = 1,
SHUTDOWN_BEGIN_PEER = 2,
SHUTDOWN_COMPLETE = 3,
LOCAL_ADDR_CHANGED = 4,
PEER_ADDR_CHANGED = 5,
NEW_STREAM = 6,
STREAMS_AVAILABLE = 7,
PEER_NEEDS_STREAMS = 8,
IDEAL_SEND_BUFFER = 9,
}
[Flags]
internal enum QUIC_CONNECTION_SHUTDOWN_FLAG : uint
{
NONE = 0x0,
SILENT = 0x1
}
internal enum QUIC_PARAM_LEVEL : uint
{
REGISTRATION = 0,
SESSION = 1,
LISTENER = 2,
CONNECTION = 3,
TLS = 4,
STREAM = 5,
}
internal enum QUIC_PARAM_REGISTRATION : uint
{
RETRY_MEMORY_PERCENT = 0,
CID_PREFIX = 1
}
internal enum QUIC_PARAM_SESSION : uint
{
TLS_TICKET_KEY = 0,
PEER_BIDI_STREAM_COUNT = 1,
PEER_UNIDI_STREAM_COUNT = 2,
IDLE_TIMEOUT = 3,
DISCONNECT_TIMEOUT = 4,
MAX_BYTES_PER_KEY = 5
}
internal enum QUIC_PARAM_LISTENER : uint
{
LOCAL_ADDRESS = 0
}
internal enum QUIC_PARAM_CONN : uint
{
QUIC_VERSION = 0,
LOCAL_ADDRESS = 1,
REMOTE_ADDRESS = 2,
IDLE_TIMEOUT = 3,
PEER_BIDI_STREAM_COUNT = 4,
PEER_UNIDI_STREAM_COUNT = 5,
LOCAL_BIDI_STREAM_COUNT = 6,
LOCAL_UNIDI_STREAM_COUNT = 7,
CLOSE_REASON_PHRASE = 8,
STATISTICS = 9,
STATISTICS_PLAT = 10,
CERT_VALIDATION_FLAGS = 11,
KEEP_ALIVE_ENABLED = 12,
DISCONNECT_TIMEOUT = 13,
SEC_CONFIG = 14,
USE_SEND_BUFFER = 15,
USE_PACING = 16,
SHARE_UDP_BINDING = 17,
IDEAL_PROCESSOR = 18,
MAX_STREAM_IDS = 19
}
internal enum QUIC_PARAM_STREAM : uint
{
ID = 0,
RECEIVE_ENABLED = 1,
ZERORTT_LENGTH = 2,
IDEAL_SEND_BUFFER = 3
}
internal enum QUIC_STREAM_EVENT : byte
{
START_COMPLETE = 0,
RECV = 1,
SEND_COMPLETE = 2,
PEER_SEND_CLOSE = 3,
PEER_SEND_ABORT = 4,
PEER_RECV_ABORT = 5,
SEND_SHUTDOWN_COMPLETE = 6,
SHUTDOWN_COMPLETE = 7,
IDEAL_SEND_BUFFER_SIZE = 8,
}
[Flags]
internal enum QUIC_STREAM_OPEN_FLAG : uint
{
NONE = 0,
UNIDIRECTIONAL = 0x1,
ZERO_RTT = 0x2,
}
[Flags]
internal enum QUIC_STREAM_START_FLAG : uint
{
NONE = 0,
FAIL_BLOCKED = 0x1,
IMMEDIATE = 0x2,
ASYNC = 0x4,
}
[Flags]
internal enum QUIC_STREAM_SHUTDOWN_FLAG : uint
{
NONE = 0,
GRACEFUL = 0x1,
ABORT_SEND = 0x2,
ABORT_RECV = 0x4,
ABORT = 0x6,
IMMEDIATE = 0x8
}
[Flags]
internal enum QUIC_SEND_FLAG : uint
{
NONE = 0,
ALLOW_0_RTT = 0x00000001,
FIN = 0x00000002,
}
[Flags]
internal enum QUIC_RECV_FLAG : byte
{
NONE = 0,
ZERO_RTT = 0x1,
FIN = 0x02
}
}

View File

@ -1,590 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
{
/// <summary>
/// Contains all native delegates and structs that are used with MsQuic.
/// </summary>
internal unsafe static class MsQuicNativeMethods
{
internal const string dllName = "msquic";
[DllImport(dllName)]
internal static extern int MsQuicOpen(int version, out NativeApi* registration);
[StructLayout(LayoutKind.Sequential)]
internal struct NativeApi
{
internal uint Version;
internal IntPtr SetContext;
internal IntPtr GetContext;
internal IntPtr SetCallbackHandler;
internal IntPtr SetParam;
internal IntPtr GetParam;
internal IntPtr RegistrationOpen;
internal IntPtr RegistrationClose;
internal IntPtr SecConfigCreate;
internal IntPtr SecConfigDelete;
internal IntPtr SessionOpen;
internal IntPtr SessionClose;
internal IntPtr SessionShutdown;
internal IntPtr ListenerOpen;
internal IntPtr ListenerClose;
internal IntPtr ListenerStart;
internal IntPtr ListenerStop;
internal IntPtr ConnectionOpen;
internal IntPtr ConnectionClose;
internal IntPtr ConnectionShutdown;
internal IntPtr ConnectionStart;
internal IntPtr StreamOpen;
internal IntPtr StreamClose;
internal IntPtr StreamStart;
internal IntPtr StreamShutdown;
internal IntPtr StreamSend;
internal IntPtr StreamReceiveComplete;
}
internal delegate uint SetContextDelegate(
IntPtr Handle,
IntPtr Context);
internal delegate IntPtr GetContextDelegate(
IntPtr Handle);
internal delegate void SetCallbackHandlerDelegate(
IntPtr Handle,
Delegate del,
IntPtr Context);
internal delegate uint SetParamDelegate(
IntPtr Handle,
uint Level,
uint Param,
uint BufferLength,
byte* Buffer);
internal delegate uint GetParamDelegate(
IntPtr Handle,
uint Level,
uint Param,
out uint BufferLength,
out byte* Buffer);
internal delegate uint RegistrationOpenDelegate(byte[] appName, out IntPtr RegistrationContext);
internal delegate void RegistrationCloseDelegate(IntPtr RegistrationContext);
[StructLayout(LayoutKind.Sequential)]
internal struct CertHash
{
internal const int ShaHashLength = 20;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = ShaHashLength)]
internal byte[] ShaHash;
}
[StructLayout(LayoutKind.Sequential)]
internal struct CertHashStore
{
internal const int ShaHashLength = 20;
internal const int StoreNameLength = 128;
internal uint Flags;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = ShaHashLength)]
internal byte[] ShaHash;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = StoreNameLength)]
internal byte[] StoreName;
}
[StructLayout(LayoutKind.Sequential)]
internal struct CertFile
{
[MarshalAs(UnmanagedType.ByValArray)]
internal byte[] ShaHashUtf8;
[MarshalAs(UnmanagedType.ByValArray)]
internal byte[] StoreNameUtf8;
}
internal delegate void SecConfigCreateCompleteDelegate(IntPtr Context, uint Status, IntPtr SecurityConfig);
internal delegate uint SecConfigCreateDelegate(
IntPtr RegistrationContext,
uint Flags,
IntPtr Certificate,
[MarshalAs(UnmanagedType.LPStr)]string Principal,
IntPtr Context,
SecConfigCreateCompleteDelegate CompletionHandler);
internal delegate void SecConfigDeleteDelegate(
IntPtr SecurityConfig);
internal delegate uint SessionOpenDelegate(
IntPtr RegistrationContext,
byte[] utf8String,
IntPtr Context,
ref IntPtr Session);
internal delegate void SessionCloseDelegate(
IntPtr Session);
internal delegate void SessionShutdownDelegate(
IntPtr Session,
uint Flags,
ushort ErrorCode);
[StructLayout(LayoutKind.Sequential)]
internal struct ListenerEvent
{
internal QUIC_LISTENER_EVENT Type;
internal ListenerEventDataUnion Data;
}
[StructLayout(LayoutKind.Explicit)]
internal struct ListenerEventDataUnion
{
[FieldOffset(0)]
internal ListenerEventDataNewConnection NewConnection;
}
[StructLayout(LayoutKind.Sequential)]
internal struct ListenerEventDataNewConnection
{
internal IntPtr Info;
internal IntPtr Connection;
internal IntPtr SecurityConfig;
internal static string BufferToString(IntPtr buffer, ushort bufferLength)
{
if (bufferLength == 0)
{
return "";
}
var utf8Bytes = new byte[bufferLength]; // TODO: Avoid extra alloc and copy.
Marshal.Copy(buffer, utf8Bytes, 0, bufferLength);
var str = Encoding.UTF8.GetString(utf8Bytes);
return str;
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct NewConnectionInfo
{
internal uint QuicVersion;
internal IntPtr LocalAddress;
internal IntPtr RemoteAddress;
internal ushort CryptoBufferLength;
internal ushort AlpnListLength;
internal ushort ServerNameLength;
internal IntPtr CryptoBuffer;
internal IntPtr AlpnList;
internal IntPtr ServerName;
}
internal delegate uint ListenerCallbackDelegate(
IntPtr listener,
IntPtr context,
ref ListenerEvent evt);
internal delegate uint ListenerOpenDelegate(
IntPtr session,
ListenerCallbackDelegate handler,
IntPtr context,
out IntPtr listener);
internal delegate uint ListenerCloseDelegate(
IntPtr listener);
internal delegate uint ListenerStartDelegate(
IntPtr listener,
ref SOCKADDR_INET localAddress);
internal delegate uint ListenerStopDelegate(
IntPtr listener);
[StructLayout(LayoutKind.Sequential)]
internal struct ConnectionEventDataConnected
{
internal bool EarlyDataAccepted;
}
[StructLayout(LayoutKind.Sequential)]
internal struct ConnectionEventDataShutdownBegin
{
internal uint Status;
}
[StructLayout(LayoutKind.Sequential)]
internal struct ConnectionEventDataShutdownBeginPeer
{
internal ushort ErrorCode;
}
[StructLayout(LayoutKind.Sequential)]
internal struct ConnectionEventDataShutdownComplete
{
internal bool TimedOut;
}
[StructLayout(LayoutKind.Sequential)]
internal struct ConnectionEventDataLocalAddrChanged
{
internal IntPtr Address; // TODO this needs to be IPV4 and IPV6
}
[StructLayout(LayoutKind.Sequential)]
internal struct ConnectionEventDataPeerAddrChanged
{
internal IntPtr Address; // TODO this needs to be IPV4 and IPV6
}
[StructLayout(LayoutKind.Sequential)]
internal struct ConnectionEventDataNewStream
{
internal IntPtr Stream;
internal QUIC_STREAM_OPEN_FLAG Flags;
}
[StructLayout(LayoutKind.Sequential)]
internal struct ConnectionEventDataStreamsAvailable
{
internal ushort BiDirectionalCount;
internal ushort UniDirectionalCount;
}
[StructLayout(LayoutKind.Sequential)]
internal struct ConnectionEventDataIdealSendBuffer
{
internal ulong NumBytes;
}
[StructLayout(LayoutKind.Explicit)]
internal struct ConnectionEventDataUnion
{
[FieldOffset(0)]
internal ConnectionEventDataConnected Connected;
[FieldOffset(0)]
internal ConnectionEventDataShutdownBegin ShutdownBegin;
[FieldOffset(0)]
internal ConnectionEventDataShutdownBeginPeer ShutdownBeginPeer;
[FieldOffset(0)]
internal ConnectionEventDataShutdownComplete ShutdownComplete;
[FieldOffset(0)]
internal ConnectionEventDataLocalAddrChanged LocalAddrChanged;
[FieldOffset(0)]
internal ConnectionEventDataPeerAddrChanged PeerAddrChanged;
[FieldOffset(0)]
internal ConnectionEventDataNewStream NewStream;
[FieldOffset(0)]
internal ConnectionEventDataStreamsAvailable StreamsAvailable;
[FieldOffset(0)]
internal ConnectionEventDataIdealSendBuffer IdealSendBuffer;
}
[StructLayout(LayoutKind.Sequential)]
internal struct ConnectionEvent
{
internal QUIC_CONNECTION_EVENT Type;
internal ConnectionEventDataUnion Data;
internal bool EarlyDataAccepted => Data.Connected.EarlyDataAccepted;
internal ulong NumBytes => Data.IdealSendBuffer.NumBytes;
internal IPEndPoint LocalAddress => null; // TODO
internal IPEndPoint PeerAddress => null; // TODO
internal uint ShutdownBeginStatus => Data.ShutdownBegin.Status;
internal ushort ShutdownBeginPeerStatus => Data.ShutdownBeginPeer.ErrorCode;
internal bool ShutdownTimedOut => Data.ShutdownComplete.TimedOut;
internal ushort BiDirectionalCount => Data.StreamsAvailable.BiDirectionalCount;
internal ushort UniDirectionalCount => Data.StreamsAvailable.UniDirectionalCount;
internal QUIC_STREAM_OPEN_FLAG StreamFlags => Data.NewStream.Flags;
}
internal delegate uint ConnectionCallbackDelegate(
IntPtr Connection,
IntPtr Context,
ref ConnectionEvent Event);
internal delegate uint ConnectionOpenDelegate(
IntPtr Session,
ConnectionCallbackDelegate Handler,
IntPtr Context,
out IntPtr Connection);
internal delegate uint ConnectionCloseDelegate(
IntPtr Connection);
internal delegate uint ConnectionStartDelegate(
IntPtr Connection,
ushort Family,
[MarshalAs(UnmanagedType.LPStr)]
string ServerName,
ushort ServerPort);
internal delegate uint ConnectionShutdownDelegate(
IntPtr Connection,
uint Flags,
ushort ErrorCode);
[StructLayout(LayoutKind.Sequential)]
internal struct StreamEventDataRecv
{
internal ulong AbsoluteOffset;
internal ulong TotalBufferLength;
internal QuicBuffer* Buffers;
internal uint BufferCount;
internal byte Flags;
}
[StructLayout(LayoutKind.Explicit)]
internal struct StreamEventDataSendComplete
{
[FieldOffset(7)]
internal byte Canceled;
[FieldOffset(8)]
internal IntPtr ClientContext;
internal bool IsCanceled()
{
return Canceled != 0;
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct StreamEventDataPeerSendAbort
{
internal ushort ErrorCode;
}
[StructLayout(LayoutKind.Sequential)]
internal struct StreamEventDataPeerRecvAbort
{
internal ushort ErrorCode;
}
[StructLayout(LayoutKind.Sequential)]
internal struct StreamEventDataSendShutdownComplete
{
internal bool Graceful;
}
[StructLayout(LayoutKind.Explicit)]
internal struct StreamEventDataUnion
{
[FieldOffset(0)]
internal StreamEventDataRecv Recv;
[FieldOffset(0)]
internal StreamEventDataSendComplete SendComplete;
[FieldOffset(0)]
internal StreamEventDataPeerSendAbort PeerSendAbort;
[FieldOffset(0)]
internal StreamEventDataPeerRecvAbort PeerRecvAbort;
[FieldOffset(0)]
internal StreamEventDataSendShutdownComplete SendShutdownComplete;
}
[StructLayout(LayoutKind.Sequential)]
internal struct StreamEvent
{
internal QUIC_STREAM_EVENT Type;
internal StreamEventDataUnion Data;
}
[StructLayout(LayoutKind.Sequential)]
internal struct SOCKADDR_IN
{
internal ushort sin_family;
internal ushort sin_port;
internal byte sin_addr0;
internal byte sin_addr1;
internal byte sin_addr2;
internal byte sin_addr3;
internal byte[] Address
{
get
{
return new byte[] { sin_addr0, sin_addr1, sin_addr2, sin_addr3 };
}
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct SOCKADDR_IN6
{
internal ushort sin6_family;
internal ushort sin6_port;
internal uint sin6_flowinfo;
internal byte sin6_addr0;
internal byte sin6_addr1;
internal byte sin6_addr2;
internal byte sin6_addr3;
internal byte sin6_addr4;
internal byte sin6_addr5;
internal byte sin6_addr6;
internal byte sin6_addr7;
internal byte sin6_addr8;
internal byte sin6_addr9;
internal byte sin6_addr10;
internal byte sin6_addr11;
internal byte sin6_addr12;
internal byte sin6_addr13;
internal byte sin6_addr14;
internal byte sin6_addr15;
internal uint sin6_scope_id;
internal byte[] Address
{
get
{
return new byte[] {
sin6_addr0, sin6_addr1, sin6_addr2, sin6_addr3 ,
sin6_addr4, sin6_addr5, sin6_addr6, sin6_addr7 ,
sin6_addr8, sin6_addr9, sin6_addr10, sin6_addr11 ,
sin6_addr12, sin6_addr13, sin6_addr14, sin6_addr15 };
}
}
}
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
internal struct SOCKADDR_INET
{
[FieldOffset(0)]
internal SOCKADDR_IN Ipv4;
[FieldOffset(0)]
internal SOCKADDR_IN6 Ipv6;
[FieldOffset(0)]
internal ushort si_family;
}
internal delegate uint StreamCallbackDelegate(
IntPtr Stream,
IntPtr Context,
StreamEvent Event);
internal delegate uint StreamOpenDelegate(
IntPtr Connection,
uint Flags,
StreamCallbackDelegate Handler,
IntPtr Context,
out IntPtr Stream);
internal delegate uint StreamStartDelegate(
IntPtr Stream,
uint Flags
);
internal delegate uint StreamCloseDelegate(
IntPtr Stream);
internal delegate uint StreamShutdownDelegate(
IntPtr Stream,
uint Flags,
ushort ErrorCode);
internal delegate uint StreamSendDelegate(
IntPtr Stream,
QuicBuffer* Buffers,
uint BufferCount,
uint Flags,
IntPtr ClientSendContext);
internal delegate uint StreamReceiveCompleteDelegate(
IntPtr Stream,
ulong BufferLength);
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct QuicBuffer
{
internal uint Length;
internal byte* Buffer;
}
private const ushort IPv4 = 2;
private const ushort IPv6 = 23;
public static SOCKADDR_INET Convert(IPEndPoint endpoint)
{
var socketAddress = new SOCKADDR_INET();
var buffer = endpoint.Address.GetAddressBytes();
if (endpoint.Address != IPAddress.Any && endpoint.Address != IPAddress.IPv6Any)
{
switch (endpoint.Address.AddressFamily)
{
case AddressFamily.InterNetwork:
socketAddress.Ipv4.sin_addr0 = buffer[0];
socketAddress.Ipv4.sin_addr1 = buffer[1];
socketAddress.Ipv4.sin_addr2 = buffer[2];
socketAddress.Ipv4.sin_addr3 = buffer[3];
socketAddress.Ipv4.sin_family = IPv4;
break;
case AddressFamily.InterNetworkV6:
socketAddress.Ipv6.sin6_addr0 = buffer[0];
socketAddress.Ipv6.sin6_addr1 = buffer[1];
socketAddress.Ipv6.sin6_addr2 = buffer[2];
socketAddress.Ipv6.sin6_addr3 = buffer[3];
socketAddress.Ipv6.sin6_addr4 = buffer[4];
socketAddress.Ipv6.sin6_addr5 = buffer[5];
socketAddress.Ipv6.sin6_addr6 = buffer[6];
socketAddress.Ipv6.sin6_addr7 = buffer[7];
socketAddress.Ipv6.sin6_addr8 = buffer[8];
socketAddress.Ipv6.sin6_addr9 = buffer[9];
socketAddress.Ipv6.sin6_addr10 = buffer[10];
socketAddress.Ipv6.sin6_addr11 = buffer[11];
socketAddress.Ipv6.sin6_addr12 = buffer[12];
socketAddress.Ipv6.sin6_addr13 = buffer[13];
socketAddress.Ipv6.sin6_addr14 = buffer[14];
socketAddress.Ipv6.sin6_addr15 = buffer[15];
socketAddress.Ipv6.sin6_family = IPv6;
break;
default:
throw new ArgumentException("Only IPv4 or IPv6 are supported");
}
}
SetPort(endpoint.Address.AddressFamily, ref socketAddress, endpoint.Port);
return socketAddress;
}
private static void SetPort(AddressFamily addressFamily, ref SOCKADDR_INET socketAddrInet, int originalPort)
{
var convertedPort = (ushort)IPAddress.HostToNetworkOrder((short)originalPort);
switch (addressFamily)
{
case AddressFamily.InterNetwork:
socketAddrInet.Ipv4.sin_port = convertedPort;
break;
case AddressFamily.InterNetworkV6:
default:
socketAddrInet.Ipv6.sin6_port = convertedPort;
break;
}
}
}
}

View File

@ -1,42 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
{
internal class MsQuicStatusException : Exception
{
internal MsQuicStatusException(uint status)
: this(status, null)
{
}
internal MsQuicStatusException(uint status, string message)
: this(status, message, null)
{
}
internal MsQuicStatusException(uint status, string message, Exception innerException)
: base(GetMessage(status, message), innerException)
{
Status = status;
}
internal uint Status { get; }
private static string GetMessage(uint status, string message)
{
var errorCode = MsQuicConstants.ErrorTypeFromErrorCode(status);
return $"Quic Error: {errorCode}. " + message;
}
internal static void ThrowIfFailed(uint status, string message = null, Exception innerException = null)
{
if (!MsQuicStatusHelper.Succeeded(status))
{
throw new MsQuicStatusException(status, message, innerException);
}
}
}
}

View File

@ -1,25 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Runtime.InteropServices;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
{
internal static class MsQuicStatusHelper
{
internal static bool Succeeded(uint status)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return status < 0x80000000;
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return (int)status <= 0;
}
return false;
}
}
}

View File

@ -1,477 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Buffers;
using System.Diagnostics;
using System.IO.Pipelines;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Logging;
using static Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal.MsQuicNativeMethods;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
{
internal class MsQuicStream : TransportConnection, IQuicStreamFeature
{
private Task _processingTask;
private MsQuicConnection _connection;
private readonly CancellationTokenSource _streamClosedTokenSource = new CancellationTokenSource();
private IMsQuicTrace _log;
private bool _disposed;
private IntPtr _nativeObjPtr;
private GCHandle _handle;
private StreamCallbackDelegate _delegate;
private string _connectionId;
private long _streamId = -1;
internal ResettableCompletionSource _resettableCompletion;
private MemoryHandle[] _bufferArrays;
private GCHandle _sendBuffer;
public MsQuicStream(MsQuicApi api, MsQuicConnection connection, MsQuicTransportContext context, QUIC_STREAM_OPEN_FLAG flags, IntPtr nativeObjPtr)
{
Debug.Assert(connection != null);
Api = api;
_nativeObjPtr = nativeObjPtr;
_connection = connection;
MemoryPool = context.Options.MemoryPoolFactory();
_log = context.Log;
ConnectionClosed = _streamClosedTokenSource.Token;
var maxReadBufferSize = context.Options.MaxReadBufferSize.Value;
var maxWriteBufferSize = context.Options.MaxWriteBufferSize.Value;
_resettableCompletion = new ResettableCompletionSource(this);
// TODO should we allow these PipeScheduler to be configurable here?
var inputOptions = new PipeOptions(MemoryPool, PipeScheduler.ThreadPool, PipeScheduler.Inline, maxReadBufferSize, maxReadBufferSize / 2, useSynchronizationContext: false);
var outputOptions = new PipeOptions(MemoryPool, PipeScheduler.Inline, PipeScheduler.ThreadPool, maxWriteBufferSize, maxWriteBufferSize / 2, useSynchronizationContext: false);
var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions);
Features.Set<IQuicStreamFeature>(this);
// TODO populate the ITlsConnectionFeature (requires client certs).
Features.Set<ITlsConnectionFeature>(new FakeTlsConnectionFeature());
if (flags.HasFlag(QUIC_STREAM_OPEN_FLAG.UNIDIRECTIONAL))
{
IsUnidirectional = true;
}
Transport = pair.Transport;
Application = pair.Application;
SetCallbackHandler();
_processingTask = ProcessSends();
}
public override MemoryPool<byte> MemoryPool { get; }
public PipeWriter Input => Application.Output;
public PipeReader Output => Application.Input;
public bool IsUnidirectional { get; }
public long StreamId
{
get
{
if (_streamId == -1)
{
_streamId = GetStreamId();
}
return _streamId;
}
}
public override string ConnectionId {
get
{
if (_connectionId == null)
{
_connectionId = $"{_connection.ConnectionId}:{StreamId}";
}
return _connectionId;
}
set
{
_connectionId = value;
}
}
private async Task ProcessSends()
{
var output = Output;
try
{
while (true)
{
var result = await output.ReadAsync();
_log.LogDebug(0, "Handling send event");
if (result.IsCanceled)
{
// TODO how to get abort codepath sync'd
ShutDown(QUIC_STREAM_SHUTDOWN_FLAG.ABORT, 0);
break;
}
var buffer = result.Buffer;
var end = buffer.End;
var isCompleted = result.IsCompleted;
if (!buffer.IsEmpty)
{
await SendAsync(buffer, QUIC_SEND_FLAG.NONE);
}
output.AdvanceTo(end);
if (isCompleted)
{
// Once the stream pipe is closed, shutdown the stream.
ShutDown(QUIC_STREAM_SHUTDOWN_FLAG.GRACEFUL, 0);
break;
}
}
}
catch (Exception)
{
ShutDown(QUIC_STREAM_SHUTDOWN_FLAG.ABORT, 0);
}
}
internal uint HandleEvent(ref MsQuicNativeMethods.StreamEvent evt)
{
var status = MsQuicConstants.Success;
switch (evt.Type)
{
case QUIC_STREAM_EVENT.START_COMPLETE:
status = HandleStartComplete();
break;
case QUIC_STREAM_EVENT.RECV:
{
HandleEventRecv(
ref evt);
}
break;
case QUIC_STREAM_EVENT.SEND_COMPLETE:
{
status = HandleEventSendComplete(ref evt);
}
break;
case QUIC_STREAM_EVENT.PEER_SEND_CLOSE:
{
status = HandleEventPeerSendClose();
}
break;
// TODO figure out difference between SEND_ABORT and RECEIVE_ABORT
case QUIC_STREAM_EVENT.PEER_SEND_ABORT:
{
_streamClosedTokenSource.Cancel();
status = HandleEventPeerSendAbort();
}
break;
case QUIC_STREAM_EVENT.PEER_RECV_ABORT:
{
_streamClosedTokenSource.Cancel();
status = HandleEventPeerRecvAbort();
}
break;
case QUIC_STREAM_EVENT.SEND_SHUTDOWN_COMPLETE:
{
status = HandleEventSendShutdownComplete(ref evt);
}
break;
case QUIC_STREAM_EVENT.SHUTDOWN_COMPLETE:
{
Close();
return MsQuicConstants.Success;
}
default:
break;
}
return status;
}
private uint HandleEventPeerRecvAbort()
{
return MsQuicConstants.Success;
}
private uint HandleEventPeerSendAbort()
{
return MsQuicConstants.Success;
}
private uint HandleStartComplete()
{
_resettableCompletion.Complete(MsQuicConstants.Success);
return MsQuicConstants.Success;
}
private uint HandleEventSendShutdownComplete(ref MsQuicNativeMethods.StreamEvent evt)
{
return MsQuicConstants.Success;
}
private uint HandleEventPeerSendClose()
{
Input.Complete();
return MsQuicConstants.Success;
}
public uint HandleEventSendComplete(ref MsQuicNativeMethods.StreamEvent evt)
{
_sendBuffer.Free();
foreach (var gchBufferArray in _bufferArrays)
{
gchBufferArray.Dispose();
}
_resettableCompletion.Complete(evt.Data.PeerRecvAbort.ErrorCode);
return MsQuicConstants.Success;
}
protected void HandleEventRecv(ref MsQuicNativeMethods.StreamEvent evt)
{
static unsafe void CopyToBuffer(Span<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 unsafe long GetStreamId()
{
byte* ptr = stackalloc byte[sizeof(long)];
var buffer = new QuicBuffer
{
Length = sizeof(long),
Buffer = ptr
};
GetParam(QUIC_PARAM_STREAM.ID, buffer);
return *(long*)ptr;
}
private void GetParam(
QUIC_PARAM_STREAM param,
QuicBuffer buf)
{
MsQuicStatusException.ThrowIfFailed(Api.UnsafeGetParam(
_nativeObjPtr,
(uint)QUIC_PARAM_LEVEL.STREAM,
(uint)param,
ref buf));
}
private void SetParam(
QUIC_PARAM_STREAM param,
QuicBuffer buf)
{
MsQuicStatusException.ThrowIfFailed(Api.UnsafeSetParam(
_nativeObjPtr,
(uint)QUIC_PARAM_LEVEL.STREAM,
(uint)param,
buf));
}
~MsQuicStream()
{
Dispose(false);
}
private void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (_nativeObjPtr != IntPtr.Zero)
{
Api.StreamCloseDelegate?.Invoke(_nativeObjPtr);
}
_handle.Free();
_nativeObjPtr = IntPtr.Zero;
Api = null;
_disposed = true;
}
}
internal class FakeTlsConnectionFeature : ITlsConnectionFeature
{
public FakeTlsConnectionFeature()
{
}
public X509Certificate2 ClientCertificate { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public Task<X509Certificate2> GetClientCertificateAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
}

View File

@ -1,21 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Extensions.Hosting;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
{
internal class MsQuicTransportContext
{
public MsQuicTransportContext(IHostApplicationLifetime appLifetime, IMsQuicTrace log, MsQuicTransportOptions options)
{
AppLifetime = appLifetime;
Log = log;
Options = options;
}
public IHostApplicationLifetime AppLifetime { get; }
public IMsQuicTrace Log { get; }
public MsQuicTransportOptions Options { get; }
}
}

View File

@ -1,44 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
{
internal class QuicSecConfig : IDisposable
{
private bool _disposed;
private MsQuicApi _registration;
public QuicSecConfig(MsQuicApi registration, IntPtr nativeObjPtr)
{
_registration = registration;
NativeObjPtr = nativeObjPtr;
}
public IntPtr NativeObjPtr { get; private set; }
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
_registration.SecConfigDeleteDelegate?.Invoke(NativeObjPtr);
NativeObjPtr = IntPtr.Zero;
_disposed = true;
}
~QuicSecConfig()
{
Dispose(disposing: false);
}
}
}

View File

@ -1,141 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net;
using System.Threading.Tasks;
using static Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal.MsQuicNativeMethods;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
{
internal sealed class QuicSession : IDisposable
{
private bool _disposed = false;
private IntPtr _nativeObjPtr;
private MsQuicApi _registration;
internal QuicSession(MsQuicApi registration, IntPtr nativeObjPtr)
{
_registration = registration;
_nativeObjPtr = nativeObjPtr;
}
public async ValueTask<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

@ -1,58 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using System.Threading.Tasks.Sources;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
{
internal class ResettableCompletionSource : IValueTaskSource<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

@ -1,25 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Runtime.InteropServices;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
{
internal static class UIntExtensions
{
internal static bool Succeeded(this uint status)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return status >= 0x80000000;
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return (int)status <= 0;
}
return false;
}
}
}

View File

@ -1,64 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic
{
public class MsQuicConnectionFactory : IConnectionFactory
{
private MsQuicApi _api;
private QuicSession _session;
private bool _started;
private MsQuicTransportContext _transportContext;
public MsQuicConnectionFactory(IOptions<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(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

@ -1,47 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic
{
public class MsQuicTransportFactory : IMultiplexedConnectionListenerFactory
{
private MsQuicTrace _log;
private IHostApplicationLifetime _applicationLifetime;
private MsQuicTransportOptions _options;
public MsQuicTransportFactory(IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory, IOptions<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,3 @@
For external contributors, msquic.dll isn't available publicly yet. See https://github.com/aspnet/Announcements/issues/393.
Credit to Diwakar Mantha and the Kaizala team for the MsQuic interop code.

View File

@ -4,7 +4,7 @@
<TargetFrameworks>$(DefaultNetCoreTargetFramework)</TargetFrameworks> <TargetFrameworks>$(DefaultNetCoreTargetFramework)</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == '$(DefaultNetCoreTargetFramework)'"> <ItemGroup Condition="'$(TargetFramework)' == '$(DefaultNetCoreTargetFramework)'">
<Compile Include="Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.netcoreapp.cs" /> <Compile Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.netcoreapp.cs" />
<Reference Include="Microsoft.AspNetCore.Hosting.Abstractions" /> <Reference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
<Reference Include="Microsoft.AspNetCore.Connections.Abstractions" /> <Reference Include="Microsoft.AspNetCore.Connections.Abstractions" />
<Reference Include="Microsoft.Extensions.Logging.Abstractions" /> <Reference Include="Microsoft.Extensions.Logging.Abstractions" />

View File

@ -5,27 +5,27 @@ namespace Microsoft.AspNetCore.Hosting
{ {
public static partial class WebHostBuilderMsQuicExtensions 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 UseQuic(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; } public static Microsoft.AspNetCore.Hosting.IWebHostBuilder UseQuic(this Microsoft.AspNetCore.Hosting.IWebHostBuilder hostBuilder, System.Action<Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.QuicTransportOptions> configureOptions) { throw null; }
} }
} }
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic
{ {
public partial class MsQuicConnectionFactory : Microsoft.AspNetCore.Connections.IConnectionFactory public partial class QuicConnectionFactory : Microsoft.AspNetCore.Connections.IConnectionFactory
{ {
public MsQuicConnectionFactory(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.MsQuicTransportOptions> options, Microsoft.Extensions.Hosting.IHostApplicationLifetime lifetime, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public QuicConnectionFactory(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.QuicTransportOptions> options, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { }
[System.Diagnostics.DebuggerStepThroughAttribute] [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 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, Microsoft.AspNetCore.Connections.IMultiplexedConnectionListenerFactory public partial class QuicTransportFactory : Microsoft.AspNetCore.Connections.IConnectionListenerFactory, Microsoft.AspNetCore.Connections.IMultiplexedConnectionListenerFactory
{ {
public MsQuicTransportFactory(Microsoft.Extensions.Hosting.IHostApplicationLifetime applicationLifetime, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.MsQuicTransportOptions> options) { } public QuicTransportFactory(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.QuicTransportOptions> 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 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 partial class QuicTransportOptions
{ {
public MsQuicTransportOptions() { } public QuicTransportOptions() { }
public long AbortErrorCode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
public string Alpn { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public 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.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 System.TimeSpan IdleTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }

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;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
{
internal class FakeTlsConnectionFeature : ITlsConnectionFeature
{
public FakeTlsConnectionFeature()
{
}
public X509Certificate2 ClientCertificate { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public Task<X509Certificate2> GetClientCertificateAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
}

View File

@ -4,13 +4,16 @@
using System; using System;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
{ {
internal interface IMsQuicTrace : ILogger internal interface IQuicTrace : ILogger
{ {
void NewConnection(string connectionId); void NewConnection(string connectionId);
void NewStream(string streamId); void NewStream(string streamId);
void ConnectionError(string connectionId, Exception ex); void ConnectionError(string connectionId, Exception ex);
void StreamError(string streamId, Exception ex); void StreamError(string streamId, Exception ex);
void StreamPause(string streamId);
void StreamResume(string streamId);
void StreamShutdownWrite(string streamId, Exception ex);
} }
} }

View File

@ -0,0 +1,80 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Net.Quic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Abstractions.Features;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
{
internal class QuicConnectionContext : TransportConnection, IQuicStreamListenerFeature, IQuicCreateStreamFeature
{
private QuicConnection _connection;
private readonly QuicTransportContext _context;
private readonly IQuicTrace _log;
private ValueTask _closeTask;
public QuicConnectionContext(QuicConnection connection, QuicTransportContext context)
{
_log = context.Log;
_context = context;
_connection = connection;
Features.Set<ITlsConnectionFeature>(new FakeTlsConnectionFeature());
Features.Set<IQuicStreamListenerFeature>(this);
Features.Set<IQuicCreateStreamFeature>(this);
_log.NewConnection(ConnectionId);
}
public ValueTask<ConnectionContext> StartUnidirectionalStreamAsync()
{
var stream = _connection.OpenUnidirectionalStream();
return new ValueTask<ConnectionContext>(new QuicStreamContext(stream, this, _context));
}
public ValueTask<ConnectionContext> StartBidirectionalStreamAsync()
{
var stream = _connection.OpenBidirectionalStream();
return new ValueTask<ConnectionContext>(new QuicStreamContext(stream, this, _context));
}
public override async ValueTask DisposeAsync()
{
try
{
if (_closeTask != default)
{
_closeTask = _connection.CloseAsync(errorCode: 0);
await _closeTask;
}
else
{
await _closeTask;
}
}
catch (Exception ex)
{
_log.LogWarning(ex, "Failed to gracefully shutdown connection.");
}
_connection.Dispose();
}
public override void Abort(ConnectionAbortedException abortReason)
{
_closeTask = _connection.CloseAsync(errorCode: _context.Options.AbortErrorCode);
}
public async ValueTask<ConnectionContext> AcceptAsync()
{
var stream = await _connection.AcceptStreamAsync();
return new QuicStreamContext(stream, this, _context);
}
}
}

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.Collections.Generic;
using System.Net;
using System.Net.Quic;
using System.Net.Security;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
{
/// <summary>
/// Listens for new Quic Connections.
/// </summary>
internal class QuicConnectionListener : IConnectionListener, IAsyncDisposable
{
private readonly IQuicTrace _log;
private bool _disposed;
private readonly QuicTransportContext _context;
private readonly QuicListener _listener;
public QuicConnectionListener(QuicTransportOptions options, IQuicTrace log, EndPoint endpoint)
{
_log = log;
_context = new QuicTransportContext(_log, options);
EndPoint = endpoint;
var sslConfig = new SslServerAuthenticationOptions();
sslConfig.ServerCertificate = options.Certificate;
sslConfig.ApplicationProtocols = new List<SslApplicationProtocol>() { new SslApplicationProtocol(options.Alpn) };
_listener = new QuicListener(QuicImplementationProviders.MsQuic, endpoint as IPEndPoint, sslConfig);
_listener.Start();
}
public EndPoint EndPoint { get; set; }
public async ValueTask<ConnectionContext> AcceptAsync(CancellationToken cancellationToken = default)
{
var quicConnection = await _listener.AcceptConnectionAsync(cancellationToken);
return new QuicConnectionContext(quicConnection, _context);
}
public async ValueTask UnbindAsync(CancellationToken cancellationToken = default)
{
await DisposeAsync();
}
public ValueTask DisposeAsync()
{
if (_disposed)
{
return new ValueTask();
}
_disposed = true;
_listener.Dispose();
return new ValueTask();
}
}
}

View File

@ -0,0 +1,316 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Buffers;
using System.IO.Pipelines;
using System.Net.Quic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
{
internal class QuicStreamContext : TransportConnection, IQuicStreamFeature
{
private readonly Task _processingTask;
private readonly QuicStream _stream;
private readonly QuicConnectionContext _connection;
private readonly QuicTransportContext _context;
private readonly CancellationTokenSource _streamClosedTokenSource = new CancellationTokenSource();
private readonly IQuicTrace _log;
private string _connectionId;
private const int MinAllocBufferSize = 4096;
private volatile Exception _shutdownReason;
private readonly TaskCompletionSource<object> _waitForConnectionClosedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
private readonly object _shutdownLock = new object();
public QuicStreamContext(QuicStream stream, QuicConnectionContext connection, QuicTransportContext context)
{
_stream = stream;
_connection = connection;
_context = context;
_log = context.Log;
ConnectionClosed = _streamClosedTokenSource.Token;
var maxReadBufferSize = context.Options.MaxReadBufferSize.Value;
var maxWriteBufferSize = context.Options.MaxWriteBufferSize.Value;
// TODO should we allow these PipeScheduler to be configurable here?
var inputOptions = new PipeOptions(MemoryPool, PipeScheduler.ThreadPool, PipeScheduler.Inline, maxReadBufferSize, maxReadBufferSize / 2, useSynchronizationContext: false);
var outputOptions = new PipeOptions(MemoryPool, PipeScheduler.Inline, PipeScheduler.ThreadPool, maxWriteBufferSize, maxWriteBufferSize / 2, useSynchronizationContext: false);
var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions);
Features.Set<IQuicStreamFeature>(this);
// TODO populate the ITlsConnectionFeature (requires client certs).
Features.Set<ITlsConnectionFeature>(new FakeTlsConnectionFeature());
CanRead = stream.CanRead;
CanWrite = stream.CanWrite;
Transport = pair.Transport;
Application = pair.Application;
_processingTask = StartAsync();
}
public override MemoryPool<byte> MemoryPool { get; }
public PipeWriter Input => Application.Output;
public PipeReader Output => Application.Input;
public bool CanRead { get; }
public bool CanWrite { get; }
public long StreamId
{
get
{
return _stream.StreamId;
}
}
public override string ConnectionId
{
get
{
if (_connectionId == null)
{
_connectionId = $"{_connection.ConnectionId}:{StreamId}";
}
return _connectionId;
}
set
{
_connectionId = value;
}
}
private async Task StartAsync()
{
try
{
// Spawn send and receive logic
var receiveTask = DoReceive();
var sendTask = DoSend();
// Now wait for both to complete
await receiveTask;
await sendTask;
}
catch (Exception ex)
{
_log.LogError(0, ex, $"Unexpected exception in {nameof(QuicStreamContext)}.{nameof(StartAsync)}.");
}
}
private async Task DoReceive()
{
Exception error = null;
try
{
await ProcessReceives();
}
catch (QuicException ex)
{
// This could be ignored if _shutdownReason is already set.
error = new ConnectionResetException(ex.Message, ex);
}
catch (Exception ex)
{
// This is unexpected.
error = ex;
_log.StreamError(ConnectionId, error);
}
finally
{
// If Shutdown() has already bee called, assume that was the reason ProcessReceives() exited.
Input.Complete(_shutdownReason ?? error);
FireStreamClosed();
await _waitForConnectionClosedTcs.Task;
}
}
private async Task ProcessReceives()
{
var input = Input;
while (true)
{
var buffer = Input.GetMemory(MinAllocBufferSize);
var bytesReceived = await _stream.ReadAsync(buffer);
if (bytesReceived == 0)
{
// Read completed.
break;
}
input.Advance(bytesReceived);
var flushTask = input.FlushAsync();
var paused = !flushTask.IsCompleted;
if (paused)
{
_log.StreamPause(ConnectionId);
}
var result = await flushTask;
if (paused)
{
_log.StreamResume(ConnectionId);
}
if (result.IsCompleted || result.IsCanceled)
{
// Pipe consumer is shut down, do we stop writing
break;
}
}
}
private void FireStreamClosed()
{
ThreadPool.UnsafeQueueUserWorkItem(state =>
{
state.CancelConnectionClosedToken();
state._waitForConnectionClosedTcs.TrySetResult(null);
},
this,
preferLocal: false);
}
private void CancelConnectionClosedToken()
{
try
{
_streamClosedTokenSource.Cancel();
}
catch (Exception ex)
{
_log.LogError(0, ex, $"Unexpected exception in {nameof(QuicStreamContext)}.{nameof(CancelConnectionClosedToken)}.");
}
}
private async Task DoSend()
{
Exception shutdownReason = null;
Exception unexpectedError = null;
try
{
await ProcessSends();
}
catch (QuicException ex)
{
shutdownReason = new ConnectionResetException(ex.Message, ex);
}
catch (Exception ex)
{
shutdownReason = ex;
unexpectedError = ex;
_log.ConnectionError(ConnectionId, unexpectedError);
}
finally
{
await ShutdownWrite(shutdownReason);
// Complete the output after disposing the stream
Output.Complete(unexpectedError);
// Cancel any pending flushes so that the input loop is un-paused
Input.CancelPendingFlush();
}
}
private async Task ProcessSends()
{
// Resolve `output` PipeReader via the IDuplexPipe interface prior to loop start for performance.
var output = Output;
while (true)
{
var result = await output.ReadAsync();
if (result.IsCanceled)
{
break;
}
var buffer = result.Buffer;
var end = buffer.End;
var isCompleted = result.IsCompleted;
if (!buffer.IsEmpty)
{
await _stream.WriteAsync(buffer, endStream: isCompleted);
}
output.AdvanceTo(end);
if (isCompleted)
{
// Once the stream pipe is closed, shutdown the stream.
break;
}
}
}
public override void Abort(ConnectionAbortedException abortReason)
{
// Don't call _stream.Shutdown and _stream.Abort at the same time.
lock (_shutdownLock)
{
_stream.AbortRead(_context.Options.AbortErrorCode);
_stream.AbortWrite(_context.Options.AbortErrorCode);
}
// Cancel ProcessSends loop after calling shutdown to ensure the correct _shutdownReason gets set.
Output.CancelPendingRead();
}
private async ValueTask ShutdownWrite(Exception shutdownReason)
{
try
{
lock (_shutdownLock)
{
_shutdownReason = shutdownReason ?? new ConnectionAbortedException("The Quic transport's send loop completed gracefully.");
_log.StreamShutdownWrite(ConnectionId, _shutdownReason);
_stream.Shutdown();
}
await _stream.ShutdownWriteCompleted();
}
catch (Exception ex)
{
_log.LogWarning(ex, "Stream failed to gracefully shutdown.");
// Ignore any errors from Shutdown() since we're tearing down the stream anyway.
}
}
public override async ValueTask DisposeAsync()
{
Transport.Input.Complete();
Transport.Output.Complete();
await _processingTask;
_stream.Dispose();
_streamClosedTokenSource.Dispose();
}
}
}

View File

@ -4,22 +4,28 @@
using System; using System;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
{ {
internal class MsQuicTrace : IMsQuicTrace internal class QuicTrace : IQuicTrace
{ {
private static readonly Action<ILogger, string, Exception> _acceptedConnection = private static readonly Action<ILogger, string, Exception> _acceptedConnection =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(4, nameof(NewConnection)), @"Connection id ""{ConnectionId}"" accepted."); LoggerMessage.Define<string>(LogLevel.Debug, new EventId(4, nameof(NewConnection)), @"Connection id ""{ConnectionId}"" accepted.");
private static readonly Action<ILogger, string, Exception> _acceptedStream = private static readonly Action<ILogger, string, Exception> _acceptedStream =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(5, nameof(NewStream)), @"Stream id ""{ConnectionId}"" accepted."); LoggerMessage.Define<string>(LogLevel.Debug, new EventId(5, nameof(NewStream)), @"Stream id ""{ConnectionId}"" accepted.");
private static readonly Action<ILogger, string, string, Exception> _connectionError = 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}""."); LoggerMessage.Define<string, string>(LogLevel.Debug, new EventId(6, nameof(ConnectionError)), @"Connection id ""{ConnectionId}"" hit an exception: ""{Reason}"".");
private static readonly Action<ILogger, string, string, Exception> _streamError = 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}""."); LoggerMessage.Define<string, string>(LogLevel.Debug, new EventId(7, nameof(StreamError)), @"Connection id ""{ConnectionId}"" hit an exception: ""{Reason}"".");
private static readonly Action<ILogger, string, Exception> _streamPause =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(7, nameof(StreamPause)), @"Stream id ""{ConnectionId}"" paused.");
private static readonly Action<ILogger, string, Exception> _streamResume =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(7, nameof(StreamResume)), @"Stream id ""{ConnectionId}"" resumed.");
private static readonly Action<ILogger, string, string, Exception> _streamShutdownWrite =
LoggerMessage.Define<string, string>(LogLevel.Debug, new EventId(7, nameof(StreamShutdownWrite)), @"Connection id ""{ConnectionId}"" shutting down writes, exception: ""{Reason}"".");
private ILogger _logger; private ILogger _logger;
public MsQuicTrace(ILogger logger) public QuicTrace(ILogger logger)
{ {
_logger = logger; _logger = logger;
} }
@ -49,5 +55,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
{ {
_streamError(_logger, streamId, ex.Message, ex); _streamError(_logger, streamId, ex.Message, ex);
} }
public void StreamPause(string streamId)
{
_streamPause(_logger, streamId, null);
}
public void StreamResume(string streamId)
{
_streamResume(_logger, streamId, null);
}
public void StreamShutdownWrite(string streamId, Exception ex)
{
_streamShutdownWrite(_logger, streamId, ex.Message, ex);
}
} }
} }

View File

@ -0,0 +1,19 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Extensions.Hosting;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal
{
internal class QuicTransportContext
{
public QuicTransportContext(IQuicTrace log, QuicTransportOptions options)
{
Log = log;
Options = options;
}
public IQuicTrace Log { get; }
public QuicTransportOptions Options { get; }
}
}

View File

@ -18,26 +18,12 @@
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.Generated.cs" Link="Internal\TransportConnection.Generated.cs" /> <Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.Generated.cs" Link="Internal\TransportConnection.Generated.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.FeatureCollection.cs" Link="Internal\TransportConnection.FeatureCollection.cs" /> <Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.FeatureCollection.cs" Link="Internal\TransportConnection.FeatureCollection.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Microsoft.AspNetCore.Hosting.Abstractions" /> <Reference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
<Reference Include="Microsoft.AspNetCore.Connections.Abstractions" /> <Reference Include="Microsoft.AspNetCore.Connections.Abstractions" />
<Reference Include="Microsoft.Extensions.Logging.Abstractions" /> <Reference Include="Microsoft.Extensions.Logging.Abstractions" />
<Reference Include="Microsoft.Extensions.Options" /> <Reference Include="Microsoft.Extensions.Options" />
</ItemGroup> </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> </Project>

View File

@ -0,0 +1,50 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Quic;
using System.Net.Security;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic
{
public class QuicConnectionFactory : IConnectionFactory
{
private QuicTransportContext _transportContext;
public QuicConnectionFactory(IOptions<QuicTransportOptions> options, ILoggerFactory loggerFactory)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Client");
var trace = new QuicTrace(logger);
_transportContext = new QuicTransportContext(trace, options.Value);
}
public async ValueTask<ConnectionContext> ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken = default)
{
if (!(endPoint is IPEndPoint ipEndPoint))
{
throw new NotSupportedException($"{endPoint} is not supported");
}
var sslOptions = new SslClientAuthenticationOptions();
sslOptions.ApplicationProtocols = new List<SslApplicationProtocol>() { new SslApplicationProtocol(_transportContext.Options.Alpn) };
var connection = new QuicConnection(QuicImplementationProviders.MsQuic, endPoint as IPEndPoint, sslOptions);
await connection.ConnectAsync(cancellationToken);
return new QuicConnectionContext(connection, _transportContext);
}
}
}

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;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic
{
public class QuicTransportFactory : IMultiplexedConnectionListenerFactory
{
private QuicTrace _log;
private QuicTransportOptions _options;
public QuicTransportFactory(ILoggerFactory loggerFactory, IOptions<QuicTransportOptions> options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic");
_log = new QuicTrace(logger);
_options = options.Value;
}
public ValueTask<IConnectionListener> BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default)
{
var transport = new QuicConnectionListener(_options, _log, endpoint);
return new ValueTask<IConnectionListener>(transport);
}
}
}

View File

@ -5,9 +5,9 @@ using System;
using System.Buffers; using System.Buffers;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Quic
{ {
public class MsQuicTransportOptions public class QuicTransportOptions
{ {
/// <summary> /// <summary>
/// The maximum number of concurrent bi-directional streams per connection. /// The maximum number of concurrent bi-directional streams per connection.
@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic
public string Alpn { get; set; } public string Alpn { get; set; }
/// <summary> /// <summary>
/// The registration name to use in MsQuic. /// The registration name to use in Quic.
/// </summary> /// </summary>
public string RegistrationName { get; set; } public string RegistrationName { get; set; }
@ -49,6 +49,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic
/// </summary> /// </summary>
public long? MaxWriteBufferSize { get; set; } = 64 * 1024; public long? MaxWriteBufferSize { get; set; } = 64 * 1024;
/// <summary>
/// The error code to abort with
/// </summary>
public long AbortErrorCode { get; set; } = 0;
internal Func<MemoryPool<byte>> MemoryPoolFactory { get; set; } = System.Buffers.SlabMemoryPoolFactory.Create; internal Func<MemoryPool<byte>> MemoryPoolFactory { get; set; } = System.Buffers.SlabMemoryPoolFactory.Create;
} }

View File

@ -3,25 +3,24 @@
using System; using System;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic; using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic;
using Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Hosting namespace Microsoft.AspNetCore.Hosting
{ {
public static class WebHostBuilderMsQuicExtensions public static class WebHostBuilderMsQuicExtensions
{ {
public static IWebHostBuilder UseMsQuic(this IWebHostBuilder hostBuilder) public static IWebHostBuilder UseQuic(this IWebHostBuilder hostBuilder)
{ {
return hostBuilder.ConfigureServices(services => return hostBuilder.ConfigureServices(services =>
{ {
services.AddSingleton<IConnectionListenerFactory, MsQuicTransportFactory>(); services.AddSingleton<IConnectionListenerFactory, QuicTransportFactory>();
}); });
} }
public static IWebHostBuilder UseMsQuic(this IWebHostBuilder hostBuilder, Action<MsQuicTransportOptions> configureOptions) public static IWebHostBuilder UseQuic(this IWebHostBuilder hostBuilder, Action<QuicTransportOptions> configureOptions)
{ {
return hostBuilder.UseMsQuic().ConfigureServices(services => return hostBuilder.UseQuic().ConfigureServices(services =>
{ {
services.Configure(configureOptions); services.Configure(configureOptions);
}); });

View File

@ -11,6 +11,13 @@ namespace Microsoft.AspNetCore.Hosting
} }
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
{ {
public partial class SocketConnectionFactory : Microsoft.AspNetCore.Connections.IConnectionFactory, System.IAsyncDisposable
{
public SocketConnectionFactory(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions> options, 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 System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
}
public sealed partial class SocketTransportFactory : Microsoft.AspNetCore.Connections.IConnectionListenerFactory public sealed partial class SocketTransportFactory : Microsoft.AspNetCore.Connections.IConnectionListenerFactory
{ {
public SocketTransportFactory(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions> options, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public SocketTransportFactory(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions> options, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { }
@ -26,13 +33,3 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
public bool NoDelay { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } public bool NoDelay { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
} }
} }
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Client
{
public partial class SocketConnectionFactory : Microsoft.AspNetCore.Connections.IConnectionFactory, System.IAsyncDisposable
{
public SocketConnectionFactory(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions> options, 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 System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
}
}

View File

@ -13,7 +13,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Client namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
{ {
public class SocketConnectionFactory : IConnectionFactory, IAsyncDisposable public class SocketConnectionFactory : IConnectionFactory, IAsyncDisposable
{ {

View File

@ -9,6 +9,6 @@
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" /> <Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
<Reference Include="Microsoft.Extensions.Logging.Console" /> <Reference Include="Microsoft.Extensions.Logging.Console" />
<Reference Include="Microsoft.Extensions.Hosting" /> <Reference Include="Microsoft.Extensions.Hosting" />
<Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic" /> <Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Quic" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -26,7 +26,7 @@ namespace Http3SampleApp
{ {
webHost.UseKestrel() webHost.UseKestrel()
// Things like APLN and cert should be able to be passed from corefx into bedrock // Things like APLN and cert should be able to be passed from corefx into bedrock
.UseMsQuic(options => .UseQuic(options =>
{ {
options.Certificate = cert; options.Certificate = cert;
options.RegistrationName = "Quic"; options.RegistrationName = "Quic";

View File

@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Diagnostics;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
namespace QuicSampleApp namespace QuicSampleApp
@ -26,7 +25,7 @@ namespace QuicSampleApp
public static void Main(string[] args) public static void Main(string[] args)
{ {
var cert = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, true); //var cert = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, true);
var hostBuilder = new WebHostBuilder() var hostBuilder = new WebHostBuilder()
.ConfigureLogging((_, factory) => .ConfigureLogging((_, factory) =>
{ {
@ -34,9 +33,9 @@ namespace QuicSampleApp
factory.AddConsole(); factory.AddConsole();
}) })
.UseKestrel() .UseKestrel()
.UseMsQuic(options => .UseQuic(options =>
{ {
options.Certificate = cert; options.Certificate = null;
options.RegistrationName = "AspNetCore-MsQuic"; options.RegistrationName = "AspNetCore-MsQuic";
options.Alpn = "QuicTest"; options.Alpn = "QuicTest";
options.IdleTimeout = TimeSpan.FromHours(1); options.IdleTimeout = TimeSpan.FromHours(1);

View File

@ -1,16 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile> <NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
<GenerateRazorAssemblyInfo>false</GenerateRazorAssemblyInfo> <GenerateRazorAssemblyInfo>false</GenerateRazorAssemblyInfo>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" /> <Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
<Reference Include="Microsoft.Extensions.Logging.Console" /> <Reference Include="Microsoft.Extensions.Logging.Console" />
<Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic" /> <Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Quic" />
</ItemGroup>
<ItemGroup Condition="'$(OS)' == 'Windows_NT'">
<PackageReference Include="MsQuicPackage.win-x64" Version="1.0.9">
<AllowExplicitReference>true</AllowExplicitReference>
</PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(OS)' == 'Unix'">
<PackageReference Include="MsQuicPackage.linux-x64" Version="1.0.9">
<AllowExplicitReference>true</AllowExplicitReference>
</PackageReference>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -5,7 +5,7 @@ using System.Security.Cryptography.X509Certificates;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic; using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic;
using Microsoft.AspNetCore.Connections.Abstractions.Features; using Microsoft.AspNetCore.Connections.Abstractions.Features;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
@ -26,26 +26,26 @@ namespace QuicSampleClient
}) })
.ConfigureServices(services => .ConfigureServices(services =>
{ {
services.AddSingleton<IConnectionFactory, MsQuicConnectionFactory>(); services.AddSingleton<IConnectionFactory, QuicConnectionFactory>();
services.AddSingleton<MsQuicClientService>(); services.AddSingleton<QuicClientService>();
services.AddOptions<MsQuicTransportOptions>(); services.AddOptions<QuicTransportOptions>();
services.Configure<MsQuicTransportOptions>((options) => services.Configure<QuicTransportOptions>((options) =>
{ {
options.Alpn = "QuicTest"; options.Alpn = "QuicTest";
options.RegistrationName = "Quic-AspNetCore-client"; options.RegistrationName = "Quic-AspNetCore-client";
options.Certificate = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, true); options.Certificate = null;
options.IdleTimeout = TimeSpan.FromHours(1); options.IdleTimeout = TimeSpan.FromHours(1);
}); });
}) })
.Build(); .Build();
await host.Services.GetService<MsQuicClientService>().RunAsync(); await host.Services.GetService<QuicClientService>().RunAsync();
} }
private class MsQuicClientService private class QuicClientService
{ {
private readonly IConnectionFactory _connectionFactory; private readonly IConnectionFactory _connectionFactory;
private readonly ILogger<MsQuicClientService> _logger; private readonly ILogger<QuicClientService> _logger;
public MsQuicClientService(IConnectionFactory connectionFactory, ILogger<MsQuicClientService> logger) public QuicClientService(IConnectionFactory connectionFactory, ILogger<QuicClientService> logger)
{ {
_connectionFactory = connectionFactory; _connectionFactory = connectionFactory;
_logger = logger; _logger = logger;
@ -53,7 +53,6 @@ namespace QuicSampleClient
public async Task RunAsync() public async Task RunAsync()
{ {
var start = Console.ReadLine();
Console.WriteLine("Starting"); Console.WriteLine("Starting");
var connectionContext = await _connectionFactory.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 5555)); var connectionContext = await _connectionFactory.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 5555));
var createStreamFeature = connectionContext.Features.Get<IQuicCreateStreamFeature>(); var createStreamFeature = connectionContext.Features.Get<IQuicCreateStreamFeature>();
@ -82,10 +81,11 @@ namespace QuicSampleClient
} }
var readResult = await streamContext.Transport.Input.ReadAsync(); var readResult = await streamContext.Transport.Input.ReadAsync();
if (readResult.IsCanceled) if (readResult.IsCanceled || readResult.IsCompleted)
{ {
break; break;
} }
if (readResult.Buffer.Length > 0) if (readResult.Buffer.Length > 0)
{ {
Console.WriteLine(Encoding.ASCII.GetString(readResult.Buffer.ToArray())); Console.WriteLine(Encoding.ASCII.GetString(readResult.Buffer.ToArray()));

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
@ -7,9 +7,22 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic" /> <Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Quic" />
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" /> <Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
<Reference Include="Microsoft.Extensions.Hosting" /> <Reference Include="Microsoft.Extensions.Hosting" />
<Reference Include="Microsoft.Extensions.Logging.Console" /> <Reference Include="Microsoft.Extensions.Logging.Console" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(OS)' == 'Windows_NT'">
<PackageReference Include="MsQuicPackage.win-x64" Version="1.0.9">
<AllowExplicitReference>true</AllowExplicitReference>
</PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(OS)' == 'Unix'">
<PackageReference Include="MsQuicPackage.linux-x64" Version="1.0.9">
<AllowExplicitReference>true</AllowExplicitReference>
</PackageReference>
</ItemGroup>
</Project> </Project>

View File

@ -219,7 +219,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{ {
internal ConnectionContext ConnectionContext { get; } internal ConnectionContext ConnectionContext { get; }
public bool IsUnidirectional => false; public bool CanRead => true;
public bool CanWrite => true;
public long StreamId => 0; public long StreamId => 0;
@ -347,7 +348,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{ {
internal ConnectionContext ConnectionContext { get; } internal ConnectionContext ConnectionContext { get; }
public bool IsUnidirectional => true; public bool CanRead => true;
public bool CanWrite => false;
// TODO // TODO
public long StreamId => 0; public long StreamId => 0;

View File

@ -4,7 +4,7 @@
using System; using System;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http2Cat; using Microsoft.AspNetCore.Http2Cat;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Client; using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets;
namespace Microsoft.Extensions.DependencyInjection namespace Microsoft.Extensions.DependencyInjection
{ {