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.
4.7.2 Link-local servers #
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 #
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
TCset 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 isunavailable. - A TCP reply with
TCset is used as it is.