Subject: [DNSOP] CNAME chain length limits

From: "John R Levine" <[email-masked]>
Date: 27 May 2020 13:48:32 -0400

While I should have been doing something else, I made a rather long CNAME 
chain.  When I looked up chain.examp1e.com it got SERVFAIL, but after I 
warmed up my cache five links at a time by looking for chain5, chain10, 
chain15, and so forth, it worked.  At least it worked in "dig" and "host". 
When I try and look up http://chain.examp1e.com, Chrome waits a while 
and says not found, Firefox waits a while and says "Hmm. We’re having 
trouble finding that site." and Safari on my Mac hangs.  (Feel free to try 
it yourself.)

I realize the answer to most questions like this can be summarized as 
"don't do that", but is there any consensus as to the maximum CNAME chain 
length that works reliably, and what happens if the chain is too long? 
Hanging seems sub-optimal.

Regards,
John Levine, [email-masked], Taughannock Networks, Trumansburg NY
Please consider the environment before reading this e-mail. https://jl.ly

$ dig chain.examp1e.com A
;; Truncated, retrying in TCP mode.

; <<>> DiG 9.10.6 <<>> chain.examp1e.com a
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 59001
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 102, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;chain.examp1e.com.		IN	A

;; ANSWER SECTION:
chain.examp1e.com.	3371	IN	CNAME	chain100.examp1e.com.
chain100.examp1e.com.	3371	IN	CNAME	chain99.examp1e.com.
chain99.examp1e.com.	3371	IN	CNAME	chain98.examp1e.com.
chain98.examp1e.com.	3371	IN	CNAME	chain97.examp1e.com.
chain97.examp1e.com.	3371	IN	CNAME	chain96.examp1e.com.
chain96.examp1e.com.	3372	IN	CNAME	chain95.examp1e.com.
chain95.examp1e.com.	3372	IN	CNAME	chain94.examp1e.com.
chain94.examp1e.com.	3372	IN	CNAME	chain93.examp1e.com.
chain93.examp1e.com.	3372	IN	CNAME	chain92.examp1e.com.
chain92.examp1e.com.	3589	IN	CNAME	chain91.examp1e.com.
chain91.examp1e.com.	3589	IN	CNAME	chain90.examp1e.com.
chain90.examp1e.com.	3583	IN	CNAME	chain89.examp1e.com.
chain89.examp1e.com.	3583	IN	CNAME	chain88.examp1e.com.
chain88.examp1e.com.	3583	IN	CNAME	chain87.examp1e.com.
chain87.examp1e.com.	3583	IN	CNAME	chain86.examp1e.com.
chain86.examp1e.com.	3583	IN	CNAME	chain85.examp1e.com.
chain85.examp1e.com.	3577	IN	CNAME	chain84.examp1e.com.
chain84.examp1e.com.	3578	IN	CNAME	chain83.examp1e.com.
chain83.examp1e.com.	3578	IN	CNAME	chain82.examp1e.com.
chain82.examp1e.com.	3578	IN	CNAME	chain81.examp1e.com.
chain81.examp1e.com.	3579	IN	CNAME	chain80.examp1e.com.
chain80.examp1e.com.	3570	IN	CNAME	chain79.examp1e.com.
chain79.examp1e.com.	3571	IN	CNAME	chain78.examp1e.com.
chain78.examp1e.com.	3571	IN	CNAME	chain77.examp1e.com.
chain77.examp1e.com.	3571	IN	CNAME	chain76.examp1e.com.
chain76.examp1e.com.	3572	IN	CNAME	chain75.examp1e.com.
chain75.examp1e.com.	3564	IN	CNAME	chain74.examp1e.com.
chain74.examp1e.com.	3564	IN	CNAME	chain73.examp1e.com.
chain73.examp1e.com.	3564	IN	CNAME	chain72.examp1e.com.
chain72.examp1e.com.	3564	IN	CNAME	chain71.examp1e.com.
chain71.examp1e.com.	3564	IN	CNAME	chain70.examp1e.com.
chain70.examp1e.com.	3519	IN	CNAME	chain69.examp1e.com.
chain69.examp1e.com.	3519	IN	CNAME	chain68.examp1e.com.
chain68.examp1e.com.	3519	IN	CNAME	chain67.examp1e.com.
chain67.examp1e.com.	3519	IN	CNAME	chain66.examp1e.com.
chain66.examp1e.com.	3519	IN	CNAME	chain65.examp1e.com.
chain65.examp1e.com.	3519	IN	CNAME	chain64.examp1e.com.
chain64.examp1e.com.	3520	IN	CNAME	chain63.examp1e.com.
chain63.examp1e.com.	3520	IN	CNAME	chain62.examp1e.com.
chain62.examp1e.com.	3520	IN	CNAME	chain61.examp1e.com.
chain61.examp1e.com.	3554	IN	CNAME	chain60.examp1e.com.
chain60.examp1e.com.	3549	IN	CNAME	chain59.examp1e.com.
chain59.examp1e.com.	3549	IN	CNAME	chain58.examp1e.com.
chain58.examp1e.com.	3549	IN	CNAME	chain57.examp1e.com.
chain57.examp1e.com.	3549	IN	CNAME	chain56.examp1e.com.
chain56.examp1e.com.	3549	IN	CNAME	chain55.examp1e.com.
chain55.examp1e.com.	3535	IN	CNAME	chain54.examp1e.com.
chain54.examp1e.com.	3536	IN	CNAME	chain53.examp1e.com.
chain53.examp1e.com.	3536	IN	CNAME	chain52.examp1e.com.
chain52.examp1e.com.	3536	IN	CNAME	chain51.examp1e.com.
chain51.examp1e.com.	3536	IN	CNAME	chain50.examp1e.com.
chain50.examp1e.com.	3536	IN	CNAME	chain49.examp1e.com.
chain49.examp1e.com.	3536	IN	CNAME	chain48.examp1e.com.
chain48.examp1e.com.	3536	IN	CNAME	chain47.examp1e.com.
chain47.examp1e.com.	3536	IN	CNAME	chain46.examp1e.com.
chain46.examp1e.com.	3541	IN	CNAME	chain45.examp1e.com.
chain45.examp1e.com.	3531	IN	CNAME	chain44.examp1e.com.
chain44.examp1e.com.	3531	IN	CNAME	chain43.examp1e.com.
chain43.examp1e.com.	3531	IN	CNAME	chain42.examp1e.com.
chain42.examp1e.com.	3531	IN	CNAME	chain41.examp1e.com.
chain41.examp1e.com.	3531	IN	CNAME	chain40.examp1e.com.
chain40.examp1e.com.	3525	IN	CNAME	chain39.examp1e.com.
chain39.examp1e.com.	3526	IN	CNAME	chain38.examp1e.com.
chain38.examp1e.com.	3526	IN	CNAME	chain37.examp1e.com.
chain37.examp1e.com.	3526	IN	CNAME	chain36.examp1e.com.
chain36.examp1e.com.	3526	IN	CNAME	chain35.examp1e.com.
chain35.examp1e.com.	3513	IN	CNAME	chain34.examp1e.com.
chain34.examp1e.com.	3513	IN	CNAME	chain33.examp1e.com.
chain33.examp1e.com.	3513	IN	CNAME	chain32.examp1e.com.
chain32.examp1e.com.	3513	IN	CNAME	chain31.examp1e.com.
chain31.examp1e.com.	3513	IN	CNAME	chain30.examp1e.com.
chain30.examp1e.com.	3508	IN	CNAME	chain29.examp1e.com.
chain29.examp1e.com.	3508	IN	CNAME	chain28.examp1e.com.
chain28.examp1e.com.	3508	IN	CNAME	chain27.examp1e.com.
chain27.examp1e.com.	3508	IN	CNAME	chain26.examp1e.com.
chain26.examp1e.com.	3508	IN	CNAME	chain25.examp1e.com.
chain25.examp1e.com.	3499	IN	CNAME	chain24.examp1e.com.
chain24.examp1e.com.	3499	IN	CNAME	chain23.examp1e.com.
chain23.examp1e.com.	3500	IN	CNAME	chain22.examp1e.com.
chain22.examp1e.com.	3500	IN	CNAME	chain21.examp1e.com.
chain21.examp1e.com.	3500	IN	CNAME	chain20.examp1e.com.
chain20.examp1e.com.	3447	IN	CNAME	chain19.examp1e.com.
chain19.examp1e.com.	3447	IN	CNAME	chain18.examp1e.com.
chain18.examp1e.com.	3447	IN	CNAME	chain17.examp1e.com.
chain17.examp1e.com.	3448	IN	CNAME	chain16.examp1e.com.
chain16.examp1e.com.	3448	IN	CNAME	chain15.examp1e.com.
chain15.examp1e.com.	3448	IN	CNAME	chain14.examp1e.com.
chain14.examp1e.com.	3448	IN	CNAME	chain13.examp1e.com.
chain13.examp1e.com.	3448	IN	CNAME	chain12.examp1e.com.
chain12.examp1e.com.	3449	IN	CNAME	chain11.examp1e.com.
chain11.examp1e.com.	3486	IN	CNAME	chain10.examp1e.com.
chain10.examp1e.com.	3455	IN	CNAME	chain9.examp1e.com.
chain9.examp1e.com.	3455	IN	CNAME	chain8.examp1e.com.
chain8.examp1e.com.	3455	IN	CNAME	chain7.examp1e.com.
chain7.examp1e.com.	3455	IN	CNAME	chain6.examp1e.com.
chain6.examp1e.com.	3455	IN	CNAME	chain5.examp1e.com.
chain5.examp1e.com.	3455	IN	CNAME	chain4.examp1e.com.
chain4.examp1e.com.	3455	IN	CNAME	chain3.examp1e.com.
chain3.examp1e.com.	3455	IN	CNAME	chain2.examp1e.com.
chain2.examp1e.com.	3455	IN	CNAME	chain1.examp1e.com.
chain1.examp1e.com.	3466	IN	CNAME	chain0.examp1e.com.
chain0.examp1e.com.	3460	IN	A	64.57.183.119

