5

Coming from node.js I can do this to tell node.js to make the request using ipv6 vs ipv4

var http = require("http");
var options = {
  hostname: "google.com",
  family: 4, // set to 6 for ipv6
};
var req = http.request(options, function(res) {
  .. handle result here ..
});
req.write("");
req.end();

Setting family to 4 forces ipv4, setting it to 6 forces ipv6. Not setting it lets either work.

How can I do the same thing in C# (.NET 3.5)

I can think of one way which is to make a DNS request myself myself for the A or AAAA records, make a direct IP request and set the host: header. Is there a better way?

gman
  • 100,619
  • 31
  • 269
  • 393

3 Answers3

6

You can use ServicePoint.BindIPEndPointDelegate.

var req = HttpWebRequest.Create(url) as HttpWebRequest;

req.ServicePoint.BindIPEndPointDelegate = (servicePoint, remoteEndPoint, retryCount) =>
{
    if (remoteEndPoint.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
    {
        return new IPEndPoint(IPAddress.IPv6Any, 0);
    }

    throw new InvalidOperationException("no IPv6 address");
};
atupal
  • 16,404
  • 5
  • 31
  • 42
Cheng Chen
  • 42,509
  • 16
  • 113
  • 174
5

A few years later, a .NET 5 answer:

Assuming you're using a HttpClient, then you can set a ConnectCallback on the SocketsHttpHandler thus:

private static readonly HttpClient _http = new HttpClient(new SocketsHttpHandler() {
    ConnectCallback = async (context, cancellationToken) => {
        // Use DNS to look up the IP address(es) of the target host
        IPHostEntry ipHostEntry = await Dns.GetHostEntryAsync(context.DnsEndPoint.Host);

        // Filter for IPv4 addresses only
        IPAddress ipAddress = ipHostEntry
            .AddressList
            .FirstOrDefault(i => i.AddressFamily == AddressFamily.InterNetwork);

        // Fail the connection if there aren't any IPV4 addresses
        if (ipAddress == null) {
            throw new Exception($"No IP4 address for {context.DnsEndPoint.Host}");
        }

        // Open the connection to the target host/port
        TcpClient tcp = new();
        await tcp.ConnectAsync(ipAddress, context.DnsEndPoint.Port, cancellationToken);

        // Return the NetworkStream to the caller
        return tcp.GetStream();
    }),
});

(This is setup for IPv4 only, to set for Ipv6 only change AddressFamiliy.InterNetwork to AddressFamily.InterNetworkV6)

Moose Morals
  • 1,628
  • 25
  • 32
  • 2
    Thanks ! This solved my problem. Using "localhost" instead of "127.0.0.1" caused 2 secs of latency sometimes. Also see this part of solution here https://stackoverflow.com/a/71641927/1529139 – 56ka Jun 17 '22 at 07:37
2

Here is my solution based on @Moose Morals, with a little optimization on caching the IP which makes a really little gain (1ms on my professional PC but it might be higher on yours)

(search IP only when host changed)

I shortened a little bit but it works the same

public class ResolveDnsOptimization
{
    public static void ApplyTo(SocketsHttpHandler handler)
    {
        CachedAddress cachedAddress = null;

        // Remove the latencies when using host name over IP address
        // Changing pool connection lifetime and forcing to open them all does not work, the DNS resolution is always done.
        // Source: https://stackoverflow.com/a/70475741/1529139
        handler.ConnectCallback = async (context, cancellationToken) =>
            {
                if (cachedAddress == null || cachedAddress.Host != context.DnsEndPoint.Host)
                {
                    // Use DNS to look up the IP address(es) of the target host and filter for IPv4 addresses only
                    IPHostEntry ipHostEntry = await Dns.GetHostEntryAsync(context.DnsEndPoint.Host);
                    IPAddress ipAddress = ipHostEntry.AddressList.FirstOrDefault(i => i.AddressFamily == AddressFamily.InterNetwork);
                    if (ipAddress == null)
                    {
                        cachedAddress = null;
                        throw new Exception($"No IP4 address for {context.DnsEndPoint.Host}");
                    }
                    cachedAddress = new CachedAddress() { Ip = ipAddress, Host = context.DnsEndPoint.Host };
                }

                TcpClient tcp = new();
                await tcp.ConnectAsync(cachedAddress.Ip, context.DnsEndPoint.Port, cancellationToken);
                return tcp.GetStream();
            };
    }

    private class CachedAddress
    {
        public IPAddress Ip;
        public string Host;
    }
}

Usage :

SocketsHttpHandler handler = new SocketsHttpHandler();
ResolveDnsOptimization.ApplyTo(handler); // <- here
HttpClient client = new HttpClient(handler)
56ka
  • 1,463
  • 1
  • 21
  • 37