Internet Protocols · DNS

Record Semantics: CNAME Exclusivity and RRset Atomicity

Why a CNAME cannot coexist with other data at the same name, how RRsets are ordered and cached as units, and what an alias does to the query type.

A zone file looks like a list of independent lines, and this is the most consequential lie it tells. Records are not independent. They group into sets that share an owner name, type, and class; those sets have a single lifetime; and some types are mutually exclusive at a name by rule rather than by convention. A zone that treats the file as a flat list will produce answers that individual records do not predict, and a resolver that caches per record rather than per set will serve combinations the authoritative server never held.

The RRset is the unit of everything

The definition is narrow: all records with the same owner name, type, and class form one RRset. Two A records for www.example.org are one RRset of size two. An A and an AAAA for the same name are two RRsets that share an owner.

Three rules follow, and all three are normative rather than advisory. First, every record in an RRset must have the same TTL. RFC 2181 deprecates unequal TTLs and forbids a server from sending a set that carries them; a client that receives such a set from an authoritative source treats the whole set as having the lowest value present, and one that receives it from a non-authoritative source discards the set. Second, the set is cached and served as a whole: a resolver decrements one counter per set, not one per record. Third, the order of records within a set carries no meaning in the protocol. An authoritative server may rotate the order between responses to spread load, and a resolver may reorder freely. Any application that depends on the first address in an A RRset being a particular address has built on a guarantee the protocol does not offer.

That last rule is why clients implement their own preference logic. Happy Eyeballs, for example, does not ask DNS which address is better; it takes the AAAA and A sets and races connections across families. The protocol’s contribution is the two separate sets, which lets the client choose an interleaving strategy without the server having to encode one.

TTL normalisation has a visible consequence for zone maintenance. A client that receives an RRset from an authoritative server with disagreeing TTLs treats the set as carrying the lowest value present, so a set published with mixed TTLs is served and cached at the shortest one; a set with mixed TTLs from a cache is discarded instead. That is the safe direction for the resolver and the surprising one for the operator: editing a single record in a set to a shorter TTL silently shortens the whole set, and editing one to a longer TTL changes nothing. The procedure that behaves predictably is to lower the TTL for the entire set, wait for the old value to expire from caches, make the change, and raise the TTL back afterwards.

Why a CNAME cannot share a name

A CNAME record says the name is an alias and the real data is elsewhere. If the name also carried an A record, the two would disagree about what the name is: one says “the address is this”, the other says “the address is whatever the target has”. The specification resolves the ambiguity by forbidding the coexistence. A CNAME RRset must be the only data at its owner name, with one exception for DNSSEC records that are not answers to ordinary queries.

The rule is enforced at load time by every conforming authoritative server, and it is why a zone file with a CNAME and a TXT at the same name fails to load or loads with a warning and unpredictable behaviour. The prohibition extends past the obvious case. A name cannot have a CNAME and MX, CNAME and NS, or CNAME and SOA. It cannot have a CNAME at the apex of a zone, because the apex must have SOA and NS, so an alias at the zone apex is impossible by construction.

That last constraint is the reason the apex-alias problem exists at all. Operators want example.com itself to point at a CDN or a hosted service, and the natural encoding would be a CNAME, which the protocol forbids. The workarounds are all partial: resolve the target at provisioning time and write A and AAAA records, accepting that the addresses can change without notice; or use a vendor extension that synthesises addresses at answer time.

The protocol does offer a redirection record that operates on whole subtrees, but it does not help here. DNAME, defined in RFC 6672, applies to names beneath its owner, not to the owner itself: a query for a name below a DNAME has its suffix rewritten and resolution restarts for the rewritten name. It has its own exclusivity rule, in that a DNAME cannot coexist with a CNAME at the same name, and no other records may exist at any subdomain of the DNAME owner, but it can sit at the apex of a zone alongside the SOA and NS records there, because the apex NS RRset is the zone’s own rather than a delegation. The response to a query below the DNAME carries the DNAME record and a synthesised CNAME for the queried name, so clients that do not understand DNAME still see a CNAME chain they can follow.

What a CNAME does to the query type

A query for www.example.org A that meets a CNAME at www.example.org does not get an A answer. It gets the CNAME, and then the resolver restarts the query for the target name with the same type. The authoritative server may follow the chain itself if the target is inside its own zone, returning the CNAME and the final answer together. If the target is outside the zone, the server returns only the CNAME and lets the resolver continue.

