Forcing HttpClient to use IPv4 or IPv6 addresses

 
 
  • Gérald Barré

Recently, I had a problem with an application where connecting to a server using IPv6 was much slower than IPv4 (more than 2 seconds to open the socket). I'm not sure why, but forcing the client to use IPv4 addresses solved the problem. I think the server (localhost in my case) is not listening on IPv6, but I don't know why this is happening on one machine only. Let's see how to force the HttpClient to use IPv4 with .NET.

Starting with .NET 5, you can configure the way the HttpClient creates the socket to communicate with the server by using the SocketsHttpHandler. This means you have full control over the DNS resolution and the IP address selection.

C#
var client = new HttpClient(new SocketsHttpHandler()
{
    ConnectCallback = async (context, cancellationToken) =>
    {
        // Use DNS to look up the IP addresses of the target host:
        // - IP v4: AddressFamily.InterNetwork
        // - IP v6: AddressFamily.InterNetworkV6
        // - IP v4 or IP v6: AddressFamily.Unspecified
        // note: this method throws a SocketException when there is no IP address for the host
        var entry = await Dns.GetHostEntryAsync(context.DnsEndPoint.Host, AddressFamily.InterNetwork, cancellationToken);

        // Open the connection to the target host/port
        var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);

        // Turn off Nagle's algorithm since it degrades performance in most HttpClient scenarios.
        socket.NoDelay = true;

        try
        {
            await socket.ConnectAsync(entry.AddressList, context.DnsEndPoint.Port, cancellationToken);

            // If you want to choose a specific IP address to connect to the server
            // await socket.ConnectAsync(
            //    entry.AddressList[Random.Shared.Next(0, entry.AddressList.Length)],
            //    context.DnsEndPoint.Port, cancellationToken);

            // Return the NetworkStream to the caller
            return new NetworkStream(socket, ownsSocket: true);
        }
        catch
        {
            socket.Dispose();
            throw;
        }
    }
});

var response = await client.GetStringAsync("https://google.com");
Console.WriteLine(response);

Note that there is no callback for QUIC (http/3) connections yet. You can follow this GitHub issue if you are interested in customizing http/3 connections.

Starting with .NET 5, you can also set the environment variable DOTNET_SYSTEM_NET_DISABLEIPV6 to true or the runtime configuration System.Net.DisableIPv6 to disable IPv6 support (documentation).

On Windows, you can also configure the system to prefer IPv4 or IPv6 by setting the registry key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\DisabledComponents (DWORD) with the value:

  • 0x20: Prefer IPv4 over IPv6
  • xx0x xxxx: Prefer IPv6 over IPv4

#Additional resources

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub