Subject: [dnsext] DNAME with exceptions - work-around found

From: Brian Dickson <[email-masked]>
Date: Fri, 10 Sep 2010 17:34:30 -0300

I've been giving more thought to the issue of DNAME vs "exceptions", and
checked both RFC 2672, and the current draft 2672-bis.

The following is *technically* allowed, and *definitely* evil.

But, at least in the current latest bind implementation I've checked (bind
9.7.1-P2), it actually works, with no modifications to the protocol or the
code.

Which means, if folks agree that this is a reasonable hack to allow use of,
it may be possible to implement all the desired features and functions for
"the same" using just a slightly-modified SHADOW.
(And suitable additional discussion in 2672-bis, for which if needed I am
willing to supply text.)

Here's the trick - on the authority server, serve up more-specific zone(s)
as needed, whose owner would have been a descendant of one of the DNAMEs
used to make things "the same".

Being "deeper" in the tree, it is found first. And not being in the same
zone, it is technically allowed, although strongly discouraged (SHOULD NOT
is the language in -bis).

So, the modifications to SHADOW would be:
Place exceptions in per-SHADOW-zone more-specific zones (generating the zone
files and conf files as needed)
Normal SHADOW zones consist of apex copies of Amber apex, plus DNAME of
Amber zone (per the previous suggestion, was it Olaf?)
(Delegations to signed SHADOW zones might not handle "exceptions" properly,
or at all, or some additional fix-ups may make them possible.)

Brian

Here's an example of it in use (apologies for the bind-specific bits):

/etc/named.conf (relevant bits anyway):

zone "foo.example.com" IN {
    type master;
    file "foo.example.zone";
    allow-update { none; };
    };

zone "bar.example.com" IN {
    type master;
    file "bar.example.zone";
    allow-update { none; };
    };

zone "bar.foo.example.com" IN {
    type master;
    file "bar.foo.example.zone";
    allow-update { none; };
    };

file bar.example.zone:

$TTL    86400
$ORIGIN bar.example.com.
@            1D IN SOA    @ root (
                    42        ; serial (d. adams)
                    3H        ; refresh
                    15M        ; retry
                    1W        ; expiry
                    1D )        ; minimum

            1D IN NS    ns1.bar.example.com.
ns1.bar.example.com.    1D IN A        127.0.0.1 ;glue record
foo            1D IN TXT    "foo.bar.example.com is what is in the zone.
Where did you find this record?"
bar            1D IN TXT    "bar.bar.example.com is what is in the zone.
Where did you find this record?"
foo.bar            1D IN TXT    "foo.bar.bar.example.com is what is in the
zone. Where did you find this record?"

file bar.foo.example.zone:

$TTL    86400
$ORIGIN bar.foo.example.com.
@                1D IN SOA    @ root (
                    42        ; serial (d. adams)
                    3H        ; refresh
                    15M        ; retry
                    1W        ; expiry
                    1D )        ; minimum

                1D IN NS    ns1.bar.foo.example.com.
ns1.bar.foo.example.com.    1D IN A        127.0.0.1 ;glue record
foo                1D IN TXT    "foo.bar.foo.example.com is what is in the
zone. Where did you find this record?"

file foo.example.zone:

$TTL    86400
$ORIGIN foo.example.com.
@            1D IN SOA    @ root (
                    42        ; serial (d. adams)
                    3H        ; refresh
                    15M        ; retry
                    1W        ; expiry
                    1D )        ; minimum

            1D IN NS    ns1.foo.example.com.
            1D IN DNAME    bar.example.com.
ns1.foo.example.com.    1D IN A        127.0.0.1 ;glue record
foo            1D IN TXT    "foo.foo.example.com is what is in the zone.
Where did you find this record?"
bar            1D IN TXT    "bar.foo.example.com is what is in the zone.
Where did you find this record?"
foo.bar            1D IN TXT    "foo.bar.foo.example.com is what is in the
zone. Where did you find this record?"
; NB - the above are, if present, occluded by the DNAME.

And the results:

bash-3.2# dig @127.0.0.1 TXT foo.foo.example.com

; <<>> DiG 9.4.3-P3 <<>> @127.0.0.1 TXT foo.foo.example.com
; (1 server found)
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 64694
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 1, ADDITIONAL: 1

