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)); }
/// <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); }
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)); }
/// <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); }