Skip to content

Commit bf78b40

Browse files
authored
wrap exceptions from callbacks in QuicError.CallbackError (#88614)
* wrap exceptions from callbacks in QuicError.CallbackError * feedback
1 parent 90b85ea commit bf78b40

File tree

5 files changed

+57
-17
lines changed

5 files changed

+57
-17
lines changed

src/libraries/System.Net.Quic/src/Resources/Strings.resx

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -195,21 +195,12 @@
195195
<data name="net_quic_accept_not_allowed" xml:space="preserve">
196196
<value>QuicConnection is configured to not accept any streams.</value>
197197
</data>
198-
<data name="net_quic_address_in_use" xml:space="preserve">
199-
<value>The local address is already in use.</value>
200-
</data>
201-
<data name="net_quic_host_unreachable" xml:space="preserve">
202-
<value>The server is currently unreachable.</value>
203-
</data>
204198
<data name="net_quic_connection_refused" xml:space="preserve">
205199
<value>The server refused the connection.</value>
206200
</data>
207201
<data name="net_quic_protocol_error" xml:space="preserve">
208202
<value>A QUIC protocol error was encountered</value>
209203
</data>
210-
<data name="net_quic_alpn_in_use" xml:space="preserve">
211-
<value>Another QUIC listener is already listening on one of the requested application protocols on the same port.</value>
212-
</data>
213204
<data name="net_quic_ver_neg_error" xml:space="preserve">
214205
<value>A version negotiation error was encountered.</value>
215206
</data>
@@ -219,9 +210,6 @@
219210
<data name="net_quic_connection_idle" xml:space="preserve">
220211
<value>The connection timed out from inactivity.</value>
221212
</data>
222-
<data name="net_quic_invalid_address" xml:space="preserve">
223-
<value>Binding to socket failed, likely caused by a family mismatch between local and remote address.</value>
224-
</data>
225213
<data name="net_quic_auth" xml:space="preserve">
226214
<value>Authentication failed: {0}.</value>
227215
</data>
@@ -239,5 +227,8 @@
239227
<data name="net_InvalidSocketAddressSize" xml:space="preserve">
240228
<value>The supplied {0} is an invalid size for the {1} end point.</value>
241229
</data>
230+
<data name="net_quic_callback_error" xml:space="preserve">
231+
<value>User configured callback failed.</value>
232+
</data>
242233
</root>
243234

src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.SslConnectionOptions.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
6868
SslPolicyErrors sslPolicyErrors = SslPolicyErrors.None;
6969
IntPtr certificateBuffer = 0;
7070
int certificateLength = 0;
71+
bool wrapException = false;
7172

7273
X509Chain? chain = null;
7374
X509Certificate2? result = null;
@@ -130,8 +131,10 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
130131
int status = QUIC_STATUS_SUCCESS;
131132
if (_validationCallback is not null)
132133
{
134+
wrapException = true;
133135
if (!_validationCallback(_connection, result, chain, sslPolicyErrors))
134136
{
137+
wrapException = false;
135138
if (_isClient)
136139
{
137140
throw new AuthenticationException(SR.net_quic_cert_custom_validation);
@@ -153,9 +156,14 @@ public unsafe int ValidateCertificate(QUIC_BUFFER* certificatePtr, QUIC_BUFFER*
153156
certificate = result;
154157
return status;
155158
}
156-
catch
159+
catch (Exception ex)
157160
{
158161
result?.Dispose();
162+
if (wrapException)
163+
{
164+
throw new QuicException(QuicError.CallbackError, null, SR.net_quic_callback_error, ex);
165+
}
166+
159167
throw;
160168
}
161169
finally

src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,16 @@ public async ValueTask<QuicConnection> AcceptConnectionAsync(CancellationToken c
209209
/// <param name="clientHello">The TLS ClientHello data.</param>
210210
private async void StartConnectionHandshake(QuicConnection connection, SslClientHelloInfo clientHello)
211211
{
212+
bool wrapException = false;
212213
CancellationToken cancellationToken = default;
213214
try
214215
{
215216
using CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_disposeCts.Token);
216217
linkedCts.CancelAfter(QuicDefaults.HandshakeTimeout);
217218
cancellationToken = linkedCts.Token;
219+
wrapException = true;
218220
QuicServerConnectionOptions options = await _connectionOptionsCallback(connection, clientHello, cancellationToken).ConfigureAwait(false);
221+
wrapException = false;
219222
options.Validate(nameof(options)); // Validate and fill in defaults for the options.
220223
await connection.FinishHandshakeAsync(options, clientHello.ServerName, cancellationToken).ConfigureAwait(false);
221224
if (!_acceptQueue.Writer.TryWrite(connection))
@@ -267,7 +270,10 @@ private async void StartConnectionHandshake(QuicConnection connection, SslClient
267270
}
268271

269272
await connection.DisposeAsync().ConfigureAwait(false);
270-
if (!_acceptQueue.Writer.TryWrite(ex))
273+
if (!_acceptQueue.Writer.TryWrite(
274+
wrapException ?
275+
ExceptionDispatchInfo.SetCurrentStackTrace(new QuicException(QuicError.CallbackError, null, SR.net_quic_callback_error, ex)) :
276+
ex))
271277
{
272278
// Channel has been closed, connection is already disposed, do nothing.
273279
}

src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,8 @@ public async Task CertificateCallbackThrowPropagates()
384384

385385
clientOptions.ClientAuthenticationOptions.TargetHost = "foobar1";
386386

387-
await Assert.ThrowsAsync<ArithmeticException>(() => CreateQuicConnection(clientOptions).AsTask());
387+
Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await CreateQuicConnection(clientOptions));
388+
Assert.True(exception.InnerException is ArithmeticException);
388389
await Assert.ThrowsAsync<AuthenticationException>(async () => await listener.AcceptConnectionAsync());
389390

390391
// Make sure the listener is still usable and there is no lingering bad connection

src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,42 @@ public async Task AcceptConnectionAsync_ThrowingOptionsCallback_Throws(bool useF
9696
await using QuicListener listener = await CreateQuicListener(listenerOptions);
9797

9898
ValueTask<QuicConnection> connectTask = CreateQuicConnection(listener.LocalEndPoint);
99-
Exception exception = await Assert.ThrowsAsync<Exception>(async () => await listener.AcceptConnectionAsync());
100-
Assert.Equal(expectedMessage, exception.Message);
99+
100+
Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await listener.AcceptConnectionAsync());
101+
Assert.NotNull(exception.InnerException);
102+
Assert.Equal(expectedMessage, exception.InnerException.Message);
103+
await Assert.ThrowsAsync<AuthenticationException>(() => connectTask.AsTask());
104+
}
105+
106+
[Fact]
107+
public async Task AcceptConnectionAsync_ThrowingCallbackOde_KeepRunning()
108+
{
109+
bool firstRun = true;
110+
111+
QuicListenerOptions listenerOptions = CreateQuicListenerOptions();
112+
// Throw an exception, which should throw the same from accept.
113+
listenerOptions.ConnectionOptionsCallback = (_, _, _) =>
114+
{
115+
if (firstRun)
116+
{
117+
firstRun = false;
118+
throw new ObjectDisposedException("failed");
119+
}
120+
121+
return ValueTask.FromResult(CreateQuicServerOptions());
122+
};
123+
await using QuicListener listener = await CreateQuicListener(listenerOptions);
124+
125+
ValueTask<QuicConnection> connectTask = CreateQuicConnection(listener.LocalEndPoint);
126+
127+
Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await listener.AcceptConnectionAsync());
128+
Assert.True(exception.InnerException is ObjectDisposedException);
129+
await Assert.ThrowsAsync<AuthenticationException>(() => connectTask.AsTask());
130+
131+
// Throwing ODE in callback should keep Listener running
132+
connectTask = CreateQuicConnection(listener.LocalEndPoint);
133+
await using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
134+
await using QuicConnection clientConnection = await connectTask;
101135
}
102136

103137
[Theory]

0 commit comments

Comments
 (0)