;; Query time: 2 msec
;; SERVER: 192.168.80.2#53(192.168.80.2)
;; WHEN: Wed May 27 13:31:17 EDT 2020
;; MSG SIZE  rcvd: 2275

---

From: Evan Hunt <[email-masked]>
Date: Wed, 27 May 2020 18:08:46 +0000

On Wed, May 27, 2020 at 01:48:32PM -0400, John R Levine wrote:
> is there any consensus as to the maximum CNAME chain length
> that works reliably, and what happens if the chain is too long? Hanging
> seems sub-optimal.

BIND cuts CNAME chains off at 16. As far as I know that was an arbitrarily-
selected value, but it's been in the code since 1999 and so far as I can
recall, no one's complained. The maximum reliable chain length won't be any
longer than that; it might be shorter.

When a chain is too long, I think BIND just returns a response with the 16
CNAMEs it's found so far, and without a final answer. The client can start a
new query from where the response left off, but I would expect most to
treat it as a non-answer.

-- 
Evan Hunt -- [email-masked]
Internet Systems Consortium, Inc.

---

From: "John R Levine" <[email-masked]>
Date: 27 May 2020 14:48:38 -0400

On Wed, 27 May 2020, John R Levine wrote:
> When I try and look up http://chain.examp1e.com, Chrome waits a while and 
> says not found, Firefox waits a while and says "Hmm. We’re having trouble 
> finding that site." and Safari on my Mac hangs.  (Feel free to try it 
> yourself.)

