Example #1
0
    public override ValueTask <ConnectionContext> ConnectAsync(IFeatureCollection?features = null, CancellationToken cancellationToken = default)
    {
        QuicStream quicStream;

        var streamDirectionFeature = features?.Get <IStreamDirectionFeature>();

        if (streamDirectionFeature != null)
        {
            if (streamDirectionFeature.CanRead)
            {
                quicStream = _connection.OpenBidirectionalStream();
            }
            else
            {
                quicStream = _connection.OpenUnidirectionalStream();
            }
        }
        else
        {
            quicStream = _connection.OpenBidirectionalStream();
        }

        // Only a handful of control streams are created by the server and they last for the
        // lifetime of the connection. No value in pooling them.
        QuicStreamContext?context = new QuicStreamContext(this, _context);

        context.Initialize(quicStream);
        context.Start();

        QuicLog.ConnectedStream(_log, context);

        return(new ValueTask <ConnectionContext>(context));
    }
    public async ValueTask <MultiplexedConnectionContext?> AcceptAsync(IFeatureCollection?features = null, CancellationToken cancellationToken = default)
    {
        if (_listener == null)
        {
            throw new InvalidOperationException($"The listener needs to be initialized by calling {nameof(CreateListenerAsync)}.");
        }

        try
        {
            var quicConnection = await _listener.AcceptConnectionAsync(cancellationToken);

            if (!_pendingConnections.TryGetValue(quicConnection, out var connectionContext))
            {
                throw new InvalidOperationException("Couldn't find ConnectionContext for QuicConnection.");
            }
            else
            {
                _pendingConnections.Remove(quicConnection);
            }

            // Verify the connection context was created and set correctly.
            Debug.Assert(connectionContext != null);
            Debug.Assert(connectionContext.GetInnerConnection() == quicConnection);

            QuicLog.AcceptedConnection(_log, connectionContext);

            return(connectionContext);
        }
        catch (QuicException ex) when(ex.QuicError == QuicError.OperationAborted)
        {
            QuicLog.ConnectionListenerAborted(_log, ex);
        }
        return(null);
    }
 private void ValidateServerAuthenticationOptions(SslServerAuthenticationOptions serverAuthenticationOptions)
 {
     if (serverAuthenticationOptions.ServerCertificate == null &&
         serverAuthenticationOptions.ServerCertificateContext == null &&
         serverAuthenticationOptions.ServerCertificateSelectionCallback == null)
     {
         QuicLog.ConnectionListenerCertificateNotSpecified(_log);
     }
     if (serverAuthenticationOptions.ApplicationProtocols == null || serverAuthenticationOptions.ApplicationProtocols.Count == 0)
     {
         QuicLog.ConnectionListenerApplicationProtocolsNotSpecified(_log);
     }
 }
Example #4
0
    public override void Abort(ConnectionAbortedException abortReason)
    {
        lock (_shutdownLock)
        {
            // Check if connection has already been already aborted.
            if (_abortReason != null)
            {
                return;
            }

            var resolvedErrorCode = _error ?? 0;
            _abortReason = ExceptionDispatchInfo.Capture(abortReason);
            QuicLog.ConnectionAbort(_log, this, resolvedErrorCode, abortReason.Message);
            _closeTask = _connection.CloseAsync(errorCode: resolvedErrorCode).AsTask();
        }
    }
    public async ValueTask <MultiplexedConnectionContext?> AcceptAsync(IFeatureCollection?features = null, CancellationToken cancellationToken = default)
    {
        try
        {
            var quicConnection = await _listener.AcceptConnectionAsync(cancellationToken);

            var connectionContext = new QuicConnectionContext(quicConnection, _context);

            QuicLog.AcceptedConnection(_log, connectionContext);

            return(connectionContext);
        }
        catch (QuicOperationAbortedException ex)
        {
            _log.LogDebug($"Listener has aborted with exception: {ex.Message}");
        }
        return(null);
    }
    public async ValueTask CreateListenerAsync()
    {
        QuicLog.ConnectionListenerStarting(_log, _quicListenerOptions.ListenEndPoint);

        try
        {
            _listener = await QuicListener.ListenAsync(_quicListenerOptions);
        }
        catch (QuicException ex) when(ex.QuicError == QuicError.AddressInUse)
        {
            throw new AddressInUseException(ex.Message, ex);
        }

        // EndPoint could be configured with an ephemeral port of 0.
        // Listener endpoint will resolve an ephemeral port, e.g. 127.0.0.1:0, into the actual port
        // so we need to update the public listener endpoint property.
        EndPoint = _listener.LocalEndPoint;
    }
Example #7
0
    public override async ValueTask <ConnectionContext?> AcceptAsync(CancellationToken cancellationToken = default)
    {
        try
        {
            var stream = await _connection.AcceptStreamAsync(cancellationToken);

            QuicStreamContext?context = null;

            // Only use pool for bidirectional streams. Just a handful of unidirecitonal
            // streams are created for a connection and they live for the lifetime of the connection.
            if (stream.CanRead && stream.CanWrite)
            {
                lock (_poolLock)
                {
                    StreamPool.TryPop(out context);
                }
            }

            if (context == null)
            {
                context = new QuicStreamContext(this, _context);
            }
            else
            {
                context.ResetFeatureCollection();
                context.ResetItems();
            }

            context.Initialize(stream);
            context.Start();

            QuicLog.AcceptedStream(_log, context);

            return(context);
        }
        catch (QuicConnectionAbortedException ex)
        {
            // Shutdown initiated by peer, abortive.
            _error = ex.ErrorCode;
            QuicLog.ConnectionAborted(_log, this, ex.ErrorCode, ex);

            ThreadPool.UnsafeQueueUserWorkItem(state =>
            {
                state.CancelConnectionClosedToken();
            },
                                               this,
                                               preferLocal: false);

            // Throw error so consumer sees the connection is aborted by peer.
            throw new ConnectionResetException(ex.Message, ex);
        }
        catch (QuicOperationAbortedException ex)
        {
            lock (_shutdownLock)
            {
                // This error should only happen when shutdown has been initiated by the server.
                // If there is no abort reason and we have this error then the connection is in an
                // unexpected state. Abort connection and throw reason error.
                if (_abortReason == null)
                {
                    Abort(new ConnectionAbortedException("Unexpected error when accepting stream.", ex));
                }

                _abortReason !.Throw();
            }
        }
        catch (OperationCanceledException)
        {
            Debug.Assert(cancellationToken.IsCancellationRequested, "Error requires cancellation is requested.");

            lock (_shutdownLock)
            {
                // Connection has been aborted. Throw reason exception.
                _abortReason?.Throw();
            }
        }
        catch (Exception ex)
        {
            Debug.Fail($"Unexpected exception in {nameof(QuicConnectionContext)}.{nameof(AcceptAsync)}: {ex}");
            throw;
        }

        // Return null for graceful closure or cancellation.
        return(null);
    }