public async Task CertificateCallbackThrowPropagates() { using CancellationTokenSource cts = new CancellationTokenSource(PassingTestTimeout); X509Certificate?receivedCertificate = null; var listenerOptions = new QuicListenerOptions(); listenerOptions.ListenEndPoint = new IPEndPoint(Socket.OSSupportsIPv6 ? IPAddress.IPv6Loopback : IPAddress.Loopback, 0); listenerOptions.ServerAuthenticationOptions = GetSslServerAuthenticationOptions(); using QuicListener listener = new QuicListener(QuicImplementationProviders.MsQuic, listenerOptions); QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(); clientOptions.RemoteEndPoint = listener.ListenEndPoint; clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => { receivedCertificate = cert; throw new ArithmeticException("foobar"); }; clientOptions.ClientAuthenticationOptions.TargetHost = "foobar1"; QuicConnection clientConnection = new QuicConnection(QuicImplementationProviders.MsQuic, clientOptions); Task <QuicConnection> serverTask = listener.AcceptConnectionAsync(cts.Token).AsTask(); await Assert.ThrowsAsync <ArithmeticException>(() => clientConnection.ConnectAsync(cts.Token).AsTask()); QuicConnection serverConnection = await serverTask; Assert.Equal(listenerOptions.ServerAuthenticationOptions.ServerCertificate, receivedCertificate); clientConnection.Dispose(); serverConnection.Dispose(); }
public static async ValueTask <QuicConnection> ConnectQuicAsync(string host, int port, SslClientAuthenticationOptions?clientAuthenticationOptions, CancellationToken cancellationToken) { IPAddress[] addresses = await Dns.GetHostAddressesAsync(host).ConfigureAwait(false); Exception?lastException = null; foreach (IPAddress address in addresses) { QuicConnection con = new QuicConnection(new IPEndPoint(address, port), clientAuthenticationOptions); try { await con.ConnectAsync(cancellationToken).ConfigureAwait(false); return(con); } // TODO: it would be great to catch a specific exception here... QUIC implementation dependent. catch (Exception ex) when(!(ex is OperationCanceledException)) { con.Dispose(); lastException = ex; } } if (lastException != null) { throw CreateWrappedException(lastException, cancellationToken); } // TODO: find correct exception to throw here. throw new HttpRequestException("No host found."); }
public override void Dispose() { // Close any remaining request streams (but NOT control streams, as these should not be closed while the connection is open) foreach (Http3LoopbackStream stream in _openStreams.Values) { stream.Dispose(); } foreach (QuicStream stream in _delayedStreams) { stream.Dispose(); } // We don't dispose the connection currently, because this causes races when the server connection is closed before // the client has received and handled all response data. // See discussion in https://github.com/dotnet/runtime/pull/57223#discussion_r687447832 #if false // Dispose the connection // If we already waited for graceful shutdown from the client, then the connection is already closed and this will simply release the handle. // If not, then this will silently abort the connection. _connection.Dispose(); // Dispose control streams so that we release their handles too. _inboundControlStream?.Dispose(); _outboundControlStream?.Dispose(); #endif }
private static async Task ServerConnectionTask(QuicConnection connection, CancellationToken cancellationToken) { try { while (true) { var stream = await connection.AcceptStreamAsync(cancellationToken).ConfigureAwait(false); if (!stream.CanRead || !stream.CanWrite) { await connection.CloseAsync(1, cancellationToken).ConfigureAwait(false); return; } _ = Helpers.Dispatch(() => ServerQuicStreamTask(connection, stream)); } } catch (QuicConnectionAbortedException e) when(e.ErrorCode == 0) { // ignore successful closing } catch (OperationCanceledException) { } finally { await connection.CloseAsync(1).ConfigureAwait(false); connection.Dispose(); } }
public override void Dispose() { foreach (Http3LoopbackStream stream in _openStreams.Values) { stream.Dispose(); } _connection.Dispose(); }
public async Task CertificateCallbackThrowPropagates() { using CancellationTokenSource cts = new CancellationTokenSource(PassingTestTimeout); X509Certificate?receivedCertificate = null; bool validationResult = false; var listenerOptions = new QuicListenerOptions(); listenerOptions.ListenEndPoint = new IPEndPoint(Socket.OSSupportsIPv6 ? IPAddress.IPv6Loopback : IPAddress.Loopback, 0); listenerOptions.ServerAuthenticationOptions = GetSslServerAuthenticationOptions(); using QuicListener listener = new QuicListener(QuicImplementationProviders.MsQuic, listenerOptions); QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(); clientOptions.RemoteEndPoint = listener.ListenEndPoint; clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => { receivedCertificate = cert; if (validationResult) { return(validationResult); } throw new ArithmeticException("foobar"); }; clientOptions.ClientAuthenticationOptions.TargetHost = "foobar1"; QuicConnection clientConnection = new QuicConnection(QuicImplementationProviders.MsQuic, clientOptions); await Assert.ThrowsAsync <ArithmeticException>(() => clientConnection.ConnectAsync(cts.Token).AsTask()); Assert.Equal(listenerOptions.ServerAuthenticationOptions.ServerCertificate, receivedCertificate); clientConnection.Dispose(); // Make sure the listner is still usable and there is no lingering bad conenction validationResult = true; (clientConnection, QuicConnection serverConnection) = await CreateConnectedQuicConnection(listener); await PingPong(clientConnection, serverConnection); clientConnection.Dispose(); serverConnection.Dispose(); }
/// <summary> /// Called when shutting down, this checks for when shutdown is complete (no more active requests) and does actual disposal. /// </summary> /// <remarks>Requires <see cref="SyncObj"/> to be locked.</remarks> private void CheckForShutdown() { Debug.Assert(Monitor.IsEntered(SyncObj)); Debug.Assert(ShuttingDown); if (_activeRequests.Count != 0) { return; } if (_connection != null) { // Close the QuicConnection in the background. if (_connectionClosedTask == null) { _connectionClosedTask = _connection.CloseAsync((long)Http3ErrorCode.NoError).AsTask(); } QuicConnection connection = _connection; _connection = null; _ = _connectionClosedTask.ContinueWith(closeTask => { if (closeTask.IsFaulted && NetEventSource.Log.IsEnabled()) { Trace($"{nameof(QuicConnection)} failed to close: {closeTask.Exception!.InnerException}"); } try { connection.Dispose(); } catch (Exception ex) { Trace($"{nameof(QuicConnection)} failed to dispose: {ex}"); } if (_clientControl != null) { _clientControl.Dispose(); _clientControl = null; } }, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); if (HttpTelemetry.Log.IsEnabled()) { if (Interlocked.Exchange(ref _markedByTelemetryStatus, TelemetryStatus_Closed) == TelemetryStatus_Opened) { HttpTelemetry.Log.Http30ConnectionClosed(); } } } }
public override void Dispose() { foreach (Http3LoopbackStream stream in _openStreams.Values) { stream.Dispose(); } if (!_closed) { CloseAsync(H3_INTERNAL_ERROR).GetAwaiter().GetResult(); } _connection.Dispose(); }
public override async ValueTask DisposeAsync() { try { _closeTask ??= _connection.CloseAsync(errorCode: 0).AsTask(); await _closeTask; } catch (Exception ex) { _log.LogWarning(ex, "Failed to gracefully shutdown connection."); } _connection.Dispose(); }
public override async ValueTask DisposeAsync() { try { if (_closeTask != default) { _closeTask = _connection.CloseAsync(errorCode: 0); await _closeTask; } else { await _closeTask; } } catch (Exception ex) { _log.LogWarning(ex, "Failed to gracefully shutdown connection."); } _connection.Dispose(); }
/// <summary> /// Called when shutting down, this checks for when shutdown is complete (no more active requests) and does actual disposal. /// </summary> /// <remarks>Requires <see cref="SyncObj"/> to be locked.</remarks> private void CheckForShutdown() { Debug.Assert(Monitor.IsEntered(SyncObj)); Debug.Assert(ShuttingDown); if (_activeRequests.Count != 0) { return; } if (_clientControl != null) { _clientControl.Dispose(); _clientControl = null; } if (_connection != null) { _connection.CloseAsync((long)Http3ErrorCode.NoError).GetAwaiter().GetResult(); // TODO: async... _connection.Dispose(); _connection = null; } }
public async Task ConnectWithCertificateCallback() { X509Certificate2 c1 = System.Net.Test.Common.Configuration.Certificates.GetServerCertificate(); X509Certificate2 c2 = System.Net.Test.Common.Configuration.Certificates.GetClientCertificate(); // This 'wrong' certificate but should be sufficient X509Certificate2 expectedCertificate = c1; using CancellationTokenSource cts = new CancellationTokenSource(); cts.CancelAfter(PassingTestTimeout); string? receivedHostName = null; X509Certificate?receivedCertificate = null; var listenerOptions = new QuicListenerOptions(); listenerOptions.ListenEndPoint = new IPEndPoint(Socket.OSSupportsIPv6 ? IPAddress.IPv6Loopback : IPAddress.Loopback, 0); listenerOptions.ServerAuthenticationOptions = GetSslServerAuthenticationOptions(); listenerOptions.ServerAuthenticationOptions.ServerCertificate = null; listenerOptions.ServerAuthenticationOptions.ServerCertificateSelectionCallback = (sender, hostName) => { receivedHostName = hostName; if (hostName == "foobar1") { return(c1); } else if (hostName == "foobar2") { return(c2); } return(null); }; using QuicListener listener = new QuicListener(QuicImplementationProviders.MsQuic, listenerOptions); QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(); clientOptions.ClientAuthenticationOptions.TargetHost = "foobar1"; clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => { receivedCertificate = cert; return(true); }; (QuicConnection clientConnection, QuicConnection serverConnection) = await CreateConnectedQuicConnection(clientOptions, listener); Assert.Equal(clientOptions.ClientAuthenticationOptions.TargetHost, receivedHostName); Assert.Equal(c1, receivedCertificate); clientConnection.Dispose(); serverConnection.Dispose(); // This should fail when callback return null. clientOptions.ClientAuthenticationOptions.TargetHost = "foobar3"; clientConnection = new QuicConnection(QuicImplementationProviders.MsQuic, clientOptions); Task clientTask = clientConnection.ConnectAsync(cts.Token).AsTask(); await Assert.ThrowsAsync <QuicException>(() => clientTask); Assert.Equal(clientOptions.ClientAuthenticationOptions.TargetHost, receivedHostName); clientConnection.Dispose(); // Do this last to make sure Listener is still functional. clientOptions.ClientAuthenticationOptions.TargetHost = "foobar2"; expectedCertificate = c2; (clientConnection, serverConnection) = await CreateConnectedQuicConnection(clientOptions, listener); Assert.Equal(clientOptions.ClientAuthenticationOptions.TargetHost, receivedHostName); Assert.Equal(c2, receivedCertificate); clientConnection.Dispose(); serverConnection.Dispose(); }
public static async Task Client(CancellationToken cancellationToken) { try { Console.WriteLine("Creating client connection"); using var client = new QuicConnection(serverEndpoint, new SslClientAuthenticationOptions { ApplicationProtocols = new List <SslApplicationProtocol> { new SslApplicationProtocol("sample") }, TargetHost = "localhost" }); Console.WriteLine($"Connecting to the server from {client.LocalEndPoint}"); await client.ConnectAsync(cancellationToken).ConfigureAwait(false); Console.WriteLine("Client connected, waiting for stream."); var stream = await client.AcceptStreamAsync(cancellationToken).ConfigureAwait(false); Console.WriteLine("Stream received, pulling data"); var buffer = new byte[1024 * 16]; var total = 0; int recv; do { recv = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); for (var i = 0; i < recv; i++) { if (buffer[i] != (byte)(total + i)) { throw new InvalidOperationException("Data corrupted"); } } total += recv; } while (recv > 0); if (total > DataSizeBytes) { throw new InvalidOperationException("Received unexpectedly more data"); } Console.WriteLine("Received all bytes"); await stream.DisposeAsync().ConfigureAwait(false); Console.WriteLine("Client stream disposed"); client.Dispose(); Console.WriteLine("Client connection disposed"); } catch (Exception e) { Console.WriteLine("Exception at client:"); Console.WriteLine(e); throw; } }