Subject: [DNSOP] Unexpected REFUSED from BIND when using example config from RFC7706

From: Bjørn Mork <[email-masked]>
Date: Thu, 06 Apr 2017 11:03:38 +0200

Hello,

We are currently trying out the configuration recommended by RFC7706,
serving a copy of the root zone on a loopback.  We are using BIND 9.10
and our configuration is directly copied from the example in appendix
B.1.  Even down to the actual loopback address used, as we needed a
dedicated one for this instance anyway.

Recently I noticed a side effect of this configuration which I consider
unwanted and unexpected: It changes how BIND replies to requests without
the RD bit set. BIND will normally answer such requests with a "best
possible redirection", using any matching NS set it has in its cache.
Which often will be the root NS.  But using the RFC7706 example config,
BIND will REFUSE all requests without RD set.

For the record, my expectations are: Absolutely no visible effects to
any DNS clients compared to a normal caching resolver, using the real
root servers directly.

Using the RFC7706 appedix B.1 example config, this is what I see:

bjorn@miraculix:~$ dig . soa @::1 +norecur

; <<>> DiG 9.10.3-P4-Debian <<>> . soa @::1 +norecur
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 49721
;; flags: qr ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;.                              IN      SOA

;; Query time: 0 msec
;; SERVER: ::1#53(::1)
;; WHEN: Thu Apr 06 10:25:30 CEST 2017
;; MSG SIZE  rcvd: 28

Snooping on the loopback interface, shows that the query is refused
without being referred to the local root instance (which makes sort of
sense, as that would be recursion...):

    1 0.000000000           ::1 → ::1          Standard query 0xc239 SOA <Root> OPT 92 DNS
    2 0.000093346           ::1 → ::1          Standard query response 0xc239 Refused SOA <Root> OPT 92 DNS

Removing the local root zone, letting BIND fall back to its built-in
hint zone:

   view recursive {
       dnssec-validation auto;
       allow-recursion { any; };
       recursion yes;
  /*     zone "." {
           type static-stub;
           server-addresses { 127.12.12.12; };
       };
 */
   };

changes this behaviour.  BIND now replies with NOERROR and an AUTHORITY
section from its hints (or actually from the cache. Note the AD flag):

bjorn@miraculix:~$ dig . soa @::1 +norecur

; <<>> DiG 9.10.3-P4-Debian <<>> . soa @::1 +norecur
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29060
;; flags: qr ra ad; QUERY: 1, ANSWER: 0, AUTHORITY: 13, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;.                              IN      SOA

;; AUTHORITY SECTION:
.                       518378  IN      NS      a.root-servers.net.
.                       518378  IN      NS      g.root-servers.net.
.                       518378  IN      NS      m.root-servers.net.
.                       518378  IN      NS      j.root-servers.net.
.                       518378  IN      NS      h.root-servers.net.
.                       518378  IN      NS      e.root-servers.net.
.                       518378  IN      NS      f.root-servers.net.
.                       518378  IN      NS      k.root-servers.net.
.                       518378  IN      NS      d.root-servers.net.
.                       518378  IN      NS      l.root-servers.net.
.                       518378  IN      NS      i.root-servers.net.
.                       518378  IN      NS      c.root-servers.net.
.                       518378  IN      NS      b.root-servers.net.

;; Query time: 0 msec
;; SERVER: ::1#53(::1)
;; WHEN: Thu Apr 06 10:18:43 CEST 2017
;; MSG SIZE  rcvd: 239

Snooping on "any" reveals that there is no recursion in this case
either, as expected.  The data source is the cache:

    1 0.000000000           ::1 → ::1          Standard query 0x7184 SOA <Root> OPT 92 DNS
    2 0.000213074           ::1 → ::1          Standard query response 0x7184 SOA <Root> NS a.root-servers.net NS g.root-servers.net NS m.root-servers.net NS j.root-servers.net NS h.root-servers.net NS e.root-servers.net NS f.root-servers.net NS k.root-servers.net NS d.root-servers

The problem is that configuring a "static-stub" root zone changes how
BIND handles non RD requests.  Note that this affects *any* such
request, not only requests for root.  Example with the "static-stub"
root zone enabled:

bjorn@miraculix:~$ dig ns mork.no @::1 +norecur

; <<>> DiG 9.10.3-P4-Debian <<>> ns mork.no @::1 +norecur
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 34742
;; flags: qr ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;mork.no.                       IN      NS

