Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 3 additions & 12 deletions src/libraries/System.Net.Quic/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -195,21 +195,12 @@
<data name="net_quic_accept_not_allowed" xml:space="preserve">
<value>QuicConnection is configured to not accept any streams.</value>
</data>
<data name="net_quic_address_in_use" xml:space="preserve">
<value>The local address is already in use.</value>
</data>
<data name="net_quic_host_unreachable" xml:space="preserve">
<value>The server is currently unreachable.</value>
</data>
<data name="net_quic_connection_refused" xml:space="preserve">
<value>The server refused the connection.</value>
</data>
<data name="net_quic_protocol_error" xml:space="preserve">
<value>A QUIC protocol error was encountered</value>
</data>
<data name="net_quic_alpn_in_use" xml:space="preserve">
<value>Another QUIC listener is already listening on one of the requested application protocols on the same port.</value>
</data>
<data name="net_quic_ver_neg_error" xml:space="preserve">
<value>A version negotiation error was encountered.</value>
</data>
Expand All @@ -219,9 +210,6 @@
<data name="net_quic_connection_idle" xml:space="preserve">
<value>The connection timed out from inactivity.</value>
</data>
<data name="net_quic_invalid_address" xml:space="preserve">
<value>Binding to socket failed, likely caused by a family mismatch between local and remote address.</value>
</data>
<data name="net_quic_auth" xml:space="preserve">
<value>Authentication failed: {0}.</value>
</data>
Expand All @@ -239,5 +227,8 @@
<data name="net_InvalidSocketAddressSize" xml:space="preserve">
<value>The supplied {0} is an invalid size for the {1} end point.</value>
</data>
<data name="net_quic_callback_error" xml:space="preserve">
<value>User configured callback failed.</value>
</data>
</root>

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,13 +209,16 @@ public async ValueTask<QuicConnection> AcceptConnectionAsync(CancellationToken c
/// <param name="clientHello">The TLS ClientHello data.</param>
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))
Expand Down Expand Up @@ -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.
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,8 @@ public async Task CertificateCallbackThrowPropagates()

clientOptions.ClientAuthenticationOptions.TargetHost = "foobar1";

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

// Make sure the listener is still usable and there is no lingering bad connection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,42 @@ public async Task AcceptConnectionAsync_ThrowingOptionsCallback_Throws(bool useF
await using QuicListener listener = await CreateQuicListener(listenerOptions);

ValueTask<QuicConnection> connectTask = CreateQuicConnection(listener.LocalEndPoint);
Exception exception = await Assert.ThrowsAsync<Exception>(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<AuthenticationException>(() => 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<QuicConnection> connectTask = CreateQuicConnection(listener.LocalEndPoint);

Exception exception = await AssertThrowsQuicExceptionAsync(QuicError.CallbackError, async () => await listener.AcceptConnectionAsync());
Assert.True(exception.InnerException is ObjectDisposedException);
await Assert.ThrowsAsync<AuthenticationException>(() => 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]
Expand Down