Creating an HttpClient that uses DNS over Https

 
 
  • Gérald Barré

DNS is a key component of the Internet, translating domain names to IP addresses. For instance, when you type https://www.meziantou.net in your browser, it queries the DNS server to get the IP address of the server hosting the website, then connects to that server using the resolved address.

A good practice is to rely on the OS configuration for DNS queries. However, you may sometimes want to use a specific DNS server to bypass configuration issues. To reliably avoid DNS configuration or firewall restrictions, you can use DNS over HTTPS (DoH). This post explains how to use DoH in .NET.

To get started, add the NuGet package TurnerSoftware.DinoDNS to your project. This package provides a DNS client that can query DNS servers using UDP, TCP, DoT, or DoH. You can then use SocketsHttpHandler.ConnectCallback to open the socket manually: resolve the hostname using the DNS client, then open the socket using the returned IP address.

Let's create the project and add the package:

Shell
dotnet new console
dotnet add package TurnerSoftware.DinoDNS

Then, you can use the following code to create the HttpClient:

C#
using System.Net;
using System.Net.Sockets;
using TurnerSoftware.DinoDNS;
using TurnerSoftware.DinoDNS.Protocol;

// Initialize the DNS client to use Cloudflare on DNS over Https.
// You can specify more than one server if needed.
var dnsClient = new DnsClient([NameServers.Cloudflare.IPv4.GetPrimary(ConnectionType.DoH)], DnsMessageOptions.Default);
var sockerHttpHandler = new SocketsHttpHandler()
{
    ConnectCallback = async (context, cancellationToken) =>
    {
        // Query DNS and get the list of IPAddress
        var dnsMessage = await dnsClient.QueryAsync(context.DnsEndPoint.Host, DnsQueryType.A, cancellationToken: cancellationToken);
        var records = dnsMessage.Answers;
        if (records.Count is 0)
            throw new Exception($"Cannot resolve domain '{context.DnsEndPoint.Host}'");

        var addresses = new List<IPAddress>();
        foreach (var record in records)
        {
            if (record.Type is DnsType.A or DnsType.AAAA)
            {
                addresses.Add(new IPAddress(record.Data.Span));
            }
        }

        // Connect to the remote host
        var socket = new Socket(SocketType.Stream, ProtocolType.Tcp)
        {
            // Turn off Nagle's algorithm since it degrades performance in most HttpClient scenarios.
            NoDelay = true
        };

        try
        {
            await socket.ConnectAsync(addresses.ToArray(), context.DnsEndPoint.Port, cancellationToken);
            return new NetworkStream(socket, ownsSocket: true);
        }
        catch
        {
            socket.Dispose();
            throw;
        }
    }
};

var httpClient = new HttpClient(sockerHttpHandler, disposeHandler: true);

Finally, you can use the HttpClient as usual:

C#
Console.WriteLine(await httpClient.GetStringAsync("https://www.meziantou.net"));

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

Follow me:
Enjoy this blog?