Exemple #1
0
        private unsafe IPEndPoint Start(QuicListenerOptions options)
        {
            List <SslApplicationProtocol> applicationProtocols = options.ServerAuthenticationOptions !.ApplicationProtocols !;
            IPEndPoint listenEndPoint = options.ListenEndPoint !;

            Internals.SocketAddress address = IPEndPointExtensions.Serialize(listenEndPoint);

            Debug.Assert(_stateHandle.IsAllocated);
            try
            {
                Debug.Assert(!Monitor.IsEntered(_state), "!Monitor.IsEntered(_state)");
                using var msquicBuffers = new MsQuicBuffers();
                msquicBuffers.Initialize(applicationProtocols, applicationProtocol => applicationProtocol.Protocol);
                // TODO: is the layout same for SocketAddress.Buffer and QuicAddr?
                // TODO: maybe add simple extensions/helpers:
                //       - QuicAddr ToQuicAddr(this IPEndPoint ipEndPoint)
                //       - IPEndPoint ToIPEndPoint(this ref QuicAddr quicAddress)
                fixed(byte *paddress = address.Buffer)
                {
                    ThrowIfFailure(MsQuicApi.Api.ApiTable->ListenerStart(
                                       _state.Handle.QuicHandle,
                                       msquicBuffers.Buffers,
                                       (uint)applicationProtocols.Count,
                                       (QuicAddr *)paddress), "ListenerStart failed");
                }
            }
            catch
            {
                _stateHandle.Free();
                throw;
            }

            Debug.Assert(!Monitor.IsEntered(_state), "!Monitor.IsEntered(_state)");
            return(MsQuicParameterHelpers.GetIPEndPointParam(MsQuicApi.Api, _state.Handle, QUIC_PARAM_LISTENER_LOCAL_ADDRESS));
        }
Exemple #2
0
    /// <summary>
    /// Initializes and starts a new instance of a <see cref="QuicListener" />.
    /// </summary>
    /// <param name="options">Options to start the listener.</param>
    private unsafe QuicListener(QuicListenerOptions options)
    {
        GCHandle context = GCHandle.Alloc(this, GCHandleType.Weak);

        try
        {
            QUIC_HANDLE *handle;
            ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->ListenerOpen(
                                               MsQuicApi.Api.Registration.QuicHandle,
                                               &NativeCallback,
                                               (void *)GCHandle.ToIntPtr(context),
                                               &handle),
                                           "ListenerOpen failed");
            _handle = new MsQuicContextSafeHandle(handle, context, MsQuicApi.Api.ApiTable->ListenerClose, SafeHandleType.Listener);
        }
        catch
        {
            context.Free();
            throw;
        }

        // Save the connection options before starting the listener
        _connectionOptionsCallback = options.ConnectionOptionsCallback;
        _acceptQueue = Channel.CreateBounded <PendingConnection>(new BoundedChannelOptions(options.ListenBacklog)
        {
            SingleWriter = true
        });

        // Start the listener, from now on MsQuic events will come.
        using MsQuicBuffers alpnBuffers = new MsQuicBuffers();
        alpnBuffers.Initialize(options.ApplicationProtocols, applicationProtocol => applicationProtocol.Protocol);
        QuicAddr address = options.ListenEndPoint.ToQuicAddr();

        if (options.ListenEndPoint.Address.Equals(IPAddress.IPv6Any))
        {
            // For IPv6Any, MsQuic would listen only for IPv6 connections. This would make it impossible
            // to connect the listener by using the IPv4 address (which could have been e.g. resolved by DNS).
            // Using the Unspecified family makes MsQuic handle connections from all IP addresses.
            address.Family = QUIC_ADDRESS_FAMILY_UNSPEC;
        }
        ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->ListenerStart(
                                           _handle.QuicHandle,
                                           alpnBuffers.Buffers,
                                           (uint)alpnBuffers.Count,
                                           &address),
                                       "ListenerStart failed");

        // Get the actual listening endpoint.
        address       = GetMsQuicParameter <QuicAddr>(_handle, QUIC_PARAM_LISTENER_LOCAL_ADDRESS);
        LocalEndPoint = address.ToIPEndPoint(options.ListenEndPoint.AddressFamily);
    }