;; QUESTION SECTION:
;foo.foo.example.com.        IN    TXT

;; ANSWER SECTION:
foo.example.com.    86400    IN    DNAME    bar.example.com.
foo.foo.example.com.    0    IN    CNAME    foo.bar.example.com.
foo.bar.example.com.    86400    IN    TXT    "foo.bar.example.com is what
is in the zone. Where did you find this record?"

;; AUTHORITY SECTION:
bar.example.com.    86400    IN    NS    ns1.bar.example.com.

;; ADDITIONAL SECTION:
ns1.bar.example.com.    86400    IN    A    127.0.0.1

;; Query time: 13 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Fri Sep 10 17:24:05 2010
;; MSG SIZE  rcvd: 206

bash-3.2# dig @127.0.0.1 TXT foo.bar.example.com

; <<>> DiG 9.4.3-P3 <<>> @127.0.0.1 TXT foo.bar.example.com
; (1 server found)
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 38887
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1

;; QUESTION SECTION:
;foo.bar.example.com.        IN    TXT

;; ANSWER SECTION:
foo.bar.example.com.    86400    IN    TXT    "foo.bar.example.com is what
is in the zone. Where did you find this record?"

;; AUTHORITY SECTION:
bar.example.com.    86400    IN    NS    ns1.bar.example.com.

;; ADDITIONAL SECTION:
ns1.bar.example.com.    86400    IN    A    127.0.0.1

;; Query time: 11 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Fri Sep 10 17:24:18 2010
;; MSG SIZE  rcvd: 159

bash-3.2# dig @127.0.0.1 TXT foo.bar.foo.example.com

; <<>> DiG 9.4.3-P3 <<>> @127.0.0.1 TXT foo.bar.foo.example.com
; (1 server found)
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 36569
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1

;; QUESTION SECTION:
;foo.bar.foo.example.com.    IN    TXT

;; ANSWER SECTION:
foo.bar.foo.example.com. 86400    IN    TXT    "foo.bar.foo.example.com is
what is in the zone. Where did you find this record?"

;; AUTHORITY SECTION:
bar.foo.example.com.    86400    IN    NS    ns1.bar.foo.example.com.

;; ADDITIONAL SECTION:
ns1.bar.foo.example.com. 86400    IN    A    127.0.0.1

;; Query time: 7 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Fri Sep 10 17:24:25 2010
;; MSG SIZE  rcvd: 167

---

From: "W.C.A. Wijngaards" <[email-masked]>
Date: Sat, 11 Sep 2010 10:34:22 +0200

Hi Brian,

That is one dirty trick, and it may even work with NSD  :-) .  However,
it fails with caches.  If a cache finds a DNAME record in the cache it
is allowed to synthesize a CNAME by itself.  And having seen foo DNAME
bar, it will no longer send queries for bar.foo to the foo server.

(it may be wise to only do this for DNSSEC validated DNAMEs, but that is
besides the point for aliasing).

Thus this may seem to work if you dig the authority server, but the
resolvers won't.  Also some authority implementations may refuse to load
the DNAME zone with a delegation below the DNAME - even bind will (IMHO
- - I am not afiliated with ISC) treat this as obscured information and
will not serve the DS record for the lower zone, or properly deny such a
DS, making the bar.foo zone DNSSEC bogus.

Best regards,
   Wouter