;; Query time: 0 msec
;; SERVER: ::1#53(::1)
;; WHEN: Thu Apr 06 10:32:45 CEST 2017
;; MSG SIZE  rcvd: 36

    1 0.000000000           ::1 → ::1          Standard query 0x87b6 NS mork.no OPT 100 DNS
    2 0.000121759           ::1 → ::1          Standard query response 0x87b6 Refused NS mork.no OPT 100 DNS

BIND will normally provide the root hint from its cache without the
"static-stub" root zone configuration:

bjorn@miraculix:~$ dig ns mork.no @::1 +norecur

; <<>> DiG 9.10.3-P4-Debian <<>> ns mork.no @::1 +norecur
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 54393
;; flags: qr ra ad; QUERY: 1, ANSWER: 0, AUTHORITY: 13, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;mork.no.                       IN      NS

;; AUTHORITY SECTION:
.                       518395  IN      NS      a.root-servers.net.
.                       518395  IN      NS      h.root-servers.net.
.                       518395  IN      NS      g.root-servers.net.
.                       518395  IN      NS      d.root-servers.net.
.                       518395  IN      NS      f.root-servers.net.
.                       518395  IN      NS      e.root-servers.net.
.                       518395  IN      NS      m.root-servers.net.
.                       518395  IN      NS      j.root-servers.net.
.                       518395  IN      NS      k.root-servers.net.
.                       518395  IN      NS      i.root-servers.net.
.                       518395  IN      NS      c.root-servers.net.
.                       518395  IN      NS      b.root-servers.net.
.                       518395  IN      NS      l.root-servers.net.

;; Query time: 1 msec
;; SERVER: ::1#53(::1)
;; WHEN: Thu Apr 06 10:34:27 CEST 2017
;; MSG SIZE  rcvd: 247

    1 0.000000000           ::1 → ::1          Standard query 0xd479 NS mork.no OPT 100 DNS
    2 0.000255371           ::1 → ::1          Standard query response 0xd479 NS mork.no NS a.root-servers.net NS h.root-servers.net NS g.root-servers.net NS d.root-servers.net NS f.root-servers.net NS e.root-servers.net NS m.root-servers.net NS j.root-servers.net NS k.root-servers.net NS i.root-servers.net NS c.root-servers.net NS b.root-servers.net NS l.root-servers.net OPT 311 DNS

These examples are all without setting RD.  I will not bore you with the
results having RD set.  Those all work as expected, giving identical
replies with or without the "static-stub" root zone.

Experimenting a bit more, I found that changing the RFC7706 example
config to use a "forward" zone instead, makes BIND behave as expected
while keeping advantages of the local root zone instance:

   view recursive {
       dnssec-validation auto;
       allow-recursion { any; };
       recursion yes;
       zone "." {
           type forward;
           forwarders { 127.12.12.12; };
       };
   };

With this configuration I get similar results to the examples without a
local root zone, while still having all requests for root served by the
local instance.  So it looks like a good solution to me.

I've tried googling a bit to see if this issue has been discussed
before, or if I could find any background for the selection of a
"static-stub" zone type for the RFC7706 example.  But I have not been
able to find any.  Which is why I decided to ask here.  Is this a known
and expected issue?  Why was "static-stub" used?  Was "forward" ever
considered?

I realize that using "forward" will change the behaviour in case of a
failing local instance, but that can also be seen as a feature.  Or it
can be prevented by simply adding "forward only" to the config, if
complete failure is preferred over falling back to the real root
servers.

Finally, if any of you wonder why I (or anyone else) with send queries
without RD set to a caching resolver:  I discovered the issue because
"dig +trace" started failing.  Quoting from dig(1):

   "Recursion is automatically disabled when the +nssearch or +trace
    query options are used."

This makes a real use case for me.  And I like to think that I'm not
that special, so there are probably other users thinking this
behavioural change is a bug as well.

Any advice is appreciated.  Our choices are basically down to either
 a) disable the local root instance, or
 b) go for the "forward" variant

But I hesitate to select option b) without knowing the background for the
"static-stub" recommendation.  Option a) seems a lot safer at the
moment...

Bjørn

---

From: Tony Finch <[email-masked]>
Date: Thu, 6 Apr 2017 12:35:55 +0100

> BIND will REFUSE all requests without RD set.

