Limits and hardening

edit

Options

All the limits are properties of DnsServerOptions:

builder.AddDnsServer(options =>
{
    options.AddUdpListener(port: 5053);

    // Largest UDP response, in bytes. A client's advertised EDNS payload size is clamped to this
    // value and larger answers are truncated so the client retries over TCP. The 1232-byte default
    // comes from DNS Flag Day 2020 and also bounds how much a spoofed query can amplify.
    options.MaxUdpResponseSize = 1232;

    // Closes TCP and DNS over TLS connections that go this long without a complete query
    // (RFC 7766 6.2.3). Use Timeout.InfiniteTimeSpan to disable.
    options.TcpIdleTimeout = TimeSpan.FromSeconds(30);

    // Idle timeout for DNS over QUIC connections.
    options.QuicIdleTimeout = TimeSpan.FromSeconds(30);

    // How many queries one connection may have in flight at once.
    options.MaxConcurrentQueriesPerConnection = 16;
});
OptionDefaultEffect
MaxUdpResponseSize1232 bytesThe largest UDP response. Between 512 and 65535.
TcpIdleTimeout30 secondsCloses a TCP or DNS over TLS connection that sends no complete query for that long. Timeout.InfiniteTimeSpan disables it.
QuicIdleTimeout30 secondsCloses an idle DNS over QUIC connection.
MaxConcurrentQueriesPerConnection16How many queries a TCP, TLS or QUIC connection can have in progress at once. Further queries wait.

To cap the number of connections, set the limit of Kestrel, which also covers the HTTP endpoints of the application:

builder.WebHost.ConfigureKestrel(kestrel => kestrel.Limits.MaxConcurrentConnections = 1000);

Response size

A UDP response is limited to the payload size the client advertises in its EDNS record, clamped between 512 bytes and MaxUdpResponseSize; without EDNS, 512 bytes. A larger response is truncated, with the TC flag set, so the client asks again over TCP. Keeping MaxUdpResponseSize small also limits how much traffic a spoofed query can make the server send to its victim.

TCP, TLS, QUIC and HTTPS responses can be up to 65535 bytes, the limit of a DNS message; a larger response is truncated too.

What the server rejects

Before the handler runs, the server:

  • drops messages that have the QR flag set, which are responses rather than queries (RFC 5625), so two servers cannot be made to answer each other indefinitely;
  • answers a message it cannot parse with FormError (HTTP 400 for DNS over HTTPS), so the client does not wait for a timeout;
  • answers an EDNS version other than 0 with BadVersion (RFC 6891);
  • limits decompressed names to 255 bytes and requires compression pointers to point backwards, so a crafted message cannot expand without limit;
  • for DNS over HTTPS, rejects bodies larger than a DNS message before reading them.

The server does not limit the number of queries per client. A public server needs rate limiting: in the handler, by context.RemoteEndPoint, as Meziantou.DnsProxy does, or in front of the server.