Example #1
0
        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();
        }
Example #2
0
        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.");
        }
Example #3
0
        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
        }
Example #4
0
        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();
        }
Example #6
0
        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();
        }
Example #7
0
        /// <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();
        }
Example #10
0
        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();
        }
Example #11
0
        /// <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;
            }
        }
Example #12
0
        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;
            }
        }