FWIW, the cache is unbound 1.10.1.

R's,
John

> I realize the answer to most questions like this can be summarized as "don't 
> do that", but is there any consensus as to the maximum CNAME chain length 
> that works reliably, and what happens if the chain is too long? Hanging seems 
> sub-optimal.

---

From: Eric Orth <[email-masked]>
Date: Wed, 27 May 2020 15:03:26 -0400

On Wed, May 27, 2020 at 1:49 PM John R Levine <[email-masked]> wrote:

> and says not found,

If Chrome is using its built-in stub, there's not expected to be a limit
(other than the overall message size limits), but nothing tests chains this
long other than security fuzzers that are only looking for crashes or
memory issues.

> Firefox waits a while and says "Hmm. We’re having
> _______________________________________________
> DNSOP mailing list
> [email-masked]
> https://www.ietf.org/mailman/listinfo/dnsop
>

---

From: Eric Orth <[email-masked]>
Date: Wed, 27 May 2020 15:05:35 -0400

I should also note though that Chrome's built-in stub won't do any followup
queries if the full chain is not in the response from the recursive.

---

From: "John R Levine" <[email-masked]>
Date: 27 May 2020 15:23:39 -0400

> queries if the full chain is not in the response from the recursive.

Interesting point -- if the result is truncated will it requery with TCP?

Regards,

---

From: "John R Levine" <[email-masked]>
Date: 27 May 2020 15:26:23 -0400

> long other than security fuzzers that are only looking for crashes or

