6.1 Receiving Queries

The stub listener's address, its loopback-only rule and its rendering are PSPU §6.8. This article covers how resolvd reads queries; the replies are §6.2.

6.1.1 UDP #

When the UDP socket is readable, at most 64 datagrams are read per event-loop iteration, including malformed or dropped datagrams. For each:

  1. A datagram whose source address is not a loopback address — anything outside 127.0.0.0/8 — is dropped without a reply.
  2. It is checked as a query (below). A refused query gets its error reply, or nothing.
  3. A valid query is admitted if fewer than 16 UDP questions from its source address and port, and fewer than 256 UDP questions in total, are waiting for engine answers. Otherwise it gets SERVFAIL, with no upstream transaction, and increments both queries and refused. This admission check precedes synthetic and cache lookup.
  4. An admitted query becomes an engine question (§4.1). Its slot is released when the engine completes it, including timeout completion.

The endpoint is an accounting key, not an authenticated process identity. Changing source ports can consume the aggregate UDP allowance but cannot consume the separate native or TCP admission allowances. These bounds limit retained work; they do not promise fairness between local processes.

6.1.1.1 The UDP read buffer #

Each datagram is read into a 4 096-byte buffer; a longer datagram is truncated by the read to its first 4 096 bytes.

6.1.1.2 A UDP query longer than the buffer #

A query longer than 4 096 bytes therefore arrives cut short. When the cut falls inside the DNS message, the message no longer decodes and, being at least 12 bytes long, is answered FORMERR (below). Bytes after the end of the message are ignored by decoding, so a query whose message fits in 4 096 bytes and is only padded beyond them is answered as usual.

6.1.1.3 Where UDP replies go #

Replies are sent to the source address and port the query came from.

6.1.2 TCP #

When the TCP listener is readable, at most 64 waiting connections are accepted per event-loop iteration. A connection is closed at once, without a reply:

  • when 256 stub TCP connections are still sending queries or waiting for engine answers;
  • when its source is not a loopback address.

A connection that cannot be made nonblocking is closed the same way.

6.1.2.1 Reading a TCP query #

Each held connection is read as it becomes readable, everything available each time, in 4 096-byte chunks. A message is a two-byte big-endian length and that many bytes.

  • When more than 65 537 bytes are buffered, the connection is closed without a reply.
  • When a whole message has arrived, it is checked as a query, and the connection leaves the set of connections still sending, but continues to occupy its admission slot while the engine works.
  • A connection carries one query. Anything sent after the first message is ignored, and the connection is closed once the answer, or the error reply, has been written.
  • A connection that closes, or fails to read, before its message is whole is closed without a reply.
  • A client that sends a complete query and then shuts down its writing side is still answered. This also applies when the read finds end of file immediately after the buffered query. The connection closes after its one reply. A partial frame followed by end of file is dropped immediately, without waiting for the delivery timeout.
  • A connection that has not delivered a whole message ten seconds after it was accepted is closed without a reply.
  • Once its query has arrived the ten-second bound no longer applies; the connection is held until the engine answers or a definite hangup/error abandons its question (§4.1). Reply writers use a separate bounded queue (§2.5).

6.1.3 Checking a query #

The messageResult
Does not decode, and is at least 12 bytes longFORMERR: the query's ID, QR set, every other header bit clear, no sections
Does not decode, and is shorter than 12 bytesDropped without a reply
Has QR setDropped without a reply
Has an opcode other than QUERYNOTIMP
Has no question, or more than oneFORMERR
Has a question class other than INNOTIMP; the question is echoed unchanged and no upstream query is made
OtherwiseAccepted

6.1.3.1 The NOTIMP and question-count FORMERR replies #

These two replies are built from the query:

  • the header copies the query's ID, opcode, RD and CD bits, and sets QR and RA; every other bit is clear;
  • the question section is the query's, whatever it holds;
  • when the query carried an OPT record, the reply carries one whose payload size is the size the query advertised, with DO clear and no options; without one, the reply has none.

6.1.4 What an accepted query contributes #

  • Only IN questions are accepted. Their decoded name and type reach the engine; unsupported classes are rejected at the stub door over both UDP and TCP.
  • RD, CD, the DO bit and any EDNS options are not looked at.
  • Stub queries always consult the cache (§4.5).

6.1.4.1 Wire names #

The decoded name reaches the engine directly, without conversion to presentation text and reparsing. Label boundaries and bytes, including embedded dots, backslashes, NUL and other binary bytes, are preserved. The usual upstream ASCII case randomization still applies (§4.3).

Names are subject to DNS wire limits, not the length of a displayed escape sequence. A valid 63-byte binary label or a valid name whose display form would exceed 255 bytes can therefore be resolved normally.

Search expansion (§4.1) uses the number of wire labels: a single label containing a literal dot remains a single-label name.

6.1.5 Which stub queries are counted #

Every valid query counts in queries (§4.10), including a UDP query refused by the pending budgets (which also increments refused). Queries rejected by syntax, opcode, question count or class checks do not count.

Edit this page