using System.IO; using System.Net.Http; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; namespace Jellyfin.Networking.HappyEyeballs { /// /// Defines the class. /// /// Implementation taken from https://github.com/ppy/osu-framework/pull/4191 . /// public static class HttpClientExtension { /// /// Gets or sets a value indicating whether the client should use IPv6. /// public static bool UseIPv6 { get; set; } = true; // Implementation taken from https://github.com/rmkerr/corefx/blob/SocketsHttpHandler_Connect_HappyEyeballs/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs /// /// Implements the httpclient callback method. /// /// The instance. /// The instance. /// The http steam. public static async ValueTask OnConnect(SocketsHttpConnectionContext context, CancellationToken cancellationToken) { if (!UseIPv6) { return await AttemptConnection(AddressFamily.InterNetwork, context, cancellationToken).ConfigureAwait(false); } using var cancelIPv6 = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var tryConnectAsyncIPv6 = AttemptConnection(AddressFamily.InterNetworkV6, context, cancelIPv6.Token); if (await Task.WhenAny(tryConnectAsyncIPv6, Task.Delay(200, cancelIPv6.Token)).ConfigureAwait(false) == tryConnectAsyncIPv6 && tryConnectAsyncIPv6.IsCompletedSuccessfully) { cancelIPv6.Cancel(); return tryConnectAsyncIPv6.GetAwaiter().GetResult(); } using var cancelIPv4 = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var tryConnectAsyncIPv4 = AttemptConnection(AddressFamily.InterNetwork, context, cancelIPv4.Token); if (await Task.WhenAny(tryConnectAsyncIPv6, tryConnectAsyncIPv4).ConfigureAwait(false) == tryConnectAsyncIPv6) { if (tryConnectAsyncIPv6.IsCompletedSuccessfully) { cancelIPv4.Cancel(); return tryConnectAsyncIPv6.GetAwaiter().GetResult(); } return tryConnectAsyncIPv4.GetAwaiter().GetResult(); } else { if (tryConnectAsyncIPv4.IsCompletedSuccessfully) { cancelIPv6.Cancel(); return tryConnectAsyncIPv4.GetAwaiter().GetResult(); } return tryConnectAsyncIPv6.GetAwaiter().GetResult(); } } private static async Task AttemptConnection(AddressFamily addressFamily, SocketsHttpConnectionContext context, CancellationToken cancellationToken) { // The following socket constructor will create a dual-mode socket on systems where IPV6 is available. var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp) { // Turn off Nagle's algorithm since it degrades performance in most HttpClient scenarios. NoDelay = true }; try { await socket.ConnectAsync(context.DnsEndPoint, cancellationToken).ConfigureAwait(false); // The stream should take the ownership of the underlying socket, // closing it when it's disposed. return new NetworkStream(socket, ownsSocket: true); } catch { socket.Dispose(); throw; } } } }