folbricht / routedns

DNS stub resolver, proxy and router with support for DoT, DoH, DoQ, and DTLS
BSD 3-Clause "New" or "Revised" License
479 stars 63 forks source link

Set RecursionAvailable in static responses #404

Closed folbricht closed 3 months ago

folbricht commented 3 months ago

Sets RecursionAvailable flag in static responses (static-responder, blocklists, etc) when the query requested it. This is to avoid the warning

;; WARNING: recursion requested but not available

Ref https://github.com/folbricht/routedns/pull/403

Anuskuss commented 3 months ago

Actually now that I've read up on what recursion actually is could you revert this please?

When a DNS server receives a non-recursive request or a request from a client that it is not willing to perform recursion for, it typically responds immediately with whatever local data it has available at the time without doing any additional processing.

The warning sucks of course but I think the ra bit is necessary for differentiating between an actual NXDOMAIN and a block (especially for EDE-unaware clients). Your call though; if you think the warning is ugly leave it as it is :)

folbricht commented 3 months ago

You might be right. Do you happen to know what other blocking DNS services do in this case? I'll take a look

folbricht commented 3 months ago

Both, cloudflare and opendns seem to be setting the RA (Recursion Available) bit in responses.

$ dig phishing.testcategory.com @1.1.1.3

; <<>> DiG 9.18.28 <<>> phishing.testcategory.com @1.1.1.3
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 49904
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; EDE: 16 (Censored)
;; QUESTION SECTION:
;phishing.testcategory.com. IN  A

;; ANSWER SECTION:
phishing.testcategory.com. 60   IN  A   0.0.0.0

;; Query time: 13 msec
;; SERVER: 1.1.1.3#53(1.1.1.3) (UDP)
;; WHEN: Mon Aug 19 08:23:11 CEST 2024
;; MSG SIZE  rcvd: 76
$ dig @208.67.222.222 www.internetbadguys.com

; <<>> DiG 9.18.28 <<>> @208.67.222.222 www.internetbadguys.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 39484
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1410
;; QUESTION SECTION:
;www.internetbadguys.com.   IN  A

;; ANSWER SECTION:
www.internetbadguys.com. 0  IN  A   146.112.61.108

;; Query time: 14 msec
;; SERVER: 208.67.222.222#53(208.67.222.222) (UDP)
;; WHEN: Mon Aug 19 08:31:00 CEST 2024
;; MSG SIZE  rcvd: 68
cbuijs commented 3 months ago

RA bit in an answer is set if the client request contains the RD bit (Recursion Desired). When RD is not set, only local answers should be provided (if any), and RA should not be set.

RD is almost always set by any stub/client, providing RA always doesn't hurt is my experience.

Not providing RA might have a positive effect to stop generating unwanted consequent requests when a NXDOMAIN (or local answers are provided), is received by a client/stub for example. Optimizing traffic potentially (depends on the client/stub).

cbuijs commented 3 months ago

Also... When providing local or generated answers, you might set the AA bit (Authoritative Answer) instead of messing with the RD bit.

folbricht commented 3 months ago

It's not changing the RD bit. The change was to set RA in the if RD is set in blocked or static responses. Should AA be set as well in those responses? Cloud services don't appear to include that based on the example responses above.

cbuijs commented 3 months ago

No. Only set the RA bit (answer) when the RD bit is set (request). As you provide Recursion as a DNS forwarder.

When RD is not set, only provide local/generated answers and answer with NXDOMAIN when you cannot or have no answer.

The AA bit might be a better way as an indicator in my opinion.

Anuskuss commented 3 months ago

Do you happen to know what other blocking DNS services do in this case?

Pi-hole seems to expect NXDOMAIN + no ra (or 0.0.0.0 via static-responder). And apparently that's based on the behaviour of Cisco Umbrella (although I couldn't confirm that myself).

Edit: Also this: https://github.com/NLnetLabs/unbound/commit/392c1f0f5495cb0cc1170e126945caa7e988f47d

DL6ER commented 3 months ago

apparently that's based on the behaviour of Cisco Umbrella (although I couldn't confirm that myself).

My memory tricked me. I found e-mail (as in: non-public) conversation with Quad9 employees back from 2018 where they described that a blocked query can be detected by the criterion NXDOMAIN = 1, AD = 1, RA = 0. Later (2019), they revised this and asked us to ignore AD, making the condition NXDOMAIN, RA = 0.

Looking at their FAQ page, it's unclear:

What will I see if a domain is blocked by Quad9?

Users receive an “NXDOMAIN” response if a site is blocked; the end user system acts as if the domain does not exist. This behavior is subject to change in the future to point individual requests to a Quad9 operated information page, informing the user of the threat mitigation and additional information.

As they are not telling us which blocking lists they are using, finding a domain where we know it is blocked may be tricky. However, if someone is willing to invest the time, this page may be useful.


What I've found - on their Pi-hole instructions - is:

Domains which are blocked by Quad9 will record Blocked (external, NXRA) in the Status column of the Query Log

which exactly corresponds to the conditions I described above (NXDOMAIN, RA = 0 from upstream). However, we obviously don't know if this part of their documentation might be dating back to 2019, too, and is not accurate any longer.

Anuskuss commented 3 months ago

where they described that a blocked query can be detected by the criterion NXDOMAIN = 1, AD = 1, RA = 0

That seems to be the case:

$ dig @9.9.9.9 www.internetbadguys.com | grep -m2 'status\|flags'
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 26268
;; flags: qr rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

Both, cloudflare and opendns seem to be setting the RA (Recursion Available) bit in responses.

Also just realized that both of your examples return IP addresses so the ra bit is expected. AdGuard DNS seems to be another such case.

cbuijs commented 3 months ago

I think there are two behaviours:

In both cases when the answer is locally generated (by RouteDNS), it doesn't really matter that the RA bit is set or not. it becomes informative and doesn't really matter for DNS resolution at that point.

In all other cases it should follow suit of the dns client. When RD is set, and the answer is retrieved, the RA bit has to be set. When RD is not set (by the client in the query), recursion is not requested, and forwarding or resolution down the tree is not necessary (and shouldn't be done), and RA is just informative at this point as well.

As indicator the AA bit can be set when generating the answer (return-code-only or spoofed), assuming authority of the answer/domain (we are so for the "blocking" and generating the answer). Adding a SOA record to the ns section of the answer, with some (fake) info to elaborate the answer works as well.

I am actually doing this by default when blocking using a static-responder, adding a SOA record with some information that points out it is blocked (example):

[groups.null-a]
type = "static-responder"
rcode = 0
answer = [". 666 IN A 0.0.0.0"]
ns = [".  666 IN SOA blocked blocked 666 666 666 666 666"]

(Using a TTL of 666 as an easy indicator as well)

Anuskuss commented 3 months ago

Adding a SOA record to the ns section of the answer, with some (fake) info to elaborate the answer works as well.

That's what EDE is for. This discussion was about if the ra bit should be returned if the answer was static/empty (when blocking). Based on the observations above it should be yes when static (0.0.0.0) and no when empty (NXDOMAIN). But maybe it should always return ra if rd was sent and Quad9 is just an outlier.