Hello,

I would like to get feedback on adding normal UDP-to-TCP fallback to
HAProxy's DNS resolver.

Current behavior
----------------

An unprefixed IPv4 or IPv6 nameserver currently uses UDP only:

    resolvers mydns
        nameserver dns 192.0.2.53:53

When a valid response has TC=1, HAProxy does not retry the query over
TCP. A common minimal truncated response has TC=1 and ANCOUNT=0, and is
currently classified as a zero-answer response before truncation can be
handled.

An operator can configure "tcp@" to send every query over TCP, but there
is no UDP-first mode with TCP fallback.

Standards background
--------------------

RFC 1123 section 6.1.3.2 recommends retrying a truncated answer over
TCP when the requester supports it:

    https://www.rfc-editor.org/rfc/rfc1123.html#section-6.1.3.2

RFC 2181 section 9 says a client receiving TC should ignore the partial
response and query again using a mechanism capable of larger replies,
such as TCP:

    https://www.rfc-editor.org/rfc/rfc2181.html#section-9

RFC 7766 treats TC as an indication that the client should retry over
TCP, while RFC 9210 requires DNS clients to support TCP so that
truncated UDP responses can be retried when necessary:

    https://www.rfc-editor.org/rfc/rfc7766.html#section-4
    https://www.rfc-editor.org/rfc/rfc9210.html#section-3

So TCP support is a MUST, while retrying a particular truncated response is 
generally described as a SHOULD.

Proposed behavior
-----------------

I propose using the existing address-prefix syntax to expose three
transport policies:

    nameserver dns 192.0.2.53:53
        UDP first, then TCP against the same address and port on TC=1.

    nameserver dns [email protected]:53
        UDP only.

    nameserver dns [email protected]:53
        TCP only.

The udp4@, udp6@, and dgram+ forms would also remain UDP-only.
The same UDP-first policy would apply to an unprefixed hostname that
resolves to an IPv4 or IPv6 address.
Nameservers imported through "parse-resolv-conf" would use the normal
UDP-first behavior.

The TCP connection would be created lazily, only after a truncated UDP
response. It would reuse the existing DNS stream connection pooling,
pipelining, connection timeout, and idle cleanup.

The normal response path would first match the query ID to an active
resolution and validate the returned question. Only then would HAProxy
act on TC=1. The same query ID, type, nameserver, address, and port would
be used over TCP. Starting fallback would not consume a resolver retry
or trigger A/AAAA query-type fallback.

If TCP fallback cannot connect or complete, the existing resolver timeout
and retry handling would apply. A truncated response received during TCP
fallback would not cause another fallback. Existing explicit "tcp@"
behavior would remain unchanged.

Explicit UDP-only nameservers would preserve the current truncated SRV
handling. With normal UDP-first nameservers, a truncated SRV response
would be discarded and retried over TCP as recommended by RFC 2181.

A "tcp_fallback" counter would be exposed through "show resolvers",
resolver statistics, and the Prometheus resolver metrics.

Response size
-------------

The existing "accepted_payload_size" setting is both the EDNS UDP size
advertisement and the maximum accepted response size. Its default is
512 bytes.

I propose using "accepted_payload_size" only as the UDP/EDNS
advertisement and UDP receive limit. Responses received over TCP would
be accepted up to the DNS protocol maximum of 65535 bytes. Otherwise,
the complete response which caused UDP truncation may still be rejected
after fallback, especially with the default 512-byte setting.

Other questions
---------------

Does the default UDP-first behavior, with "udp@" as an explicit opt-out,
seem preferable to introducing a new opt-in keyword?

Should "parse-resolv-conf" nameservers use the same default behavior?

Is retaining the existing partial truncated SRV handling for nameservers
where UDP-to-TCP fallback is disabled the right compatibility boundary?

I have a local prototype and a VTest fixture covering complete UDP
answers, truncated A and SRV responses, UDP-only and TCP-only modes,
unreachable TCP endpoints, and the resolver counters. I have not
attached the patch so that the configuration and compatibility choices
can be discussed first.

Thanks,
Manu

Reply via email to