On 09/10/2010 10:34 PM, Brian Dickson wrote:
> But, at least in the current latest bind implementation I've checked
> (bind 9.7.1-P2), it actually works, with no modifications to the
> protocol or the code.
> 
> Which means, if folks agree that this is a reasonable hack to allow use
> of, it may be possible to implement all the desired features and
> functions for "the same" using just a slightly-modified SHADOW.
> (And suitable additional discussion in 2672-bis, for which if needed I
> am willing to supply text.)
> 
> Here's the trick - on the authority server, serve up more-specific
> zone(s) as needed, whose owner would have been a descendant of one of
> the DNAMEs used to make things "the same".
> 
> zone, it is technically allowed, although strongly discouraged (SHOULD
> NOT is the language in -bis).
> 
> 
> Place exceptions in per-SHADOW-zone more-specific zones (generating the
> zone files and conf files as needed)
> (Delegations to signed SHADOW zones might not handle "exceptions"
> properly, or at all, or some additional fix-ups may make them possible.)
> 
> Brian
> 
> 
> zone "foo.example.com <http://foo.example.com>" IN {
>     type master;
>     file "foo.example.zone";
>     allow-update { none; };
>     };
> 
> zone "bar.example.com <http://bar.example.com>" IN {
>     type master;
>     file "bar.example.zone";
>     allow-update { none; };
>     };
> 
> zone "bar.foo.example.com <http://bar.foo.example.com>" IN {
>     type master;
> $ORIGIN bar.example.com <http://bar.example.com>.
> @            1D IN SOA    @ root (
>                     42        ; serial (d. adams)
>                     3H        ; refresh
>                     15M        ; retry
>                     1W        ; expiry
>                     1D )        ; minimum
> 
>             1D IN NS    ns1.bar.example.com <http://ns1.bar.example.com>.
> ns1.bar.example.com <http://ns1.bar.example.com>.    1D IN A       
> 127.0.0.1 ;glue record
> foo            1D IN TXT    "foo.bar.example.com
> <http://foo.bar.example.com> is what is in the zone. Where did you find
> this record?"
> bar            1D IN TXT    "bar.bar.example.com
> <http://bar.bar.example.com> is what is in the zone. Where did you find
> this record?"
> foo.bar            1D IN TXT    "foo.bar.bar.example.com
> <http://foo.bar.bar.example.com> is what is in the zone. Where did you
> find this record?"
> 
> 
> $ORIGIN bar.foo.example.com <http://bar.foo.example.com>.
> @                1D IN SOA    @ root (
>                     42        ; serial (d. adams)
>                     3H        ; refresh
>                     15M        ; retry
>                     1W        ; expiry
>                     1D )        ; minimum
> 
>                 1D IN NS    ns1.bar.foo.example.com
> <http://ns1.bar.foo.example.com>.
> ns1.bar.foo.example.com <http://ns1.bar.foo.example.com>.    1D IN A   
>     127.0.0.1 ;glue record
> foo                1D IN TXT    "foo.bar.foo.example.com
> <http://foo.bar.foo.example.com> is what is in the zone. Where did you
> find this record?"
> 
> 
> file foo.example.zone:
> 
> $TTL    86400
> $ORIGIN foo.example.com <http://foo.example.com>.
> @            1D IN SOA    @ root (
>                     42        ; serial (d. adams)
>                     3H        ; refresh
>                     15M        ; retry
>                     1W        ; expiry
>                     1D )        ; minimum
> 
>             1D IN NS    ns1.foo.example.com <http://ns1.foo.example.com>.
>             1D IN DNAME    bar.example.com <http://bar.example.com>.
> ns1.foo.example.com <http://ns1.foo.example.com>.    1D IN A       
> 127.0.0.1 ;glue record
> foo            1D IN TXT    "foo.foo.example.com
> <http://foo.foo.example.com> is what is in the zone. Where did you find
> this record?"
> bar            1D IN TXT    "bar.foo.example.com
> <http://bar.foo.example.com> is what is in the zone. Where did you find
> this record?"
> foo.bar            1D IN TXT    "foo.bar.foo.example.com
> <http://foo.bar.foo.example.com> is what is in the zone. Where did you
> find this record?"
> bash-3.2# dig @127.0.0.1 <http://127.0.0.1> TXT foo.foo.example.com
> <http://foo.foo.example.com>
> 
> ; <<>> DiG 9.4.3-P3 <<>> @127.0.0.1 <http://127.0.0.1> TXT
> foo.foo.example.com <http://foo.foo.example.com>
> ; (1 server found)
> ;foo.foo.example.com <http://foo.foo.example.com>.        IN    TXT
> 
> ;; ANSWER SECTION:
> foo.example.com <http://foo.example.com>.    86400    IN    DNAME   
> bar.example.com <http://bar.example.com>.
> foo.foo.example.com <http://foo.foo.example.com>.    0    IN    CNAME   
> foo.bar.example.com <http://foo.bar.example.com>.
> foo.bar.example.com <http://foo.bar.example.com>.    86400    IN   
> TXT    "foo.bar.example.com <http://foo.bar.example.com> is what is in
> the zone. Where did you find this record?"
> 
> ;; AUTHORITY SECTION:
> bar.example.com <http://bar.example.com>.    86400    IN    NS   
> ns1.bar.example.com <http://ns1.bar.example.com>.
> 
> ;; ADDITIONAL SECTION:
> ns1.bar.example.com <http://ns1.bar.example.com>.    86400    IN    A   
> 127.0.0.1
> 
> ;; Query time: 13 msec
> bash-3.2# dig @127.0.0.1 <http://127.0.0.1> TXT foo.bar.example.com
> <http://foo.bar.example.com>
> 
> ; <<>> DiG 9.4.3-P3 <<>> @127.0.0.1 <http://127.0.0.1> TXT
> foo.bar.example.com <http://foo.bar.example.com>
> ; (1 server found)
> ;foo.bar.example.com <http://foo.bar.example.com>.        IN    TXT
> 
> ;; ANSWER SECTION:
> foo.bar.example.com <http://foo.bar.example.com>.    86400    IN   
> TXT    "foo.bar.example.com <http://foo.bar.example.com> is what is in
> the zone. Where did you find this record?"
> 
> ;; AUTHORITY SECTION:
> bar.example.com <http://bar.example.com>.    86400    IN    NS   
> ns1.bar.example.com <http://ns1.bar.example.com>.
> 
> ;; ADDITIONAL SECTION:
> ns1.bar.example.com <http://ns1.bar.example.com>.    86400    IN    A   
> 127.0.0.1
> 
> ;; Query time: 11 msec
> bash-3.2# dig @127.0.0.1 <http://127.0.0.1> TXT foo.bar.foo.example.com
> <http://foo.bar.foo.example.com>
> 
> ; <<>> DiG 9.4.3-P3 <<>> @127.0.0.1 <http://127.0.0.1> TXT
> foo.bar.foo.example.com <http://foo.bar.foo.example.com>
> ; (1 server found)
> ;foo.bar.foo.example.com <http://foo.bar.foo.example.com>.    IN    TXT
> 
> ;; ANSWER SECTION:
> foo.bar.foo.example.com <http://foo.bar.foo.example.com>. 86400    IN   
> TXT    "foo.bar.foo.example.com <http://foo.bar.foo.example.com> is what
> bar.foo.example.com <http://bar.foo.example.com>.    86400    IN   
> NS    ns1.bar.foo.example.com <http://ns1.bar.foo.example.com>.
> 
> ;; ADDITIONAL SECTION:
> ns1.bar.foo.example.com <http://ns1.bar.foo.example.com>. 86400    IN   
> A    127.0.0.1
> 
> ;; Query time: 7 msec

---

From: Brian Dickson <[email-masked]>
Date: Sun, 12 Sep 2010 19:24:20 -0300

Hi, Wouter,

Good point about the cache issue.

It took a day or two to investigate a bit further, but I think I have an
even dirtier trick...

Set the DNAME TTL to 0.

:-)

