Internet Protocols · DNS
EDNS(0) and the Truncation Boundary
How the OPT pseudo-record extends the header, how the advertised UDP payload size interacts with the 512-byte default, and why truncation is not a partial answer.
The original protocol assumed a 512-octet UDP payload and left itself no way to negotiate anything larger. The header has no field for a per-query option, and its response code is four bits. EDNS(0) addressed all of it by appending a record to the additional section: a record that is not data, is not signed, and is not cached, but which carries the parameters the header had no room for.
A record that is not a record
The OPT pseudo-record appears in the additional section of a query or response. Its owner name is the root (a single zero octet), its type is 41, and its class field does not mean class at all: it holds the requestor’s UDP payload size in octets. Its TTL field is likewise repurposed: the high octet holds the upper eight bits of the extended response code, the next octet holds the EDNS version, and the low sixteen bits hold flags. Only the RDATA carries actual options, as a sequence of code-length-value triples.
| Header field | Normal meaning | Meaning in OPT |
|---|---|---|
| Owner name | record owner | always root, zero octet |
| TYPE | record type | 41 |
| CLASS | class | requestor’s UDP payload size |
| TTL | time to live | extended RCODE, version, flags |
| RDLEN/RDATA | data length and data | options, code-length-value |
Because the payload size lives in a 16-bit field, the advertised value can be large, but the practical limit is set by path MTU and by the resolver’s own buffers. A common advertisement is 1232 octets, which is 1280 (the IPv6 minimum MTU) minus the 40-octet IPv6 header and the 8-octet UDP header, leaving room for a full-size datagram without fragmentation. Advertising a larger value risks IP fragmentation, which loses the whole datagram if any fragment is dropped, so the larger number does not reliably buy anything.
The extended response code is twelve bits wide, formed from the eight high bits in the OPT TTL and the four low bits in the header. That is how BADVERS (16), BADCOOKIE (23), and the DNSSEC-related codes fit. A server that needs to return one of these must include an OPT record in its response, because without it the high bits have nowhere to live; a client that receives a response with no OPT sees only the four-bit code, so BADVERS is indistinguishable from NOERROR.
The version field is the next eight bits of the OPT TTL, and it is zero for EDNS(0). A server that receives a higher version must respond with BADVERS and must include an OPT record stating the highest version it supports, which is the negotiation mechanism. There has been no EDNS(1), so the field is a reserved escape hatch that has not been used, and a response carrying a non-zero version should be treated as an error rather than as a future feature.
Truncation is a signal, not an answer
When a response does not fit in the space available, the server sets the TC bit and returns as much as fits. The client must discard the response and retry over TCP. The critical rule is that a truncated response must not be cached, must not be used to answer anything, and must not be partially merged with a later response. The reason is that the truncated message may contain a partial RRset, meaning some of the addresses rather than all of them, and an RRset is atomic.
This rule is violated by implementations that treat TC as “the answer is here, there is just more of it elsewhere”. There is no “elsewhere”: the retry is a fresh query over a different transport, and the response to it is a complete message.
Truncation caused by additional-section overflow is a separate question. RFC 2181 says the TC bit should not be set merely because some additional record did not fit: the server omits that record and sends the reply with TC clear. RFC 9471 carves out one exception: in a referral, the glue for name servers at or below the delegated name is required, so a server that cannot fit all of it sets TC instead of returning an incomplete referral. Outside that case, setting TC over additional-section overflow forces a TCP round trip the client did not need.
/* Build a UDP response: drop optional glue, signal TC if the answer will not fit. */
static size_t build(uint8_t *buf, size_t cap, struct msg *m)
{
/* Sizes first: the header counts must match what is actually written. */
size_t q = 12 + question_size(m);
size_t base = q + rrset_size(m->answer) + rrset_size(m->authority);
size_t add = rrset_size(m->additional);
if (base > cap) { /* the answer itself overflows */
size_t n = write_header(buf, m, 1, 0, 0, 0);
return n + write_question(buf + n, m); /* question only; TC says retry */
}
int emit_add = base + add <= cap; /* optional additional data */
size_t n = write_header(buf, m, 0,
m->ancount, m->nscount,
emit_add ? m->arcount : 0);
n += write_question(buf + n, m);
n += write_rrs(buf + n, m->answer);
n += write_rrs(buf + n, m->authority);
if (emit_add)
n += write_rrs(buf + n, m->additional);
return n;
}
The asymmetry in that snippet is the whole policy: the answer and authority sections are what the response means, and the additional section is an optimisation, apart from the in-domain glue that RFC 9471 requires. Omitting optional glue costs the client an extra lookup at most; emitting part of an answer would cost it correctness, so that case becomes TC with the question alone.
The 512-byte floor and why it still matters
Without an OPT record, a response must fit in 512 octets. That is not a legacy detail: a server that receives a query with no OPT must assume the old limit, and a client that receives a response with no OPT must be prepared for truncation. The fallback path is therefore always present, and any resolver that implements EDNS must also implement the non-EDNS path correctly, because a middlebox can strip the OPT record from a query and leave the resolver talking to a server that never sees it.
Middlebox interference is the practical argument for keeping the old limit working. A firewall that drops UDP responses over 512 octets, or that strips records it does not recognise, turns a large EDNS response into a black hole. Resolvers respond by retrying without EDNS, and a resolver that never retries without EDNS will fail against such a network while working everywhere else. The classic symptom is a name that resolves from one network and not another, with no error visible at either end.
TCP removes the size problem entirely, because a TCP message carries a two-octet length prefix and can be up to 65535 octets. RFC 7766 requires authoritative servers and resolvers to support TCP, and it recommends keeping connections open and reusing them rather than opening one per query. Connection reuse changes the cost model: a TCP handshake is one round trip, and a query on an established connection is one round trip, so the first query over a new connection costs two and subsequent ones cost one.
The port question is separate and occasionally confusing. DNS over TCP and UDP uses port 53 in both cases; RFC 7766 states this explicitly, so no separate port is allocated for TCP. Some deployments block TCP/53 on the assumption that DNS is a UDP protocol, which turns every truncated response into a failed lookup, and that is a common reason a large answer works on one network and not another.
Cookies and the state they carry
An EDNS option with code 10 carries a client cookie: eight octets chosen by the client, echoed by the server with an additional server cookie appended. The purpose is to let a server verify that a query comes from the address it claims, without maintaining per-query state. The client cookie is stable for a period, and the server cookie is derived from the client’s address and a server secret, so a spoofed query with a guessed client cookie gets a response the attacker cannot use to amplify.
The cookie option lets a server decide whether an unvalidated address is worth a large response. Without it, a server that answers a spoofed query contributes to a reflection attack: the query is small, the response can be much larger, and the amplification factor is the ratio. With a cookie, the server can require a valid cookie before sending a large response, which removes the amplification.
The trade is state and complexity. The server must be able to rotate its secret, must handle clients that do not support cookies, and must decide how to respond to a query with an invalid cookie. RFC 7873 allows it to discard the query, return a small BADCOOKIE error carrying a new server cookie, or process the request normally. A client that receives BADCOOKIE retries with the new cookie, and repeated failures can prompt a retry over TCP. The error response is not an answer to the original question and must not be cached as one.
What EDNS did not fix
EDNS(0) extended the response code space and the payload size, but it did not add a way to signal which options a server understands beyond the version field, and it did not add a way to negotiate record types. A server that does not understand an option ignores it, which is the correct default for optional data but means a client cannot tell “ignored” from “not seen”. The COOKIE and NSID options work around this by having observable effects, but the general problem remains.
The other thing it did not fix is the response code space running out again. The twelve-bit extended space is larger than four bits but still finite, and the mechanism for adding more would require another pseudo-record with the same trick, which no specification has defined. The practical result is that new error conditions are usually expressed as a specific EDNS option rather than as a new response code.
The format also did not gain a way to carry the query’s own parameters to the authoritative server through a recursive resolver, beyond what the resolver chooses to forward. The resolver is free to strip options it does not understand, and the client has no way to require forwarding. That is a deliberate design choice: the resolver is the entity that decides policy, and options that affect policy belong to it rather than to the stub. The privacy consequences of that arrangement are the subject of Encrypted DNS Transports and Their Trust Boundaries, and the size consequences feed directly into Authenticated Denial of Existence with NSEC and NSEC3, where signed negative answers routinely exceed what a 512-octet response can carry.