I agree this behaviour is unhelpful and weird. It seems to come from the
following bit of the source, though the comment doesn't help very much to
explain the whys or wherefores.
https://source.isc.org/cgi-bin/gitweb.cgi?p=bind9.git;a=blob;f=bin/named/query.c;h=0cfdf9288fb16a8f991e7a31f3248118add691d5;hb=HEAD#l1040

	/*
	 * Non recursive query to a static-stub zone is prohibited; its
	 * zone content is not public data, but a part of local configuration
	 * and should not be disclosed.
	 */
	if (dns_zone_gettype(zone) == dns_zone_staticstub &&
	    !RECURSIONOK(client)) {
		return (DNS_R_REFUSED);
	}

You might be able to work around the problem by adding a
match-recursion-only option to the recursive view, and adding a
non-recursive view that has allow-query-cache, and use attach-cache
so all views share the same cache. I have not tried this :-)

Tony.
-- 
f.anthony.n.finch  <[email-masked]>  http://dotat.at/  -  I xn--zr8h punycode
Viking, North Utsire, South Utsire, Northeast Forties: Westerly 5 to 7,
occasionally gale 8 at first in Viking and South Utsire, veering northwesterly
5 or 6. Rough, becoming moderate later. Occasional drizzle, fog patches
developing. Moderate, occasionally very poor.

---

From: Bjørn Mork <[email-masked]>
Date: Thu, 06 Apr 2017 14:07:31 +0200

> 	if (dns_zone_gettype(zone) == dns_zone_staticstub &&

Thanks for the code pointer!  I tried looking at the git commit log, but
it didn't help explaining this either.  The comment and test were part
of the commit adding the "static-stub" feature.

> so all views share the same cache. I have not tried this :-)

And the main objection I hear wrt RFC7706 is that it complicates the
config :)

Bjørn

---

From: Paul Vixie <[email-masked]>
Date: Thu, 06 Apr 2017 05:32:25 -0700

> Tony Finch <[email-masked]> writes:
...
> And the main objection I hear wrt RFC7706 is that it complicates the

if you want to run yeti-style, there are some perl scripts that will
fetch and verify the root zone, edit the apex NS and DNSKEY RRsets,
re-sign with your local key, and give you a zone you can run on several
servers inside your internal network, such that you can point your
"hints" and your dnssec anchor at servers you control, for all your
internal-network recursives, the code is here:

https://github.com/BII-Lab/Yeti-Project/tree/master/script/TISF

note, my internal-network is the virtualbox shared network on my own
laptop, so while this is a complex approach, it's fewer moving parts for
me than RFC 7706 would have been.

-- 
P Vixie

---

From: David Conrad <[email-masked]>
Date: Thu, 6 Apr 2017 13:53:25 -1000

On Apr 6, 2017, 2:32 AM -1000, Paul Vixie <[email-masked]>, wrote:

> internal-network recursives,

Not so sure this is something I'd go about recommending to pretty much anyone other than hardcore, very experienced DNS/DNSSEC protocol geeks since it pretty much defeats the purpose of DNSSEC (edit the apex? ugh) and requires all relying devices to configure a "non-default" trust anchor or suffer SERVFAILs.

Regards,
-drc

---

From: Paul Vixie <[email-masked]>
Date: Fri, 07 Apr 2017 00:24:24 +0000

On Thursday, April 6, 2017 11:53:25 PM GMT David Conrad wrote:
> Not so sure this is something I'd go about recommending to pretty much
> anyone other than hardcore, very experienced DNS/DNSSEC protocol geeks
> since it pretty much defeats the purpose of DNSSEC (edit the apex? ugh) and
> requires all relying devices to configure a "non-default" trust anchor or
> suffer SERVFAILs.

other than one proviso and one misstatement, i agree with this.

the proviso is, RFC 7706 is also completely unsuitable for non-hardcore or non-
experienced or non-protocol-geeks; and both approaches are appropriate only for closed 
internal networks where the configuration is controlled by a single administration.

the misstatement is, dnssec's purpose is not defeated, because iana's signatures are 
checked before the zone is accepted, and new signatures are added using local keys 
before publication.

for my many-vm's laptop environment, running on a loopback isn't a solution.

see also:

http://www.circleid.com/posts/20160330_let_me_make_yeti_dns_perfectly_clear/

vixie

---

From: David Conrad <[email-masked]>
Date: Thu, 6 Apr 2017 14:51:40 -1000