This is why a CNAME answer is type-independent in a way no other record type is. A query for MX at an aliased name returns the CNAME, not the target’s MX records, unless the server chose to follow the chain. A query for CNAME at the same name returns the CNAME directly. A query for TXT at the same name also returns the CNAME, and the client that asked for TXT has to decide whether to chase it. Some applications do not, and silently treat the answer as “no TXT record”, which is a real failure mode for services that publish verification tokens at aliased names.

The chain-length limit exists because each link restarts resolution. A resolver that follows links without a bound can be made to do arbitrary work by a zone that publishes a cycle. Cycles are invalid, but a resolver cannot detect one until it has followed enough links to see a repeat, so the practical defence is a fixed cap; the value is local policy rather than a protocol constant, and any fixed cap is conforming.

/* Following one CNAME link: the same qtype travels with the new name. */
struct query { char name[256]; uint16_t qtype; uint16_t qclass; };

int follow_cname(struct query *q, const struct rr *cname)
{
    if (strlen(cname->rdata.target) >= sizeof q->name)
        return -1;                     /* target too long for the buffer */
    strcpy(q->name, cname->rdata.target);
    return 0;                          /* qtype and qclass unchanged */
}

The type staying unchanged is the part worth stating explicitly, because the intuitive alternative, looking up whatever the target has, is not what the protocol does. An A query that meets a CNAME continues as an A query for the target.

Why some types cannot be redirected

The restriction is on zone data, not on the query algorithm. A name that carries a CNAME cannot also carry an NS or SOA RRset, so a delegation cannot be expressed through an alias: the NS RRset at a cut is parent-side data, and the parent cannot place a CNAME at the cut point without contradicting the delegation. A resolver that asks for NS or SOA at an aliased name still follows the CNAME, but the target supplies only the records it actually holds; the alias itself never becomes a delegation or a zone apex. An MX target must not be a name with a CNAME, per RFC 2181, because mail transfer agents that follow the alias would be following a pointer whose target can change independently of the mail routing policy.

SRV records carry a target that is also expected to be a real name with address records, for the same reason: the indirection is already in the record, and a second indirection through a CNAME doubles the failure surface. PTR records in the reverse tree follow the same query algorithm: a CNAME at the queried name is chased, and the target’s PTR is returned.

The general principle is that a CNAME encodes “this name is an alias”, and any record type that describes a property of the name itself (a delegation, a zone’s authority, a mail route) cannot be a property of an alias, because the alias is not the entity that owns the property. Types that describe an endpoint reachable at the name, such as A and AAAA, are the ones that make sense to chase.

The canonical form and why it matters later

DNSSEC signs RRsets, and signing a set requires a byte-exact representation of it. That representation is the canonical form: owner names lowercased and fully qualified, no compression, records sorted by their RDATA in canonical order, and each name inside RDATA also lowercased and expanded. RFC 4034 defines the ordering, which compares RDATA octets as if they were left-justified and padded, so that A records sort by address and MX records sort by preference.

The sorting requirement means an RRset has a canonical order even though the wire order is free. A zone signer computes signatures over the sorted form; a validator sorts the set it received before verifying. If a validator skipped the sort, a server that shuffled records between the signing and the serving would produce a verification failure that looks like an attack but is only a reordering.

This is the mechanism behind the rule that TTL is not part of the signed data in the way it appears on the wire. The signature covers the RRset with the TTL replaced by the original TTL from the RRSIG record, so a cached set whose TTL has counted down still verifies. The validator substitutes the RRSIG’s original TTL for the received one and checks that the received TTL is not greater than it. That check is what stops a cache from extending a signature’s validity by inflating a TTL.

The whole structure, sets as atomic units plus exclusive types plus canonical ordering, exists so that a third party can verify a statement it did not receive from the party that made it. DNSSEC Validation and the Chain of Trust builds directly on these rules, and Authenticated Denial of Existence with NSEC and NSEC3 extends them to the harder problem of proving that something is absent.

References

  1. RFC 1034: Domain Names — Concepts and Facilities — IETF
  2. RFC 2181: Clarifications to the DNS Specification — IETF
  3. RFC 4034: Resource Records for the DNS Security Extensions — IETF
  4. RFC 6672: DNAME Redirection in the DNS — IETF