Exemple #3
0
        private unsafe IPEndPoint Start(QuicListenerOptions options)
        {
            List <SslApplicationProtocol> applicationProtocols = options.ServerAuthenticationOptions !.ApplicationProtocols !;
            IPEndPoint listenEndPoint = options.ListenEndPoint !;

            Debug.Assert(_stateHandle.IsAllocated);
            try
            {
                Debug.Assert(!Monitor.IsEntered(_state), "!Monitor.IsEntered(_state)");
                using var msquicBuffers = new MsQuicBuffers();
                msquicBuffers.Initialize(applicationProtocols, applicationProtocol => applicationProtocol.Protocol);

                QuicAddr address = listenEndPoint.ToQuicAddr();

                if (listenEndPoint.Address.Equals(IPAddress.IPv6Any))
                {
                    // For IPv6Any, MsQuic would listen only for IPv6 connections. This would make it impossible
                    // to connect the listener by using the IPv4 address (which could have been e.g. resolved by DNS).
                    // Using the Unspecified family makes MsQuic handle connections from all IP addresses.
                    address.Family = QUIC_ADDRESS_FAMILY_UNSPEC;
                }

                ThrowIfFailure(MsQuicApi.Api.ApiTable->ListenerStart(
                                   _state.Handle.QuicHandle,
                                   msquicBuffers.Buffers,
                                   (uint)applicationProtocols.Count,
                                   &address), "ListenerStart failed");
            }
            catch
            {
                _stateHandle.Free();
                throw;
            }

            Debug.Assert(!Monitor.IsEntered(_state), "!Monitor.IsEntered(_state)");
            // override the address family to the original value in case we had to use UNSPEC
            return(MsQuicParameterHelpers.GetIPEndPointParam(MsQuicApi.Api, _state.Handle, QUIC_PARAM_LISTENER_LOCAL_ADDRESS, listenEndPoint.AddressFamily));
        }