The DNAME only causes a problem in the cache, if it is in the cache before
the query for the more-specific exception is made.
Not caching the DNAME prevents this. (The best way to win a race is to not
have the race at all.)

This works, both directly, and through a cache. And it can be made to also
validate for DNSSEC, by the following process:

Use the same DNSKEYs for all the variants at each level (i.e. one set of key
for all the exceptions under one zone), and combining the (mapped) DS keys,
gets you the necessary delegation DNSSEC authentication/validation. The
DNAME rewrites the QNAME, but the returned values authenticate since they
are the same RDATA. (Yes, it's a cute and evil trick.)

For the variants (parent zones of the exceptions), each variant consists of
the apex RRs plus the DNAME, signed. Their DS records get inserted into the
parent zone(s).

The server for all the variants is also the server for the exceptions. All
of those zones are loaded.
The delegation server needs only the variants, and does not need to know
about the exceptions (yay!).
The delegations inside the variants, for the exceptions, don't actually
exist - they occur via the DNAME target zone, and are served up directly (in
response to appropriate QNAME/QTYPE/QCLASS matches).

This has been verified by trying it out using bind, on a couple of instances
talking to each other (recursive resolver and authority server). I *think*
it will also work with NSD... it doesn't require anything BIND specific. It
just abuses the RFCs. :-)

