From 2ea674021f58e9f02b3b5e0b4cefbf29d99b6a7b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2020 10:19:44 -0700 Subject: [PATCH] Sync shared code from runtime (#25435) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../Implementations/Mock/MockConnection.cs | 6 +- .../MsQuic/Internal/MsQuicApi.cs | 84 +++++++++++++++---- .../MsQuic/Internal/MsQuicSession.cs | 8 +- .../MsQuic/Internal/QuicExceptionHelpers.cs | 7 +- .../MsQuic/MsQuicConnection.cs | 31 +++++-- .../Implementations/MsQuic/MsQuicStream.cs | 10 +++ .../Implementations/QuicConnectionProvider.cs | 2 +- .../runtime/Quic/Interop/MsQuicStatusCodes.cs | 39 ++++++--- .../Quic/QuicClientConnectionOptions.cs | 2 +- src/Shared/runtime/Quic/QuicConnection.cs | 6 +- src/Shared/runtime/Quic/QuicException.cs | 9 +- 11 files changed, 156 insertions(+), 48 deletions(-) diff --git a/src/Shared/runtime/Quic/Implementations/Mock/MockConnection.cs b/src/Shared/runtime/Quic/Implementations/Mock/MockConnection.cs index b97f4876bf..07995dd661 100644 --- a/src/Shared/runtime/Quic/Implementations/Mock/MockConnection.cs +++ b/src/Shared/runtime/Quic/Implementations/Mock/MockConnection.cs @@ -14,7 +14,7 @@ namespace System.Net.Quic.Implementations.Mock { private readonly bool _isClient; private bool _disposed; - private IPEndPoint? _remoteEndPoint; + private EndPoint? _remoteEndPoint; private IPEndPoint? _localEndPoint; private object _syncObject = new object(); private Socket? _socket; @@ -24,7 +24,7 @@ namespace System.Net.Quic.Implementations.Mock private long _nextOutboundUnidirectionalStream; // Constructor for outbound connections - internal MockConnection(IPEndPoint? remoteEndPoint, SslClientAuthenticationOptions? sslClientAuthenticationOptions, IPEndPoint? localEndPoint = null) + internal MockConnection(EndPoint? remoteEndPoint, SslClientAuthenticationOptions? sslClientAuthenticationOptions, IPEndPoint? localEndPoint = null) { _remoteEndPoint = remoteEndPoint; _localEndPoint = localEndPoint; @@ -59,7 +59,7 @@ namespace System.Net.Quic.Implementations.Mock internal override IPEndPoint LocalEndPoint => new IPEndPoint(_localEndPoint!.Address, _localEndPoint.Port); - internal override IPEndPoint RemoteEndPoint => new IPEndPoint(_remoteEndPoint!.Address, _remoteEndPoint.Port); + internal override EndPoint RemoteEndPoint => _remoteEndPoint!; internal override SslApplicationProtocol NegotiatedApplicationProtocol => throw new NotImplementedException(); diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs index 5c62f1dbc6..d90b7beede 100644 --- a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs +++ b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. #nullable enable +using System.Buffers; +using System.Collections.Generic; using System.IO; using System.Net.Security; using System.Runtime.InteropServices; @@ -318,26 +320,72 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal return secConfig; } - public unsafe IntPtr SessionOpen(byte[] alpn) + public unsafe IntPtr SessionOpen(List alpnProtocols) + { + if (alpnProtocols.Count == 1) + { + return SessionOpen(alpnProtocols[0]); + } + + var memoryHandles = ArrayPool.Shared.Rent(alpnProtocols.Count); + var quicBuffers = ArrayPool.Shared.Rent(alpnProtocols.Count); + + try + { + for (int i = 0; i < alpnProtocols.Count; ++i) + { + ReadOnlyMemory alpnProtocol = alpnProtocols[i].Protocol; + MemoryHandle h = alpnProtocol.Pin(); + + memoryHandles[i] = h; + quicBuffers[i].Buffer = (byte*)h.Pointer; + quicBuffers[i].Length = (uint)alpnProtocol.Length; + } + + IntPtr session; + + fixed (MsQuicNativeMethods.QuicBuffer* pQuicBuffers = quicBuffers) + { + session = SessionOpen(pQuicBuffers, (uint)alpnProtocols.Count); + } + + ArrayPool.Shared.Return(quicBuffers); + ArrayPool.Shared.Return(memoryHandles); + + return session; + } + finally + { + foreach (MemoryHandle handle in memoryHandles) + { + handle.Dispose(); + } + } + } + + private unsafe IntPtr SessionOpen(SslApplicationProtocol alpnProtocol) + { + ReadOnlyMemory memory = alpnProtocol.Protocol; + using MemoryHandle h = memory.Pin(); + + var quicBuffer = new MsQuicNativeMethods.QuicBuffer() + { + Buffer = (byte*)h.Pointer, + Length = (uint)memory.Length + }; + + return SessionOpen(&quicBuffer, 1); + } + + private unsafe IntPtr SessionOpen(MsQuicNativeMethods.QuicBuffer *alpnBuffers, uint bufferCount) { IntPtr sessionPtr = IntPtr.Zero; - uint status; - - fixed (byte* pAlpn = alpn) - { - var alpnBuffer = new MsQuicNativeMethods.QuicBuffer - { - Length = (uint)alpn.Length, - Buffer = pAlpn - }; - - status = SessionOpenDelegate( - _registrationContext, - &alpnBuffer, - 1, - IntPtr.Zero, - ref sessionPtr); - } + uint status = SessionOpenDelegate( + _registrationContext, + alpnBuffers, + bufferCount, + IntPtr.Zero, + ref sessionPtr); QuicExceptionHelpers.ThrowIfFailed(status, "Could not open session."); diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs index 962e8046fc..8b0e28e290 100644 --- a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs +++ b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicSession.cs @@ -1,5 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; +using System.Net.Security; namespace System.Net.Quic.Implementations.MsQuic.Internal { @@ -21,7 +23,7 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal { if (!_opened) { - OpenSession(options.ClientAuthenticationOptions!.ApplicationProtocols![0].Protocol.ToArray(), + OpenSession(options.ClientAuthenticationOptions!.ApplicationProtocols!, (ushort)options.MaxBidirectionalStreams, (ushort)options.MaxUnidirectionalStreams); } @@ -36,7 +38,7 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal return connectionPtr; } - private void OpenSession(byte[] alpn, ushort bidirectionalStreamCount, ushort undirectionalStreamCount) + private void OpenSession(List alpn, ushort bidirectionalStreamCount, ushort undirectionalStreamCount) { _opened = true; _nativeObjPtr = MsQuicApi.Api.SessionOpen(alpn); @@ -49,7 +51,7 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal { if (!_opened) { - OpenSession(options.ServerAuthenticationOptions!.ApplicationProtocols![0].Protocol.ToArray(), + OpenSession(options.ServerAuthenticationOptions!.ApplicationProtocols!, (ushort)options.MaxBidirectionalStreams, (ushort)options.MaxUnidirectionalStreams); } diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/QuicExceptionHelpers.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/QuicExceptionHelpers.cs index a68cbbe939..673e766822 100644 --- a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/QuicExceptionHelpers.cs +++ b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/QuicExceptionHelpers.cs @@ -10,8 +10,13 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal { if (!MsQuicStatusHelper.SuccessfulStatusCode(status)) { - throw new QuicException($"{message} Error Code: {MsQuicStatusCodes.GetError(status)}"); + throw CreateExceptionForHResult(status, message, innerException); } } + + internal static Exception CreateExceptionForHResult(uint status, string? message = null, Exception? innerException = null) + { + return new QuicException($"{message} Error Code: {MsQuicStatusCodes.GetError(status)}", innerException); + } } } diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicConnection.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicConnection.cs index 632c755294..fb9a28691d 100644 --- a/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicConnection.cs +++ b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicConnection.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.IO; using System.Net.Quic.Implementations.MsQuic.Internal; using System.Net.Security; +using System.Net.Sockets; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; @@ -32,7 +33,7 @@ namespace System.Net.Quic.Implementations.MsQuic // Endpoint to either connect to or the endpoint already accepted. private IPEndPoint? _localEndPoint; - private readonly IPEndPoint _remoteEndPoint; + private readonly EndPoint _remoteEndPoint; private SslApplicationProtocol _negotiatedAlpnProtocol; @@ -92,7 +93,7 @@ namespace System.Net.Quic.Implementations.MsQuic MsQuicParameterHelpers.SetSecurityConfig(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.SEC_CONFIG, _securityConfig!.NativeObjPtr); } - internal override IPEndPoint RemoteEndPoint => new IPEndPoint(_remoteEndPoint.Address, _remoteEndPoint.Port); + internal override EndPoint RemoteEndPoint => _remoteEndPoint; internal override SslApplicationProtocol NegotiatedApplicationProtocol => _negotiatedAlpnProtocol; @@ -155,7 +156,9 @@ namespace System.Net.Quic.Implementations.MsQuic { if (!_connected) { - _connectTcs.SetException(ExceptionDispatchInfo.SetCurrentStackTrace(new IOException("Connection has been shutdown."))); + uint hresult = connectionEvent.Data.ShutdownInitiatedByTransport.Status; + Exception ex = QuicExceptionHelpers.CreateExceptionForHResult(hresult, "Connection has been shutdown by transport."); + _connectTcs.SetException(ExceptionDispatchInfo.SetCurrentStackTrace(ex)); } _acceptQueue.Writer.Complete(); @@ -243,12 +246,28 @@ namespace System.Net.Quic.Implementations.MsQuic { ThrowIfDisposed(); + (string address, int port) = _remoteEndPoint switch + { + DnsEndPoint dnsEp => (dnsEp.Host, dnsEp.Port), + IPEndPoint ipEp => (ipEp.Address.ToString(), ipEp.Port), + _ => throw new Exception($"Unsupported remote endpoint type '{_remoteEndPoint.GetType()}'.") + }; + + // values taken from https://github.com/microsoft/msquic/blob/main/docs/api/ConnectionStart.md + int af = _remoteEndPoint.AddressFamily switch + { + AddressFamily.Unspecified => 0, + AddressFamily.InterNetwork => 2, + AddressFamily.InterNetworkV6 => 23, + _ => throw new Exception($"Unsupported address family of '{_remoteEndPoint.AddressFamily}' for remote endpoint.") + }; + QuicExceptionHelpers.ThrowIfFailed( MsQuicApi.Api.ConnectionStartDelegate( _ptr, - (ushort)_remoteEndPoint.AddressFamily, - _remoteEndPoint.Address.ToString(), - (ushort)_remoteEndPoint.Port), + (ushort)af, + address, + (ushort)port), "Failed to connect to peer."); return new ValueTask(_connectTcs.Task); diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs index 48a135799b..3b01df503b 100644 --- a/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs +++ b/src/Shared/runtime/Quic/Implementations/MsQuic/MsQuicStream.cs @@ -225,6 +225,11 @@ namespace System.Net.Quic.Implementations.MsQuic throw new InvalidOperationException("Reading is not allowed on stream."); } + if (NetEventSource.IsEnabled) + { + NetEventSource.Info(this, $"[{GetHashCode()}] reading into Memory of '{destination.Length}' bytes."); + } + lock (_sync) { if (_readState == ReadState.ReadsCompleted) @@ -474,6 +479,11 @@ namespace System.Net.Quic.Implementations.MsQuic private uint HandleEvent(ref StreamEvent evt) { + if (NetEventSource.IsEnabled) + { + NetEventSource.Info(this, $"[{GetHashCode()}] handling event '{evt.Type}'."); + } + uint status = MsQuicStatusCodes.Success; try diff --git a/src/Shared/runtime/Quic/Implementations/QuicConnectionProvider.cs b/src/Shared/runtime/Quic/Implementations/QuicConnectionProvider.cs index da511640f2..7febd47be1 100644 --- a/src/Shared/runtime/Quic/Implementations/QuicConnectionProvider.cs +++ b/src/Shared/runtime/Quic/Implementations/QuicConnectionProvider.cs @@ -12,7 +12,7 @@ namespace System.Net.Quic.Implementations internal abstract IPEndPoint LocalEndPoint { get; } - internal abstract IPEndPoint RemoteEndPoint { get; } + internal abstract EndPoint RemoteEndPoint { get; } internal abstract ValueTask ConnectAsync(CancellationToken cancellationToken = default); diff --git a/src/Shared/runtime/Quic/Interop/MsQuicStatusCodes.cs b/src/Shared/runtime/Quic/Interop/MsQuicStatusCodes.cs index af7fd30300..c48fac85f9 100644 --- a/src/Shared/runtime/Quic/Interop/MsQuicStatusCodes.cs +++ b/src/Shared/runtime/Quic/Interop/MsQuicStatusCodes.cs @@ -26,13 +26,16 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal 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 ConnectionTimeout = 0x80410006; + internal const uint ConnectionIdle = 0x80410005; internal const uint HostUnreachable = 0x800704D0; + internal const uint InternalError = 0x80410003; + internal const uint ConnectionRefused = 0x800704C9; + internal const uint ProtocolError = 0x80410004; internal const uint VerNegError = 0x80410001; + internal const uint TlsError = 0x80072B18; + internal const uint UserCanceled = 0x80410002; + internal const uint AlpnNegotiationFailure = 0x80410007; // TODO return better error messages here. public static string GetError(uint status) @@ -53,11 +56,15 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal AddressInUse => "ADDRESS_IN_USE", ConnectionTimeout => "CONNECTION_TIMEOUT", ConnectionIdle => "CONNECTION_IDLE", + HostUnreachable => "UNREACHABLE", InternalError => "INTERNAL_ERROR", - ServerBusy => "SERVER_BUSY", + ConnectionRefused => "CONNECTION_REFUSED", ProtocolError => "PROTOCOL_ERROR", VerNegError => "VER_NEG_ERROR", - _ => status.ToString() + TlsError => "TLS_ERROR", + UserCanceled => "USER_CANCELED", + AlpnNegotiationFailure => "ALPN_NEG_FAILURE", + _ => $"0x{status:X8}" }; } } @@ -79,9 +86,15 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal internal const uint ConnectionTimeout = 110; internal const uint ConnectionIdle = 200000011; internal const uint InternalError = 200000012; - internal const uint ServerBusy = 200000007; + internal const uint ConnectionRefused = 200000007; internal const uint ProtocolError = 200000013; internal const uint VerNegError = 200000014; + internal const uint EpollError = 200000015; + internal const uint DnsResolutionError = 200000016; + internal const uint SocketError = 200000017; + internal const uint TlsError = 200000018; + internal const uint UserCanceled = 200000019; + internal const uint AlpnNegotiationFailure = 200000020; // TODO return better error messages here. public static string GetError(uint status) @@ -103,10 +116,16 @@ namespace System.Net.Quic.Implementations.MsQuic.Internal ConnectionTimeout => "CONNECTION_TIMEOUT", ConnectionIdle => "CONNECTION_IDLE", InternalError => "INTERNAL_ERROR", - ServerBusy => "SERVER_BUSY", + ConnectionRefused => "CONNECTION_REFUSED", ProtocolError => "PROTOCOL_ERROR", VerNegError => "VER_NEG_ERROR", - _ => status.ToString() + EpollError => "EPOLL_ERROR", + DnsResolutionError => "DNS_RESOLUTION_ERROR", + SocketError => "SOCKET_ERROR", + TlsError => "TLS_ERROR", + UserCanceled => "USER_CANCELED", + AlpnNegotiationFailure => "ALPN_NEG_FAILURE", + _ => $"0x{status:X8}" }; } } diff --git a/src/Shared/runtime/Quic/QuicClientConnectionOptions.cs b/src/Shared/runtime/Quic/QuicClientConnectionOptions.cs index c4ad3447cb..9852644101 100644 --- a/src/Shared/runtime/Quic/QuicClientConnectionOptions.cs +++ b/src/Shared/runtime/Quic/QuicClientConnectionOptions.cs @@ -24,7 +24,7 @@ namespace System.Net.Quic /// /// The endpoint to connect to. /// - public IPEndPoint? RemoteEndPoint { get; set; } + public EndPoint? RemoteEndPoint { get; set; } /// /// Limit on the number of bidirectional streams the peer connection can create diff --git a/src/Shared/runtime/Quic/QuicConnection.cs b/src/Shared/runtime/Quic/QuicConnection.cs index 999ce7aadd..6c82a237df 100644 --- a/src/Shared/runtime/Quic/QuicConnection.cs +++ b/src/Shared/runtime/Quic/QuicConnection.cs @@ -22,13 +22,13 @@ namespace System.Net.Quic /// The remote endpoint to connect to. /// TLS options /// The local endpoint to connect from. - public QuicConnection(IPEndPoint remoteEndPoint, SslClientAuthenticationOptions? sslClientAuthenticationOptions, IPEndPoint? localEndPoint = null) + public QuicConnection(EndPoint remoteEndPoint, SslClientAuthenticationOptions? sslClientAuthenticationOptions, IPEndPoint? localEndPoint = null) : this(QuicImplementationProviders.Default, remoteEndPoint, sslClientAuthenticationOptions, localEndPoint) { } // !!! TEMPORARY: Remove "implementationProvider" before shipping - public QuicConnection(QuicImplementationProvider implementationProvider, IPEndPoint remoteEndPoint, SslClientAuthenticationOptions? sslClientAuthenticationOptions, IPEndPoint? localEndPoint = null) + public QuicConnection(QuicImplementationProvider implementationProvider, EndPoint remoteEndPoint, SslClientAuthenticationOptions? sslClientAuthenticationOptions, IPEndPoint? localEndPoint = null) : this(implementationProvider, new QuicClientConnectionOptions() { RemoteEndPoint = remoteEndPoint, ClientAuthenticationOptions = sslClientAuthenticationOptions, LocalEndPoint = localEndPoint }) { } @@ -50,7 +50,7 @@ namespace System.Net.Quic public IPEndPoint LocalEndPoint => _provider.LocalEndPoint; - public IPEndPoint RemoteEndPoint => _provider.RemoteEndPoint; + public EndPoint RemoteEndPoint => _provider.RemoteEndPoint; public SslApplicationProtocol NegotiatedApplicationProtocol => _provider.NegotiatedApplicationProtocol; diff --git a/src/Shared/runtime/Quic/QuicException.cs b/src/Shared/runtime/Quic/QuicException.cs index 16a26d8afc..9a2351e6a9 100644 --- a/src/Shared/runtime/Quic/QuicException.cs +++ b/src/Shared/runtime/Quic/QuicException.cs @@ -1,12 +1,17 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#nullable enable namespace System.Net.Quic { internal class QuicException : Exception { - public QuicException(string message) - : base (message) + public QuicException(string? message) + : base(message) + { + } + public QuicException(string? message, Exception? innerException) + : base(message, innerException) { } }