From 78ff7040d995d683c5de84679fc72a4362f00e8b Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Sun, 3 Mar 2024 13:09:48 +0100 Subject: [PATCH 1/3] QUIC traverse all ip addresses and connect on host connection --- .../src/System/Net/Quic/QuicConnection.cs | 73 +++++++++++-------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs index 5a4f626e2f5465..2eeb4d3d5f806d 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs @@ -69,14 +69,51 @@ static async ValueTask StartConnectAsync(QuicClientConnectionOpt using CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - if (options.HandshakeTimeout != Timeout.InfiniteTimeSpan && options.HandshakeTimeout != TimeSpan.Zero) - { - linkedCts.CancelAfter(options.HandshakeTimeout); - } - try { - await connection.FinishConnectAsync(options, linkedCts.Token).ConfigureAwait(false); + if (!options.RemoteEndPoint.TryParse(out string? host, out IPAddress? address, out int port)) + { + throw new ArgumentException(SR.Format(SR.net_quic_unsupported_endpoint_type, options.RemoteEndPoint.GetType()), nameof(options)); + } + + if (address is not null && host is null) + { + if (options.HandshakeTimeout != Timeout.InfiniteTimeSpan && options.HandshakeTimeout != TimeSpan.Zero) + { + linkedCts.CancelAfter(options.HandshakeTimeout); + } + + await connection.FinishConnectAsync(options, null, address, port, linkedCts.Token).ConfigureAwait(false); + return connection; + } + + Debug.Assert(host is not null); + IPAddress[] addresses = await Dns.GetHostAddressesAsync(host, cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + if (addresses.Length == 0) + { + throw new SocketException((int)SocketError.HostNotFound); + } + + ExceptionDispatchInfo? lastException = null; + + foreach (IPAddress addr in addresses) + { + connection = new QuicConnection(); + try + { + await connection.FinishConnectAsync(options, host, addr, port, linkedCts.Token).ConfigureAwait(false); + lastException = null; + break; + } + catch (Exception ex) + { + lastException = ExceptionDispatchInfo.Capture(ex); + await connection.DisposeAsync().ConfigureAwait(false); + } + } + + lastException?.Throw(); } catch (OperationCanceledException) { @@ -289,7 +326,7 @@ internal unsafe QuicConnection(QUIC_HANDLE* handle, QUIC_NEW_CONNECTION_INFO* in #endif } - private async ValueTask FinishConnectAsync(QuicClientConnectionOptions options, CancellationToken cancellationToken = default) + private async ValueTask FinishConnectAsync(QuicClientConnectionOptions options, string? host, IPAddress address, int port, CancellationToken cancellationToken = default) { if (_connectedTcs.TryInitialize(out ValueTask valueTask, this, cancellationToken)) { @@ -297,28 +334,6 @@ private async ValueTask FinishConnectAsync(QuicClientConnectionOptions options, _defaultStreamErrorCode = options.DefaultStreamErrorCode; _defaultCloseErrorCode = options.DefaultCloseErrorCode; - if (!options.RemoteEndPoint.TryParse(out string? host, out IPAddress? address, out int port)) - { - throw new ArgumentException(SR.Format(SR.net_quic_unsupported_endpoint_type, options.RemoteEndPoint.GetType()), nameof(options)); - } - - if (address is null) - { - Debug.Assert(host is not null); - - // Given just a ServerName to connect to, msquic would also use the first address after the resolution - // (https://github.com/microsoft/msquic/issues/1181) and it would not return a well-known error code - // for resolution failures we could rely on. By doing the resolution in managed code, we can guarantee - // that a SocketException will surface to the user if the name resolution fails. - IPAddress[] addresses = await Dns.GetHostAddressesAsync(host, cancellationToken).ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); - if (addresses.Length == 0) - { - throw new SocketException((int)SocketError.HostNotFound); - } - address = addresses[0]; - } - QuicAddr remoteQuicAddress = new IPEndPoint(address, port).ToQuicAddr(); MsQuicHelpers.SetMsQuicParameter(_handle, QUIC_PARAM_CONN_REMOTE_ADDRESS, remoteQuicAddress); From 5ea605f3d2b333c8ae3f659c038992bd444986ac Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Mon, 4 Mar 2024 15:55:54 +0100 Subject: [PATCH 2/3] Check if connection disposed, before creating new one --- .../System.Net.Quic/src/System/Net/Quic/QuicConnection.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs index 2eeb4d3d5f806d..a7ed002744b01a 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs @@ -99,7 +99,10 @@ static async ValueTask StartConnectAsync(QuicClientConnectionOpt foreach (IPAddress addr in addresses) { - connection = new QuicConnection(); + if (connection._disposed != 0) + { + connection = new QuicConnection(); + } try { await connection.FinishConnectAsync(options, host, addr, port, linkedCts.Token).ConfigureAwait(false); From e81973163179bc58961958ff9aa1c7cfca8d893e Mon Sep 17 00:00:00 2001 From: Ahmet Ibrahim Aksoy Date: Tue, 5 Mar 2024 08:14:50 +0100 Subject: [PATCH 3/3] Reuse configuration --- .../src/System/Net/Quic/QuicConnection.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs index a7ed002744b01a..e445738bfb5c05 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs @@ -65,7 +65,9 @@ public static ValueTask ConnectAsync(QuicClientConnectionOptions static async ValueTask StartConnectAsync(QuicClientConnectionOptions options, CancellationToken cancellationToken) { + MsQuicSafeHandle? config = MsQuicConfiguration.Create(options); QuicConnection connection = new QuicConnection(); + connection._configuration = config; using CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); @@ -102,20 +104,24 @@ static async ValueTask StartConnectAsync(QuicClientConnectionOpt if (connection._disposed != 0) { connection = new QuicConnection(); + connection._configuration = config; } try { await connection.FinishConnectAsync(options, host, addr, port, linkedCts.Token).ConfigureAwait(false); lastException = null; + config = null; break; } catch (Exception ex) { lastException = ExceptionDispatchInfo.Capture(ex); + connection._configuration = null; // Ignore disposal of config on failure, to reuse it. await connection.DisposeAsync().ConfigureAwait(false); } } + config?.Dispose(); lastException?.Throw(); } catch (OperationCanceledException) @@ -354,7 +360,6 @@ private async ValueTask FinishConnectAsync(QuicClientConnectionOptions options, options.ClientAuthenticationOptions.CertificateRevocationCheckMode, options.ClientAuthenticationOptions.RemoteCertificateValidationCallback, options.ClientAuthenticationOptions.CertificateChainPolicy?.Clone()); - _configuration = MsQuicConfiguration.Create(options); // RFC 6066 forbids IP literals. // IDN mapping is handled by MsQuic. @@ -367,7 +372,7 @@ private async ValueTask FinishConnectAsync(QuicClientConnectionOptions options, { ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ConnectionStart( _handle, - _configuration, + _configuration!, (ushort)remoteQuicAddress.Family, (sbyte*)targetHostPtr, (ushort)port),