Paul,

On Apr 6, 2017, 2:24 PM -1000, Paul Vixie <[email-masked]>, wrote:
> the proviso is, RFC 7706 is also completely unsuitable for non-hardcore or non-experienced or non-protocol-geeks;

7706 doesn't recommend editing someone else's zone file, re-signing it, and figuring out how to replicate the hints and trust anchor to _all_ relying devices (and restoring the real hints/trust anchor when you're done). This strikes me as a bit more complicated than 7706. YMMV.

> and both approaches are appropriate only for closed internal networks where the configuration is controlled by a single administration.

If I set up an open resolver using 7706, what negative repercussions do you see?

> the misstatement is, dnssec's purpose is not defeated, because iana's signatures are checked before the zone is accepted, and new signatures are added using local keys before publication.

Meaningless given you are explicitly editing the zone as published from and signed by the authority. You claim you aren't modifying the namespace content (well, except for the root NSes), but the point of DNSSEC and the rather Byzantine structures we've built around establishing trust is that we don't have to believe you: you can see _everything_ having to do with creating the trust anchor and everything down to the leaf.

The only advantage(?) I see in your approach is that is allows (forces) you to play around with the namespace and re-sign. Might be useful for neo maxi zoom DNSSEC nerd experimentation, however as mentioned previously, that doesn't strike me as something one might recommend lightheartedly.

> for my many-vm's laptop environment, running on a loopback isn't a solution.

I strongly suspect you could come up with a solution that didn't use loopback but which also didn't require modifying the root zone, resigning it, and getting all relying devices to use both your NS hints and trust anchor. Like, perhaps running on a non-loopback address within your flock of laptop VMs?

> http://www.circleid.com/posts/20160330_let_me_make_yeti_dns_perfectly_clear/

Saw that a year ago and still think it post-hoc rationalization and specious, but I'm sure that's just me.

Regards,
-drc

---

From: Paul Vixie <[email-masked]>
Date: Fri, 07 Apr 2017 02:01:40 +0000

On Friday, April 7, 2017 12:51:40 AM GMT David Conrad wrote:
> ... This strikes me as a bit more complicated than 7706. YMMV.

yes, it does.

> > and both approaches are appropriate only for closed internal networks
> > where the configuration is controlled by a single administration.

you'll need some name server features that aren't otherwise standardized, and maybe a 
patch or two. in my preferred approach, you just load a zone, because all of the special 
logic is outside of the name server and its configuration.

> > the misstatement is, dnssec's purpose is not defeated, because iana's
> > signatures are checked before the zone is accepted, and new signatures
> > are added using local keys before publication.
> Meaningless given you are explicitly editing the zone as published from and
> signed by the authority.

i hear you say meaningless, but i know that you know the difference between a name 
space and a zone that implements that namespace, and i know you've read my 
explainations about the importance of that distinction. it's not meaningless.

> You claim you aren't modifying the namespace
> content (well, except for the root NSes), ...

what is namespace content? i know namespaces, and i know zone content.

> but the point of DNSSEC and the
> rather Byzantine structures we've built around establishing trust is that
> we don't have to believe you: you can see _everything_ having to do with
> creating the trust anchor and everything down to the leaf.

that point is not lost. dig +trace +dnssec works as expected. transitive trust is a real thing: 
if i trust where i got the data, because i verified the signatures, and i trust my own keys 
and signatures, then trading one set of trust anchors and signatures for another lost me 
no root-to-leaf trust, in the eyes of my target audience, who is in the case of my laptop, 
me.

> The only advantage(?) I see in your approach is that is allows (forces) you
> to play around with the namespace and re-sign.

it plays around with the zone content. not the namespace.

> Might be useful for neo maxi
> zoom DNSSEC nerd experimentation, however as mentioned previously, that
> doesn't strike me as something one might recommend lightheartedly.

note that since many enterprise networks do augment the iana namespace for internal 
use (creating problems like .home and .corp collisions), there is often a loss of dnssec 
integrity. my approach is one way to retain dnssec integrity in that situation, and has not 
been recommended lightheartedly.

> I strongly suspect you could come up with a solution that didn't use
> loopback but which also didn't require modifying the root zone, resigning
> it, and getting all relying devices to use both your NS hints and trust
> anchor. Like, perhaps running on a non-loopback address within your flock
> of laptop VMs?

i do run on a non-loopback address within my vm-local network. but unless i pirate the 
addresses of all the real root name servers (which i won't do), or re-sign the iana content 
with a local NS RRset and trust anchoe (which i am doing), then cache priming is going to 
cause my hints to be ignored by the other rdns servers local to me.

note that very few people will ever run multiple rdns servers inside a laptop. i am not 
suggesting that my use case is or will ever be common. but it works.

at a slightly broader scale, for instance my home network, it would also work. however, i'm 
using the yeti system here, so i can't experiment separately.

at enterprise scale, or metro or region or national scale, it would also work.

as you know i've suggested for some time now that iana build and sign a second root 
zone, having the same name space (which is regulated by icann and ietf) but having a 
different apex NS RRset (which i think can be regulated differently.) iana is in a perfect 
position to sign its zone using NS RR's and associated AAAA RR's that lead to addresses i 
*would* be willing to pirate. this is also called "the AS112 unowned anycast approach".

if i could slave an iana-signed zone on my laptop that already had pirateable IP addresses 
reached by hints and the apex NS RRset, i would not need Perl to do what i'm doing. and i 
would be using the iana trust anchor. and it would be a configuration that i would 
recommend for all internet users.

sadly, a lot of people saying that "the namespace and the zone are the same thing" has 
kept that door pretty well closed. so, here i am with Perl instead.

> specious, but I'm sure that's just me.

i am also sure that it's just you, because those principles were documented by the yeti 
project before it served its first request.

vixie

---

From: Bjørn Mork <[email-masked]>
Date: Fri, 07 Apr 2017 10:20:00 +0200

Just to avoid any confusion: Although I demonstrated the issue by
running BIND on my laptop only, the real usage scenario is resolver
service for a few million distinct administrative domains (aka
"customers").  Changing the trust anchor is not an option.

Bjørn

---

From: bert hubert <[email-masked]>
Date: Fri, 7 Apr 2017 10:23:26 +0200

> "customers").  Changing the trust anchor is not an option.

Perhaps https://lists.isc.org/mailman/listinfo/bind-users is a great place
to discuss BIND configurations and issues.

	Bert

---

From: Bjørn Mork <[email-masked]>
Date: Fri, 07 Apr 2017 10:50:09 +0200

> to discuss BIND configurations and issues.

Definitely.  Or even bind-workers when it comes to questions about the
reason for making static-stub zones recursive only.

The reason I ask here first, is because RFC 7706 includes a BIND
specific configuration example (as well as examples for other recursive
server software).  So before considering changing config or code, I
wanted to know the background of that example. Was there a real reason
for the obscure(?)  "static-stub" zone type, or was that just an
arbitrary choice?

My apologies if this is considered OT here. I will shut up now.

Bjørn

---

From: "Paul Hoffman" <[email-masked]>
Date: Fri, 07 Apr 2017 08:11:37 -0700

> My apologies if this is considered OT here. I will shut up now.

The contents of an RFC that went through this WG is not off-topic. 
Please do not shut up. (The fact that some people took your original 
message into "how to fix it in BIND" instead of your original topic is 
not relevant.)

See the archives of this mailing list starting around November 11, 2014, 
in the thread titled "New Version Notification for 
draft-wkumari-dnsop-root-loopback-01.txt".

--Paul Hoffman

---

From: Tony Finch <[email-masked]>
Date: Fri, 7 Apr 2017 18:56:57 +0100

> for the obscure(?)  "static-stub" zone type, or was that just an

The choice between "forward" and "static-stub" depends on whether the
target server offers recursive service or not. If it doesn't, then a BIND
resolver that is forwarding to the server can produce the wrong results,
because it is expecting a fully-resolved answer but doesn't get one.

It's a lot simpler to have a local copy of the root zone without any of
the view trickery, but then the resolver trusts the zone contents without
verifying them.

I think that if you are doing this on a production server, it would be
better to have external scripting to fetch and verify the root zone so you
can be sure you don't have an outage because the transfer got corrupted.

Tony.
-- 
f.anthony.n.finch  <[email-masked]>  http://dotat.at/  -  I xn--zr8h punycode
Rockall, Malin, Hebrides: West or southwest 5 or 6, occasionally 7 except in
Malin. Moderate or rough, occasionally very rough in north Hebrides.
Occasional rain or drizzle. Good, occasionally moderate.