diff --git a/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs b/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs index ea756707f4ff76..1958aed186b996 100644 --- a/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs +++ b/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs @@ -64,6 +64,7 @@ public sealed partial class QuicException : System.IO.IOException { public QuicException(System.Net.Quic.QuicError error, long? applicationErrorCode, string message) { } public long? ApplicationErrorCode { get { throw null; } } + public long? TransportErrorCode { get { throw null; } } public System.Net.Quic.QuicError QuicError { get { throw null; } } } public sealed partial class QuicListener : System.IAsyncDisposable diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicHelpers.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicHelpers.cs index bf454c047c3cdd..f97c55a6292894 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicHelpers.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicHelpers.cs @@ -67,11 +67,7 @@ internal static unsafe T GetMsQuicParameter(MsQuicSafeHandle handle, uint par &length, (byte*)&value); - if (StatusFailed(status)) - { - throw ThrowHelper.GetExceptionForMsQuicStatus(status, $"GetParam({handle}, {parameter}) failed"); - } - + ThrowHelper.ThrowIfMsQuicError(status, $"GetParam({handle}, {parameter}) failed"); return value; } @@ -84,9 +80,6 @@ internal static unsafe void SetMsQuicParameter(MsQuicSafeHandle handle, uint (uint)sizeof(T), (byte*)&value); - if (StatusFailed(status)) - { - throw ThrowHelper.GetExceptionForMsQuicStatus(status, $"SetParam({handle}, {parameter}) failed"); - } + ThrowHelper.ThrowIfMsQuicError(status, $"SetParam({handle}, {parameter}) failed"); } } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/ThrowHelper.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/ThrowHelper.cs index 5774f7f9d7df67..a2e621efb8ec27 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/ThrowHelper.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/ThrowHelper.cs @@ -1,12 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.Quic; using System.Security.Authentication; using System.Net.Security; using System.Net.Sockets; using static Microsoft.Quic.MsQuic; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; namespace System.Net.Quic; @@ -38,21 +38,21 @@ internal static bool TryGetStreamExceptionForMsQuicStatus(int status, [NotNullWh else if (status == QUIC_STATUS_INVALID_STATE) { // If status == QUIC_STATUS_INVALID_STATE, we have closed the connection. - exception = ThrowHelper.GetOperationAbortedException(); + exception = GetOperationAbortedException(); return true; } else if (StatusFailed(status)) { - exception = ThrowHelper.GetExceptionForMsQuicStatus(status); + exception = GetExceptionForMsQuicStatus(status); return true; } exception = null; return false; } - internal static Exception GetExceptionForMsQuicStatus(int status, string? message = null) + internal static Exception GetExceptionForMsQuicStatus(int status, long? errorCode = default, string? message = null) { - Exception ex = GetExceptionInternal(status, message); + Exception ex = GetExceptionInternal(status, errorCode, message); if (status != 0) { // Include the raw MsQuic status in the HResult property for better diagnostics @@ -61,17 +61,17 @@ internal static Exception GetExceptionForMsQuicStatus(int status, string? messag return ex; - static Exception GetExceptionInternal(int status, string? message) + static Exception GetExceptionInternal(int status, long? errorCode, string? message) { // // Start by checking for statuses mapped to QuicError enum // - if (status == QUIC_STATUS_CONNECTION_REFUSED) return new QuicException(QuicError.ConnectionRefused, null, SR.net_quic_connection_refused); - if (status == QUIC_STATUS_CONNECTION_TIMEOUT) return new QuicException(QuicError.ConnectionTimeout, null, SR.net_quic_timeout); - if (status == QUIC_STATUS_VER_NEG_ERROR) return new QuicException(QuicError.VersionNegotiationError, null, SR.net_quic_ver_neg_error); - if (status == QUIC_STATUS_CONNECTION_IDLE) return new QuicException(QuicError.ConnectionIdle, null, SR.net_quic_connection_idle); - if (status == QUIC_STATUS_PROTOCOL_ERROR) return new QuicException(QuicError.TransportError, null, SR.net_quic_protocol_error); - if (status == QUIC_STATUS_ALPN_IN_USE) return new QuicException(QuicError.AlpnInUse, null, SR.net_quic_protocol_error); + if (status == QUIC_STATUS_CONNECTION_REFUSED) return new QuicException(QuicError.ConnectionRefused, null, errorCode, SR.net_quic_connection_refused); + if (status == QUIC_STATUS_CONNECTION_TIMEOUT) return new QuicException(QuicError.ConnectionTimeout, null, errorCode, SR.net_quic_timeout); + if (status == QUIC_STATUS_VER_NEG_ERROR) return new QuicException(QuicError.VersionNegotiationError, null, errorCode, SR.net_quic_ver_neg_error); + if (status == QUIC_STATUS_CONNECTION_IDLE) return new QuicException(QuicError.ConnectionIdle, null, errorCode, SR.net_quic_connection_idle); + if (status == QUIC_STATUS_PROTOCOL_ERROR) return new QuicException(QuicError.TransportError, null, errorCode, SR.net_quic_protocol_error); + if (status == QUIC_STATUS_ALPN_IN_USE) return new QuicException(QuicError.AlpnInUse, null, errorCode, SR.net_quic_protocol_error); // // Transport errors will throw SocketException @@ -81,7 +81,7 @@ static Exception GetExceptionInternal(int status, string? message) if (status == QUIC_STATUS_UNREACHABLE) return new SocketException((int)SocketError.HostUnreachable); // - // TLS and certificate erros throw AuthenticationException to match SslStream + // TLS and certificate errors throw AuthenticationException to match SslStream // if (status == QUIC_STATUS_TLS_ERROR || status == QUIC_STATUS_CERT_EXPIRED || @@ -125,11 +125,12 @@ static Exception GetExceptionInternal(int status, string? message) } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void ThrowIfMsQuicError(int status, string? message = null) { if (StatusFailed(status)) { - throw GetExceptionForMsQuicStatus(status, message); + throw GetExceptionForMsQuicStatus(status, message: message); } } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs index b3ac83567cee41..a1e6f0fdc78b12 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs @@ -42,7 +42,7 @@ public sealed partial class QuicConnection : IAsyncDisposable /// /// The actual secret structure wrapper passed to MsQuic. /// - private MsQuicTlsSecret? _tlsSecret; + private readonly MsQuicTlsSecret? _tlsSecret; #endif /// @@ -304,7 +304,7 @@ private async ValueTask FinishConnectAsync(QuicClientConnectionOptions options, // RFC 6066 forbids IP literals // DNI mapping is handled by MsQuic - var hostname = TargetHostNameHelper.IsValidAddress(options.ClientAuthenticationOptions.TargetHost) + string hostname = TargetHostNameHelper.IsValidAddress(options.ClientAuthenticationOptions.TargetHost) ? string.Empty : options.ClientAuthenticationOptions.TargetHost ?? string.Empty; @@ -492,9 +492,7 @@ private unsafe int HandleEventConnected(ref CONNECTED_DATA data) } private unsafe int HandleEventShutdownInitiatedByTransport(ref SHUTDOWN_INITIATED_BY_TRANSPORT_DATA data) { - // TODO: we should propagate transport error code. - // https://github.com/dotnet/runtime/issues/72666 - Exception exception = ExceptionDispatchInfo.SetCurrentStackTrace(ThrowHelper.GetExceptionForMsQuicStatus(data.Status)); + Exception exception = ExceptionDispatchInfo.SetCurrentStackTrace(ThrowHelper.GetExceptionForMsQuicStatus(data.Status, (long)data.ErrorCode)); _connectedTcs.TrySetException(exception); _acceptQueue.Writer.TryComplete(exception); return QUIC_STATUS_SUCCESS; diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicException.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicException.cs index 7324c826e2b8f6..ce8d733095c3b4 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicException.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicException.cs @@ -17,7 +17,18 @@ public sealed class QuicException : IOException /// The application protocol error code associated with the error. /// The message for the exception. public QuicException(QuicError error, long? applicationErrorCode, string message) - : this(error, applicationErrorCode, message, null) + : this(error, applicationErrorCode, null, message, null) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The error associated with the exception. + /// The application protocol error code associated with the error. + /// The transport protocol error code associated with the error. + /// The message for the exception. + internal QuicException(QuicError error, long? applicationErrorCode, long? transportErrorCode, string message) + : this(error, applicationErrorCode, transportErrorCode, message, null) { } /// @@ -28,10 +39,23 @@ public QuicException(QuicError error, long? applicationErrorCode, string message /// The message for the exception. /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. internal QuicException(QuicError error, long? applicationErrorCode, string message, Exception? innerException) + : this(error, applicationErrorCode, null, message, innerException) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The error associated with the exception. + /// The application protocol error code associated with the error. + /// The transport protocol error code associated with the error. + /// The message for the exception. + /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified. + internal QuicException(QuicError error, long? applicationErrorCode, long? transportErrorCode, string message, Exception? innerException) : base(message, innerException) { QuicError = error; ApplicationErrorCode = applicationErrorCode; + TransportErrorCode = transportErrorCode; } /// @@ -46,4 +70,9 @@ internal QuicException(QuicError error, long? applicationErrorCode, string messa /// This property contains the error code set by the application layer when closing the connection () or closing a read/write direction of a QUIC stream (). Contains null for all other errors. /// public long? ApplicationErrorCode { get; } + + /// + /// The transport protocol error code associated with the error. + /// + public long? TransportErrorCode { get; } } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs index ac45fbb312fd32..6165f2085cb5f0 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStream.cs @@ -581,13 +581,9 @@ private unsafe int HandleEventShutdownComplete(ref SHUTDOWN_COMPLETE_DATA data) // It's local shutdown by app, this side called QuicConnection.CloseAsync, throw QuicError.OperationAborted. (shutdownByApp: true, closedRemotely: false) => ThrowHelper.GetOperationAbortedException(), // It's remote shutdown by transport, we received a CONNECTION_CLOSE frame with a QUIC transport error code, throw error based on the status. - // TODO: we should propagate the transport error code - // https://github.com/dotnet/runtime/issues/72666 - (shutdownByApp: false, closedRemotely: true) => ThrowHelper.GetExceptionForMsQuicStatus(data.ConnectionCloseStatus, $"Shutdown by transport {data.ConnectionErrorCode}"), + (shutdownByApp: false, closedRemotely: true) => ThrowHelper.GetExceptionForMsQuicStatus(data.ConnectionCloseStatus, (long)data.ConnectionErrorCode, $"Shutdown by transport {data.ConnectionErrorCode}"), // It's local shutdown by transport, most likely due to a timeout, throw error based on the status. - // TODO: we should propagate the transport error code - // https://github.com/dotnet/runtime/issues/72666 - (shutdownByApp: false, closedRemotely: false) => ThrowHelper.GetExceptionForMsQuicStatus(data.ConnectionCloseStatus), + (shutdownByApp: false, closedRemotely: false) => ThrowHelper.GetExceptionForMsQuicStatus(data.ConnectionCloseStatus, (long)data.ConnectionErrorCode), }; _startedTcs.TrySetException(exception); _receiveTcs.TrySetException(exception, final: true);