Replat on System.Net.Quic (#18689)
This commit is contained in:
parent
962db1fecc
commit
1daebd1722
|
|
@ -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" 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.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.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" />
|
||||
|
|
|
|||
|
|
@ -198,7 +198,8 @@ namespace Microsoft.AspNetCore.Connections.Features
|
|||
}
|
||||
public partial interface IQuicStreamFeature
|
||||
{
|
||||
bool IsUnidirectional { get; }
|
||||
bool CanRead { get; }
|
||||
bool CanWrite { get; }
|
||||
long StreamId { get; }
|
||||
}
|
||||
public partial interface IQuicStreamListenerFeature
|
||||
|
|
|
|||
|
|
@ -198,7 +198,8 @@ namespace Microsoft.AspNetCore.Connections.Features
|
|||
}
|
||||
public partial interface IQuicStreamFeature
|
||||
{
|
||||
bool IsUnidirectional { get; }
|
||||
bool CanRead { get; }
|
||||
bool CanWrite { get; }
|
||||
long StreamId { get; }
|
||||
}
|
||||
public partial interface IQuicStreamListenerFeature
|
||||
|
|
|
|||
|
|
@ -198,7 +198,8 @@ namespace Microsoft.AspNetCore.Connections.Features
|
|||
}
|
||||
public partial interface IQuicStreamFeature
|
||||
{
|
||||
bool IsUnidirectional { get; }
|
||||
bool CanRead { get; }
|
||||
bool CanWrite { get; }
|
||||
long StreamId { get; }
|
||||
}
|
||||
public partial interface IQuicStreamListenerFeature
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ namespace Microsoft.AspNetCore.Connections.Features
|
|||
{
|
||||
public interface IQuicStreamFeature
|
||||
{
|
||||
bool IsUnidirectional { get; }
|
||||
bool CanRead { get; }
|
||||
bool CanWrite { get; }
|
||||
long StreamId { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
var streamId = streamFeature.StreamId;
|
||||
HighestStreamId = streamId;
|
||||
|
||||
if (streamFeature.IsUnidirectional)
|
||||
if (!streamFeature.CanWrite)
|
||||
{
|
||||
var stream = new Http3ControlStream<TContext>(application, this, httpConnectionContext);
|
||||
ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false);
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +141,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
}
|
||||
catch (Http3StreamErrorException)
|
||||
{
|
||||
// TODO
|
||||
// TODO
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -151,7 +151,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
|
|||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO
|
||||
// TODO
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "http2cat", "samples\http2ca
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuicSampleApp", "samples\QuicSampleApp\QuicSampleApp.csproj", "{53A8634C-DFC5-4A5B-8864-9EF1707E3F18}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic", "Transport.MsQuic\src\Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.csproj", "{62CFF861-807E-43F6-9403-22AA7F06C9A6}"
|
||||
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
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuicSampleClient", "samples\QuicSampleClient\QuicSampleClient.csproj", "{F39A942B-85A8-4C1B-A5BC-435555E79F20}"
|
||||
EndProject
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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.
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
<TargetFrameworks>$(DefaultNetCoreTargetFramework)</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<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.Connections.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
|
|
@ -5,27 +5,27 @@ namespace Microsoft.AspNetCore.Hosting
|
|||
{
|
||||
public static partial class WebHostBuilderMsQuicExtensions
|
||||
{
|
||||
public static Microsoft.AspNetCore.Hosting.IWebHostBuilder UseMsQuic(this Microsoft.AspNetCore.Hosting.IWebHostBuilder hostBuilder) { throw null; }
|
||||
public static Microsoft.AspNetCore.Hosting.IWebHostBuilder UseMsQuic(this Microsoft.AspNetCore.Hosting.IWebHostBuilder hostBuilder, System.Action<Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.MsQuicTransportOptions> configureOptions) { throw null; }
|
||||
public static Microsoft.AspNetCore.Hosting.IWebHostBuilder UseQuic(this Microsoft.AspNetCore.Hosting.IWebHostBuilder hostBuilder) { 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]
|
||||
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) { }
|
||||
[System.Diagnostics.DebuggerStepThroughAttribute]
|
||||
public QuicTransportFactory(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.QuicTransportOptions> options) { }
|
||||
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 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 { } }
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,13 +4,16 @@
|
|||
using System;
|
||||
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 NewStream(string streamId);
|
||||
void ConnectionError(string connectionId, Exception ex);
|
||||
void StreamError(string streamId, Exception ex);
|
||||
void StreamPause(string streamId);
|
||||
void StreamResume(string streamId);
|
||||
void StreamShutdownWrite(string streamId, Exception ex);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,22 +4,28 @@
|
|||
using System;
|
||||
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 =
|
||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(4, nameof(NewConnection)), @"Connection id ""{ConnectionId}"" accepted.");
|
||||
private static readonly Action<ILogger, string, Exception> _acceptedStream =
|
||||
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(5, nameof(NewStream)), @"Stream id ""{ConnectionId}"" accepted.");
|
||||
private static readonly Action<ILogger, string, string, Exception> _connectionError =
|
||||
LoggerMessage.Define<string, string>(LogLevel.Debug, new EventId(6, nameof(NewStream)), @"Connection id ""{ConnectionId}"" hit an exception: ""{Reason}"".");
|
||||
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 =
|
||||
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;
|
||||
|
||||
public MsQuicTrace(ILogger logger)
|
||||
public QuicTrace(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
|
@ -49,5 +55,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal
|
|||
{
|
||||
_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -18,26 +18,12 @@
|
|||
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.Generated.cs" Link="Internal\TransportConnection.Generated.cs" />
|
||||
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.FeatureCollection.cs" Link="Internal\TransportConnection.FeatureCollection.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
|
||||
<Reference Include="Microsoft.AspNetCore.Connections.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.Options" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="msquic.dll" Condition="Exists('msquic.dll')">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="libmsquic.so" Condition="Exists('libmsquic.so')">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="msquic.pdb" Condition="Exists('msquic.pdb')">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,9 +5,9 @@ using System;
|
|||
using System.Buffers;
|
||||
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>
|
||||
/// 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; }
|
||||
|
||||
/// <summary>
|
||||
/// The registration name to use in MsQuic.
|
||||
/// The registration name to use in Quic.
|
||||
/// </summary>
|
||||
public string RegistrationName { get; set; }
|
||||
|
||||
|
|
@ -49,6 +49,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic
|
|||
/// </summary>
|
||||
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;
|
||||
|
||||
}
|
||||
|
|
@ -3,25 +3,24 @@
|
|||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Hosting
|
||||
{
|
||||
public static class WebHostBuilderMsQuicExtensions
|
||||
{
|
||||
public static IWebHostBuilder UseMsQuic(this IWebHostBuilder hostBuilder)
|
||||
public static IWebHostBuilder UseQuic(this IWebHostBuilder hostBuilder)
|
||||
{
|
||||
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);
|
||||
});
|
||||
|
|
@ -11,6 +11,13 @@ namespace Microsoft.AspNetCore.Hosting
|
|||
}
|
||||
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 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 { } }
|
||||
}
|
||||
}
|
||||
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; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Client
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
|
||||
{
|
||||
public class SocketConnectionFactory : IConnectionFactory, IAsyncDisposable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@
|
|||
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
|
||||
<Reference Include="Microsoft.Extensions.Logging.Console" />
|
||||
<Reference Include="Microsoft.Extensions.Hosting" />
|
||||
<Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic" />
|
||||
<Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Quic" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ namespace Http3SampleApp
|
|||
{
|
||||
webHost.UseKestrel()
|
||||
// Things like APLN and cert should be able to be passed from corefx into bedrock
|
||||
.UseMsQuic(options =>
|
||||
.UseQuic(options =>
|
||||
{
|
||||
options.Certificate = cert;
|
||||
options.RegistrationName = "Quic";
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Connections.Features;
|
|||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
|
||||
namespace QuicSampleApp
|
||||
|
|
@ -26,7 +25,7 @@ namespace QuicSampleApp
|
|||
|
||||
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()
|
||||
.ConfigureLogging((_, factory) =>
|
||||
{
|
||||
|
|
@ -34,9 +33,9 @@ namespace QuicSampleApp
|
|||
factory.AddConsole();
|
||||
})
|
||||
.UseKestrel()
|
||||
.UseMsQuic(options =>
|
||||
.UseQuic(options =>
|
||||
{
|
||||
options.Certificate = cert;
|
||||
options.Certificate = null;
|
||||
options.RegistrationName = "AspNetCore-MsQuic";
|
||||
options.Alpn = "QuicTest";
|
||||
options.IdleTimeout = TimeSpan.FromHours(1);
|
||||
|
|
|
|||
|
|
@ -1,16 +1,28 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
|
||||
<GenerateRazorAssemblyInfo>false</GenerateRazorAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
|
||||
<Reference Include="Microsoft.Extensions.Logging.Console" />
|
||||
<Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic" />
|
||||
<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>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using System.Security.Cryptography.X509Certificates;
|
|||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.MsQuic;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic;
|
||||
using Microsoft.AspNetCore.Connections.Abstractions.Features;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
|
|
@ -26,26 +26,26 @@ namespace QuicSampleClient
|
|||
})
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton<IConnectionFactory, MsQuicConnectionFactory>();
|
||||
services.AddSingleton<MsQuicClientService>();
|
||||
services.AddOptions<MsQuicTransportOptions>();
|
||||
services.Configure<MsQuicTransportOptions>((options) =>
|
||||
services.AddSingleton<IConnectionFactory, QuicConnectionFactory>();
|
||||
services.AddSingleton<QuicClientService>();
|
||||
services.AddOptions<QuicTransportOptions>();
|
||||
services.Configure<QuicTransportOptions>((options) =>
|
||||
{
|
||||
options.Alpn = "QuicTest";
|
||||
options.RegistrationName = "Quic-AspNetCore-client";
|
||||
options.Certificate = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, true);
|
||||
options.Certificate = null;
|
||||
options.IdleTimeout = TimeSpan.FromHours(1);
|
||||
});
|
||||
})
|
||||
.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 ILogger<MsQuicClientService> _logger;
|
||||
public MsQuicClientService(IConnectionFactory connectionFactory, ILogger<MsQuicClientService> logger)
|
||||
private readonly ILogger<QuicClientService> _logger;
|
||||
public QuicClientService(IConnectionFactory connectionFactory, ILogger<QuicClientService> logger)
|
||||
{
|
||||
_connectionFactory = connectionFactory;
|
||||
_logger = logger;
|
||||
|
|
@ -53,7 +53,6 @@ namespace QuicSampleClient
|
|||
|
||||
public async Task RunAsync()
|
||||
{
|
||||
var start = Console.ReadLine();
|
||||
Console.WriteLine("Starting");
|
||||
var connectionContext = await _connectionFactory.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 5555));
|
||||
var createStreamFeature = connectionContext.Features.Get<IQuicCreateStreamFeature>();
|
||||
|
|
@ -82,10 +81,11 @@ namespace QuicSampleClient
|
|||
}
|
||||
|
||||
var readResult = await streamContext.Transport.Input.ReadAsync();
|
||||
if (readResult.IsCanceled)
|
||||
if (readResult.IsCanceled || readResult.IsCompleted)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (readResult.Buffer.Length > 0)
|
||||
{
|
||||
Console.WriteLine(Encoding.ASCII.GetString(readResult.Buffer.ToArray()));
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
|
|
@ -7,9 +7,22 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<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.Extensions.Hosting" />
|
||||
<Reference Include="Microsoft.Extensions.Logging.Console" />
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -219,7 +219,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
internal ConnectionContext ConnectionContext { get; }
|
||||
|
||||
public bool IsUnidirectional => false;
|
||||
public bool CanRead => true;
|
||||
public bool CanWrite => true;
|
||||
|
||||
public long StreamId => 0;
|
||||
|
||||
|
|
@ -347,7 +348,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
internal ConnectionContext ConnectionContext { get; }
|
||||
|
||||
public bool IsUnidirectional => true;
|
||||
public bool CanRead => true;
|
||||
public bool CanWrite => false;
|
||||
|
||||
// TODO
|
||||
public long StreamId => 0;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
using System;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Http2Cat;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Client;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue