diff --git a/src/libraries/System.Net.Quic/src/Resources/Strings.resx b/src/libraries/System.Net.Quic/src/Resources/Strings.resx
index 0aff3df0a71f..dc8b015c3bf6 100644
--- a/src/libraries/System.Net.Quic/src/Resources/Strings.resx
+++ b/src/libraries/System.Net.Quic/src/Resources/Strings.resx
@@ -195,21 +195,12 @@
QuicConnection is configured to not accept any streams.
-
- The local address is already in use.
-
-
- The server is currently unreachable.
-
The server refused the connection.
A QUIC protocol error was encountered
-
- Another QUIC listener is already listening on one of the requested application protocols on the same port.
-
A version negotiation error was encountered.
@@ -219,9 +210,6 @@
The connection timed out from inactivity.
-
- Binding to socket failed, likely caused by a family mismatch between local and remote address.
-
Authentication failed: {0}.
@@ -239,5 +227,8 @@
The supplied {0} is an invalid size for the {1} end point.
+
+ User configured callback failed.
+
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.SslConnectionOptions.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.SslConnectionOptions.cs
index 333ef433bb99..dad23bfc342c 100644
--- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.SslConnectionOptions.cs
+++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.SslConnectionOptions.cs
@@ -68,6 +68,7 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
SslPolicyErrors sslPolicyErrors = SslPolicyErrors.None;
IntPtr certificateBuffer = 0;
int certificateLength = 0;
+ bool wrapException = false;
X509Chain? chain = null;
X509Certificate2? result = null;
@@ -130,8 +131,10 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
int status = QUIC_STATUS_SUCCESS;
if (_validationCallback is not null)
{
+ wrapException = true;
if (!_validationCallback(_connection, result, chain, sslPolicyErrors))
{
+ wrapException = false;
if (_isClient)
{
throw new AuthenticationException(SR.net_quic_cert_custom_validation);
@@ -153,9 +156,14 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
certificate = result;
return status;
}
- catch
+ catch (Exception ex)
{
result?.Dispose();
+ if (wrapException)
+ {
+ throw new QuicException(QuicError.CallbackError, null, SR.net_quic_callback_error, ex);
+ }
+
throw;
}
finally
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs
index 7b4c6613afab..eac32181dba2 100644
--- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs
+++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs
@@ -209,13 +209,16 @@ public async ValueTask AcceptConnectionAsync(CancellationToken c
/// The TLS ClientHello data.
private async void StartConnectionHandshake(QuicConnection connection, SslClientHelloInfo clientHello)
{
+ bool wrapException = false;
CancellationToken cancellationToken = default;
try
{
using CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_disposeCts.Token);
linkedCts.CancelAfter(QuicDefaults.HandshakeTimeout);
cancellationToken = linkedCts.Token;
+ wrapException = true;
QuicServerConnectionOptions options = await _connectionOptionsCallback(connection, clientHello, cancellationToken).ConfigureAwait(false);
+ wrapException = false;
options.Validate(nameof(options)); // Validate and fill in defaults for the options.
await connection.FinishHandshakeAsync(options, clientHello.ServerName, cancellationToken).ConfigureAwait(false);
if (!_acceptQueue.Writer.TryWrite(connection))
@@ -267,7 +270,10 @@ private async void StartConnectionHandshake(QuicConnection connection, SslClient
}
await connection.DisposeAsync().ConfigureAwait(false);
- if (!_acceptQueue.Writer.TryWrite(ex))
+ if (!_acceptQueue.Writer.TryWrite(
+ wrapException ?
+ ExceptionDispatchInfo.SetCurrentStackTrace(new QuicException(QuicError.CallbackError, null, SR.net_quic_callback_error, ex)) :
+ ex))
{
// Channel has been closed, connection is already disposed, do nothing.
}
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs
index 1a15e3f373f0..0471fdb2bbb4 100644
--- a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs
+++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs
@@ -384,7 +384,8 @@ public async Task CertificateCallbackThrowPropagates()
clientOptions.ClientAuthenticationOptions.TargetHost = "foobar1";
- await Assert.ThrowsAsync(() => CreateQuicConnection(clientOptions).AsTask());
+ Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await CreateQuicConnection(clientOptions));
+ Assert.True(exception.InnerException is ArithmeticException);
await Assert.ThrowsAsync(async () => await listener.AcceptConnectionAsync());
// Make sure the listener is still usable and there is no lingering bad connection
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs
index 68790da90195..68509bf6b557 100644
--- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs
+++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs
@@ -96,8 +96,42 @@ public async Task AcceptConnectionAsync_ThrowingOptionsCallback_Throws(bool useF
await using QuicListener listener = await CreateQuicListener(listenerOptions);
ValueTask connectTask = CreateQuicConnection(listener.LocalEndPoint);
- Exception exception = await Assert.ThrowsAsync(async () => await listener.AcceptConnectionAsync());
- Assert.Equal(expectedMessage, exception.Message);
+
+ Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await listener.AcceptConnectionAsync());
+ Assert.NotNull(exception.InnerException);
+ Assert.Equal(expectedMessage, exception.InnerException.Message);
+ await Assert.ThrowsAsync(() => connectTask.AsTask());
+ }
+
+ [Fact]
+ public async Task AcceptConnectionAsync_ThrowingCallbackOde_KeepRunning()
+ {
+ bool firstRun = true;
+
+ QuicListenerOptions listenerOptions = CreateQuicListenerOptions();
+ // Throw an exception, which should throw the same from accept.
+ listenerOptions.ConnectionOptionsCallback = (_, _, _) =>
+ {
+ if (firstRun)
+ {
+ firstRun = false;
+ throw new ObjectDisposedException("failed");
+ }
+
+ return ValueTask.FromResult(CreateQuicServerOptions());
+ };
+ await using QuicListener listener = await CreateQuicListener(listenerOptions);
+
+ ValueTask connectTask = CreateQuicConnection(listener.LocalEndPoint);
+
+ Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await listener.AcceptConnectionAsync());
+ Assert.True(exception.InnerException is ObjectDisposedException);
+ await Assert.ThrowsAsync(() => connectTask.AsTask());
+
+ // Throwing ODE in callback should keep Listener running
+ connectTask = CreateQuicConnection(listener.LocalEndPoint);
+ await using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
+ await using QuicConnection clientConnection = await connectTask;
}
[Theory]