4.7 The Upstream Query

PSPU §6.7 sets the upstream behaviour. This article is what resolvd puts on the wire to meet it.

4.7.1 Sockets #

4.7.1.1 One socket per transaction #

Every transaction has its own socket, opened when the transaction is sent and closed when it ends.

4.7.1.2 The UDP socket #

  • A new socket is bound to the unspecified address of the server's family, port 0, so the kernel picks the source address and a fresh ephemeral source port.
  • The socket is connected to the server's port 53, so the kernel delivers only datagrams from that address and port.
  • Each datagram is read into a 4 096-byte buffer; a longer datagram is truncated by the read.

The query is sent with one send.

4.7.1.3 The TCP connection #

A new nonblocking socket is connected to the server's port 53. When the connection completes, the query is written with its two-byte length prefix; once it is all written, the socket is read until the two-byte length and that many bytes of reply have arrived. Bytes after the first message are ignored. One query is sent per connection, and the connection is closed when the transaction ends.

4.7.1.4 A transaction that cannot be sent #

A failure found while a transaction is being sent — opening or binding its socket, connecting it, or, for UDP, sending the query — fails the attempt at once (§4.6) and is logged at warn level as upstream <server>: <error>. A TCP failure found later, once the connection is under way, is not logged, and neither is a timeout or a UDP receive error.

A link-local IPv6 server from netd is reached through the interface named by its DNS scope. Both UDP and TCP upstream sockets carry that interface’s index in sin6_scope_id; a truncated UDP answer keeps it on the TCP retry. An address such as fe80::1 can therefore be advertised in RDNSS without a zone suffix. Failures of that address on one scope do not demote the same address on another scope.

A snapshot may also spell it fe80::1%eth0 or use a numeric interface index. An explicit zone must match the containing scope’s interface; invalid or mismatched zones are logged and ignored. Global addresses continue to use ordinary unscoped sockets.

FallbackServers has no containing interface scope and continues to accept plain IP addresses only: a %zone value is logged and skipped. Configure link-local DNS on the interface profile instead. A bare link-local fallback cannot be sent; it fails as a local address error, without marking a remote server unhealthy.

4.7.3 The message #

FieldValue
ID16 bits from the kernel CSPRNG, fresh for every transaction
FlagsRD set; CD clear; everything else clear
QuestionThe candidate, case-randomised; the record type asked; class IN
AdditionalOne OPT record: payload size 1 232, DO clear, extended response code 0, version 0, no options

Authority and answer sections are empty.

4.7.3.1 Query entropy #

Every transaction obtains 34 fresh bytes through getrandom with GRND_NONBLOCK: two bytes for the message ID, interpreted little-endian, and 32 bytes of case bits, enough for every letter in a DNS name. This uses the kernel CSPRNG without a predictable userspace generator or a clock fallback.

If entropy is unavailable, including before the kernel CSPRNG is ready, resolvd logs the error, increments refused, and settles the affected question as unavailable without sending it. The event loop does not wait for entropy readiness; later requests can try again. Synthetic and cached answers do not need upstream entropy.

4.7.3.2 Case randomisation #

  • Every ASCII letter in the candidate is flipped between upper and lower case when its random bit is 1. Digits, hyphens and other bytes are unchanged.
  • Case bytes follow the ID bytes. Bits are taken lowest first from each byte in order. A bit is used only for a letter.
  • Each transaction — a retry to the same server, and the TCP retry after truncation, included — gets a new pattern and a new ID.

4.7.4 No fallback without EDNS0 #

Every query carries the OPT record. resolvd does not fall back to a query without EDNS0: a server that answers an EDNS0 query with FORMERR has failed the attempt (§4.6).

4.7.5 The TCP retry #

  • A matching UDP reply with TC set is not used. The same candidate is sent to the same server over TCP as a new transaction, with a new ID, a new case pattern and its own two-second deadline, if an attempt remains in the question budget. The TCP transaction spends one attempt (§4.6); without budget the question is unavailable.
  • A TCP reply with TC set is used as it is.

Edit this page