The targets of the DNAMEs get cached as needed, and thus the *only* thing
not cached are the DNAMEs themselves (and the synthesized CNAMEs, and their
RRSIGs).

So, if there were no other work done, and a registrant had need for this,
the main impact would be that the DNAME look-ups would not be cached, and
every variant look-up would propagate to the authority server(s) for the
registrant.

This suggests some kind of work, either specifically concerning the overall
"sameness" problem, or perhaps specifically to handle DNAMEs with TTL=0 (to
do something "special" to handle the scaling, while preserving the ability
to implement a work-around like this.)

I think that, performance hit on the DNAME aside, it scales pretty okay, but
obviously isn't something someone would want to run on a big (high-volume)
TLD.
It appears, at first blush, to support hierarchies of this kind with
exceptions at each level.
(It does require using common DNSKEYs among variants, but not among
parent/children relations.)

Brian

On Sat, Sep 11, 2010 at 5:34 AM, W.C.A. Wijngaards <[email-masked]>wrote:

---

From: "W.C.A. Wijngaards" <[email-masked]>
Date: Mon, 13 Sep 2010 09:12:19 +0200

On 09/13/2010 12:24 AM, Brian Dickson wrote:
> This works, both directly, and through a cache. And it can be made to
> also validate for DNSSEC, by the following process:

Yes, that is dirty, :-)

> Use the same DNSKEYs for all the variants at each level (i.e. one set of
> key for all the exceptions under one zone), and combining the (mapped)
> DS keys, gets you the necessary delegation DNSSEC
> authentication/validation. The DNAME rewrites the QNAME, but the
> returned values authenticate since they are the same RDATA. (Yes, it's a
> cute and evil trick.)

Well, the signer name in the RRSIG needs to refer to the correct key.
Not sure if zone-signers, servers and validators can agree on that.  Or
be made to, but you could include multiple RRSIGs as a cop-out I guess.

I note the high traffic volume of this solution.  Due to TTL=0.  (for
the requirements).

Best regards,
   Wouter

---

From: Niall O'Reilly <[email-masked]>
Date: Mon, 13 Sep 2010 16:09:56 +0100

> used to make things "the same".

	[Without considering cache]

	All descendant zones would have to use the same set of
	authority servers as those which carried the
	furthest (highest, least-specific) occluding DNAME,
	or a superset of that set.

	Ouch!

	/Niall

---

From: Andrew Sullivan <[email-masked]>
Date: Mon, 13 Sep 2010 11:58:28 -0400

No hat.

> Set the DNAME TTL to 0.

I don't believe that this WG is going to standardize anything that
requires TTLs = 0.  It's particularly not the sort of advice we can
offer operators of TLDs and so on.  Nifty hacks are fun to think
about, but I don't think this is a proposal that is really going to go
anywhere.

A

-- 
Andrew Sullivan
[email-masked]
Shinkuro, Inc.

---

From: Brian Dickson <[email-masked]>
Date: Mon, 13 Sep 2010 13:46:03 -0300

> about, but I don't think this is a proposal that is really going to go
Actually, I concur.

I am using the properties needed in achieving the hack, to show that I
believe some work is probably needed, to avoid unpleasant choices by users
of the DNS, i.e. registrants.

It's traditional logic, "reduction to absurdity". If you start with a set of
axioms, and reach an absurd conclusion, it means one of those axioms is a
bad choice.

The axioms here were:
DNS protocol as is
Make multiple domains "the same"
Allow exceptions (to handle things which cannot be the same)
Make it scale (i.e. something other than brute force provisioning -> dname)

Registrants are likely to not have choice over the "allow exceptions", or
"Make multiple domains the same", as requirements imposed on them.

This suggests either provisioning-only as a solution, or a requirement to
make some sort of change to the existing DNS RFCs to improve things where
those requirements are taken as axiomatic.

Brian

P.S. I'm pretty sure that hierarchical SHADOW, without support for
exceptions, can work, based on the hackery I did.