On my Mac, I get surprisingly consietent browser results.  In Chrome, 
Firefox, and Safari, chain10.examp1e.com works, chain11.examp1e.com fails 
slowly.  From the TTLs I get from dig, it appears that none of them are 
using the resolver to follow the CNAME chains.  For Firefox I have the 
canary domain blocked so I dunno what it is doing.

Regards,

---

From: dagon <[email-masked]>
Date: Wed, 27 May 2020 20:06:14 +0000

> treat it as a non-answer.

This is an interesting topic.

Some recursives cut off at 8 CNAME chains.  Some (Level 3, if I
recall) fail at less, but retry right after sending SERVFAIL or
RCODE!=0 to the stub, perhaps to populate cache farms.  Some major
"cloud DNS" (e.g., Google if I recall) chase 30 chains before fail.
Some appear to have a ~3-sec window for the outbound queries (meaning
they have no chain count limit, only time); some appear to have a hard
numbered limit like BIND.

Poorly crafted DNS crawler scripts seem to follow CNAMEs forever (up
to some script mediated timeout period, or until the operator stops
the script and complains to the parent zone's registrar, on the theory
that unexpected behavior is abuse---despite CNAME chains being useful
for path diagnosis in VPN operation, passive DNS monitoring, etc.)

The CNAME behavioral matrix can also be extended to include:

  -- Tests for ("improper") horizontal vs. vertical CNAMEs.  Some
     recursive speakers fail; some complain ("BAD (HORIZONTAL)
     REFERRAL", but answer), and some follow without complaint.

  -- All should avoid graph cycles in CNAME chains back to ancestral
     records.

  -- Tests for slow responses, where the authority crafting the CNAME
     delays by some variable millisecond time period, to test whether
     the chain depth is time or count based.

  -- One could test for 1034 s.3.6.2 restart to chase discovered
     CNAMEs, absent additional records being added to cache.  Some
     platforms (Azure, if I recall) return just the CNAME, even if
     local cache (evidently) holds a terminating record.  I've not
     tested if this (re)introduces circular dependencies, but
     Azure(?)'s explanation would be that the restart and cycle
     detection must now occur in the stub.  But one should test
     with/without BIND's minimal-responses (and similar configurations
     in other recursives), and implications for cycles.

  -- All of the above, but for DNAME instead of CNAME.  PowerDNS will
     not support this of course, for what I infer are (understandable)
     architectural and commercial demand reasons.  But conceptually an
     authority creating synthetic CNAME records is a workable
     substitute for DNAMEs.  Some DNAME chase limits follow the CNAME
     chain limits.  One can chain multiple CNAME chains together using
     DNAME, and this may count against the original chain counter, or
     start a new one (and sometimes within some timeout period of
     course).  This also stops many script-based crawlers, which don't
     handle DNAMEs or don't bother to substitute synthesize the query
     under the new zone tree.  (I.e., they appear to cut/grep DNAME
     answers, and not handle out-zone synthesis, making them blind to
     the referral subzones.)

  -- Same, but for so-called ANAME or 'rooted CNAME' records.

  -- Measure all of these behaviors tests where the NS has essentially
     zero TTL (e.g., to measure whether retries are with/without NS
     delegation rediscovery).

  -- Measure all of the above with/without DNS authorities that are
     whitelisted for EDNS Client Subnet. (It is hard to think of a
     reason to allow ECS in a CNAME, PTR, or similarly constrained
     query type.  But some architectures use "turtles all the way
     down", and the CDN itself needs a CDN via ECS, which then needs a
     CDN via ECS, etc.
     
I've tested all of these combinations and more.  There are also many
valid commercial uses for CNAME chains beyond CDN and zone agility
(e.g., path discovery, edns-client-subnet testing, etc.)  So
blocking/limiting CNAME chains seems unwise.

-- 
David Dagon
[email-masked]
D970 6D9E E500 E877 B1E3  D3F8 5937 48DC 0FDC E717

---

From: Eric Orth <[email-masked]>
Date: Wed, 27 May 2020 16:33:30 -0400

> Interesting point -- if the result is truncated will it requery with TCP?

On TC bit, yes.

---

From: Paul Vixie <[email-masked]>
Date: Wed, 27 May 2020 20:59:13 +0000

> queries if the full chain is not in the response from the recursive.

that's correct behaviour. full resolvers iterate (and recurse). stubs do not.

-- 
Paul

---

From: Tony Finch <[email-masked]>
Date: Thu, 28 May 2020 01:02:47 +0100

>      REFERRAL", but answer), and some follow without complaint.

Can you explain what these are, please?

Tony.
-- 
f.anthony.n.finch  <[email-masked]>  http://dotat.at/
Forth, Tyne, Dogger: Variable mainly south 2 to 4. Smooth or slight. Mainly
fair. Good, occasionally poor.

---

From: dagon <[email-masked]>
Date: Thu, 28 May 2020 02:44:10 +0000

> Can you explain what these are, please?

If a canonical answer points to the same level as the 'owner name',
then the left and right sides share NS.  (This is the most common
case, and even outlined in 1034.)  If this discovery occurs during a
CNAME chain chase with yet another empty answer, the NS is in a sense
making a referral to itself, or its pool of secondary NS serving the
same delegation cut level---the bad horizontal referral.

1034 merely says resolution should be robust, and that "CNAME chains
should be followed and CNAME loops signalled as an error", s.3.6.2.
But that doesn't mean resolvers have to put up with this behavior
quietly.  Dig issues warnings in such cases; see followup_lookup() in
dighost.c:

   if (namereln == dns_namereln_equal) {
      if (!horizontal)
         printf(";; BAD (HORIZONTAL) REFERRAL\n");
      horizontal = true;

Tools that warn about this seem to take the larger view that such
referrals should be directed maybe to a new sibling tree (e.g.,
something in example.org CNAME'd to some place in example.com) or
further downward (implying there's a zone cut, but that aspect is not
enforced or audited by dig.)  There's sense in this: during the second
empty answer in CNAME to a mere sibling label, the recursive is
*already* talking to the right authority, dammit, and frankly it's
inability to sort the zone into a non-chained state is an unhelpful
referral.  (It may also symptomatic of a zone configuration error---
implicit $ORIGINs and inconsistent fqdns, and such---which is probably
why dig included this warning.)

You can actually experience something like horizontal referrals in
some US airport security screenings, which segregate passenger lines
based on ticket classes.  If you have a higher ranked ticket, but
stand in the *longer* lower ranked line to chat with your friend, you
might be referred back to the end of the higher ranked ticket line,
just to arrive again at the very same screening point---a bad
horizontal referral in most people's view.  I never encountered this
in European airports, and so it is very fitting that ldns tools and
kdig don't check horizontal referrals like dig.

By this analogy, DNS resolvers have a choice in chained horizontal
follows: quietly continue the journey, continue on but argue to anyone
logging complaints (dig), or just abruptly cancel the flight
(SERVFAIL).

It would be useful to have a survey of all such behaviors for various
appliances and tools on the Internet, since this sometimes leads to
zones being unavailable.  You can find a few anecdotes in the BIND
support lists.

---

From: Mark Andrews <[email-masked]>
Date: Thu, 28 May 2020 13:22:40 +1000

'BAD (HORIZONTAL) REFERRAL' has nothing to do with CNAMES.  It’s reporting
a referral to a set of servers that in turn return a referral to another
set of servers server at the same depth.  It’s reported by ‘dig +trace’. 

> On 28 May 2020, at 12:44, dagon <[email-masked]> wrote:
> 

-- 
Mark Andrews, ISC
1 Seymour St., Dundas Valley, NSW 2117, Australia
PHONE: +61 2 9871 4742              INTERNET: [email-masked]

---

From: "John R Levine" <[email-masked]>
Date: 27 May 2020 23:24:32 -0400

> same delegation cut level---the bad horizontal referral.

It sounds like "horizontal" means within the same zone, and "vertical" 
means a different zone.  I don't know what you mean by "level" -- same 
number of components in the name? All but the last component the same? 
In the same zone?  Something else?

In RFC 1034 CNAMEs are intended as short term transition aidss, sort of 
like yellow forwarding stickers on paper mail.  Now they're mostly used to 
dynamically delegate names across authority boundaries, which works pretty 
well, but there's a lot of ancient cruft still sticking to them.

Regards,

---

From: dagon <[email-masked]>
Date: Thu, 28 May 2020 04:16:03 +0000

> set of servers server at the same depth.  It’s reported by ‘dig +trace’.

This thread sought ideas about what to measure in chain handling,
where one can encounter this error.  It's not CNAME-specific of
course, nor were the other NS-oriented comments, or resolution timing
limits, etc.