Exemple #4
0
    /// <inheritdoc cref="WriteAsync(System.ReadOnlyMemory{byte},System.Threading.CancellationToken)"/>
    /// <param name="buffer">The region of memory to write data from.</param>
    /// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param>
    /// <param name="completeWrites">Notifies the peer about gracefully closing the write side, i.e.: sends FIN flag with the data.</param>
    public ValueTask WriteAsync(ReadOnlyMemory <byte> buffer, bool completeWrites, CancellationToken cancellationToken = default)
    {
        ObjectDisposedException.ThrowIf(_disposed == 1, this);

        if (!_canWrite)
        {
            throw new InvalidOperationException(SR.net_quic_writing_notallowed);
        }

        if (NetEventSource.Log.IsEnabled())
        {
            NetEventSource.Info(this, $"{this} Stream writing memory of '{buffer.Length}' bytes while {(completeWrites ? "completing" : "not completing")} writes.");
        }

        if (_sendTcs.IsCompleted)
        {
            // Special case exception type for pre-canceled token while we've already transitioned to a final state and don't need to abort write.
            // It must happen before we try to get the value task, since the task source is versioned and each instance must be awaited.
            cancellationToken.ThrowIfCancellationRequested();
        }

        // Concurrent call, this one lost the race.
        if (!_sendTcs.TryGetValueTask(out ValueTask valueTask, this, cancellationToken))
        {
            throw new InvalidOperationException(SR.Format(SR.net_io_invalidnestedcall, "write"));
        }

        // No need to call anything since we already have a result, most likely an exception.
        if (valueTask.IsCompleted)
        {
            return(valueTask);
        }

        // For an empty buffer complete immediately, close the writing side of the stream if necessary.
        if (buffer.IsEmpty)
        {
            _sendTcs.TrySetResult();
            if (completeWrites)
            {
                CompleteWrites();
            }
            return(valueTask);
        }

        try
        {
            _sendBuffers.Initialize(buffer);
            unsafe
            {
                int status = MsQuicApi.Api.ApiTable->StreamSend(
                    _handle.QuicHandle,
                    _sendBuffers.Buffers,
                    (uint)_sendBuffers.Count,
                    completeWrites ? QUIC_SEND_FLAGS.FIN : QUIC_SEND_FLAGS.NONE,
                    null);
                if (status == QUIC_STATUS_ABORTED)
                {
                    // If status == QUIC_STATUS_ABORTED, we either received PEER_RECEIVE_ABORTED or will receive SHUTDOWN_COMPLETE(ConnectionClose) later, all of which completes the _sendTcs.
                    _sendBuffers.Reset();
                    return(valueTask);
                }
                ThrowHelper.ThrowIfMsQuicError(status, "StreamSend failed");
            }
        }
        catch (Exception ex)
        {
            _sendTcs.TrySetException(ex, final: true);
            _sendBuffers.Reset();
            throw;
        }

        return(valueTask);
    }
        // TODO: this is called from MsQuicListener and when it fails it wreaks havoc in MsQuicListener finalizer.
        //       Consider moving bigger logic like this outside of constructor call chains.
        private static unsafe SafeMsQuicConfigurationHandle Create(QuicOptions options, QUIC_CREDENTIAL_FLAGS flags, X509Certificate?certificate, SslStreamCertificateContext?certificateContext, List <SslApplicationProtocol>?alpnProtocols, CipherSuitesPolicy?cipherSuitesPolicy)
        {
            // TODO: some of these checks should be done by the QuicOptions type.
            if (alpnProtocols == null || alpnProtocols.Count == 0)
            {
                throw new Exception("At least one SslApplicationProtocol value must be present in SslClientAuthenticationOptions or SslServerAuthenticationOptions.");
            }

            if (options.MaxBidirectionalStreams > ushort.MaxValue)
            {
                throw new Exception("MaxBidirectionalStreams overflow.");
            }

            if (options.MaxBidirectionalStreams > ushort.MaxValue)
            {
                throw new Exception("MaxBidirectionalStreams overflow.");
            }

            if ((flags & QUIC_CREDENTIAL_FLAGS.CLIENT) == 0)
            {
                if (certificate == null && certificateContext == null)
                {
                    throw new Exception("Server must provide certificate");
                }
            }
            else
            {
                flags |= QUIC_CREDENTIAL_FLAGS.INDICATE_CERTIFICATE_RECEIVED | QUIC_CREDENTIAL_FLAGS.NO_CERTIFICATE_VALIDATION;
            }

            if (!OperatingSystem.IsWindows())
            {
                // Use certificate handles on Windows, fall-back to ASN1 otherwise.
                flags |= QUIC_CREDENTIAL_FLAGS.USE_PORTABLE_CERTIFICATES;
            }

            Debug.Assert(!MsQuicApi.Api.Registration.IsInvalid);

            QUIC_SETTINGS settings = default(QUIC_SETTINGS);

            settings.IsSet.PeerUnidiStreamCount = 1;
            settings.PeerUnidiStreamCount       = (ushort)options.MaxUnidirectionalStreams;
            settings.IsSet.PeerBidiStreamCount  = 1;
            settings.PeerBidiStreamCount        = (ushort)options.MaxBidirectionalStreams;

            settings.IsSet.IdleTimeoutMs = 1;
            if (options.IdleTimeout != Timeout.InfiniteTimeSpan)
            {
                if (options.IdleTimeout <= TimeSpan.Zero)
                {
                    throw new Exception("IdleTimeout must not be negative.");
                }
                settings.IdleTimeoutMs = (ulong)options.IdleTimeout.TotalMilliseconds;
            }
            else
            {
                settings.IdleTimeoutMs = 0;
            }

            SafeMsQuicConfigurationHandle configurationHandle;

            X509Certificate2[]? intermediates = null;

            QUIC_HANDLE *handle;

            using var msquicBuffers = new MsQuicBuffers();
            msquicBuffers.Initialize(alpnProtocols, alpnProtocol => alpnProtocol.Protocol);
            ThrowIfFailure(MsQuicApi.Api.ApiTable->ConfigurationOpen(
                               MsQuicApi.Api.Registration.QuicHandle,
                               msquicBuffers.Buffers,
                               (uint)alpnProtocols.Count,
                               &settings,
                               (uint)sizeof(QUIC_SETTINGS),
                               (void *)IntPtr.Zero,
                               &handle), "ConfigurationOpen failed");
            configurationHandle = new SafeMsQuicConfigurationHandle(handle);

            try
            {
                QUIC_CREDENTIAL_CONFIG config = default;
                config.Flags = flags; // TODO: consider using LOAD_ASYNCHRONOUS with a callback.

                if (cipherSuitesPolicy != null)
                {
                    config.Flags |= QUIC_CREDENTIAL_FLAGS.SET_ALLOWED_CIPHER_SUITES;
                    config.AllowedCipherSuites = CipherSuitePolicyToFlags(cipherSuitesPolicy);
                }

                if (certificateContext != null)
                {
                    certificate   = certificateContext.Certificate;
                    intermediates = certificateContext.IntermediateCertificates;
                }

                int status;
                if (certificate != null)
                {
                    if (OperatingSystem.IsWindows())
                    {
                        config.Type = QUIC_CREDENTIAL_TYPE.CERTIFICATE_CONTEXT;
                        config.CertificateContext = (void *)certificate.Handle;
                        status = MsQuicApi.Api.ApiTable->ConfigurationLoadCredential(configurationHandle.QuicHandle, &config);
                    }
                    else
                    {
                        byte[] asn1;

                        if (intermediates?.Length > 0)
                        {
                            X509Certificate2Collection collection = new X509Certificate2Collection();
                            collection.Add(certificate);
                            for (int i = 0; i < intermediates?.Length; i++)
                            {
                                collection.Add(intermediates[i]);
                            }

                            asn1 = collection.Export(X509ContentType.Pkcs12) !;
                        }
                        else
                        {
                            asn1 = certificate.Export(X509ContentType.Pkcs12);
                        }

                        fixed(byte *ptr = asn1)
                        {
                            QUIC_CERTIFICATE_PKCS12 pkcs12Config = new QUIC_CERTIFICATE_PKCS12
                            {
                                Asn1Blob           = ptr,
                                Asn1BlobLength     = (uint)asn1.Length,
                                PrivateKeyPassword = (sbyte *)IntPtr.Zero
                            };

                            config.Type = QUIC_CREDENTIAL_TYPE.CERTIFICATE_PKCS12;
                            config.CertificatePkcs12 = &pkcs12Config;
                            status = MsQuicApi.Api.ApiTable->ConfigurationLoadCredential(configurationHandle.QuicHandle, &config);
                        }
                    }
                }
                else
                {
                    config.Type = QUIC_CREDENTIAL_TYPE.NONE;
                    status      = MsQuicApi.Api.ApiTable->ConfigurationLoadCredential(configurationHandle.QuicHandle, &config);
                }

#if TARGET_WINDOWS
                if ((Interop.SECURITY_STATUS)status == Interop.SECURITY_STATUS.AlgorithmMismatch && MsQuicApi.Tls13MayBeDisabled)
                {
                    throw new MsQuicException(status, SR.net_ssl_app_protocols_invalid);
                }
#endif

                ThrowIfFailure(status, "ConfigurationLoadCredential failed");
            }
            catch
            {
                configurationHandle.Dispose();
                throw;
            }

            return(configurationHandle);
        }