Hello,

I'm trying to setup a knot-resolver 5.2.0-1 instance where all DNS queries should return a fixed IPv4 address, but the domains on an allow list that should return the real IPv4 address instead (by using a policy.PASS action), and the ones on a block list that should return a SRVFAIL response (by using a  policy.DROP action).

I'm using Response Policy Zones (RPZ), and with an explicit list of domains to redirect the traffic to (plus the explicit allow and block lists) everything works fine. However, when trying to redirect all queries by default by using a policy.all rule, the allow list does not longer work and all queries (but blocked ones) are responded with the fixed IPv4 address.

This is my '/etc/knot-resolver/kresd.conf':

-- turns off DNSSEC validation
trust_anchors.remove('.')

-- Network interface configuration
net.listen('127.0.0.1', 53, { kind = 'dns' })
net.listen('10.127.0.20', 53, { kind = 'dns' })

net.ipv6 = false

net.listen('/tmp/kres.control', nil, { kind = 'control'})

-- Load useful modules
modules = {
        'hints > iterate',  -- Load /etc/hosts and allow custom root hints
        'stats',            -- Track internal statistics
        'predict',          -- Prefetch expiring/frequent records
}

-- Cache size
cache.size = 100 * MB

policy.add(policy.rpz(policy.DENY_MSG('domain blocked'), '/etc/knot-resolver/blocklist.rpz', true))
--policy.add(policy.rpz(policy.ANSWER(), '/etc/knot-resolver/redirectlist.rpz', true))
policy.add(policy.rpz(policy.PASS(), '/etc/knot-resolver/allowlist.rpz', true))

policy.add(policy.all(policy.ANSWER({ [kres.type.A] = { rdata=kres.str2ip('10.127.0.10'), ttl=300 } })))


and these are the RPZ zones:

$ cat '/etc/knot-resolver/allowlist.rpz':
www.google.com 600 IN CNAME rpz-passthrough.
www.bing.com 600 IN CNAME rpz-passthrough.

$ cat /etc/knot-resolver/blocklist.rpz
examplemalwaredomain.com 600 IN CNAME .
*.examplemalwaredomain.com 600 IN CNAME .


Thus, www.examplemalwaredomain.com is blocked:


$ dig www.examplemalwaredomain.com @127.0.0.1

; <<>> DiG 9.16.1-Ubuntu <<>> www.examplemalwaredomain.com @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 26657
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1

;; QUESTION SECTION:
;www.examplemalwaredomain.com. IN A

;; AUTHORITY SECTION:
www.examplemalwaredomain.com. 10800 IN SOA www.examplemalwaredomain.com. nobody.invalid. 1 3600 1200 604800 10800

;; ADDITIONAL SECTION:
explanation.invalid. 10800 IN TXT "domain blocked"


And any other domain is redirected:

$ dig nic.cz @127.0.0.1

; <<>> DiG 9.16.1-Ubuntu <<>> nic.cz @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 60891
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;nic.cz. IN A

;; ANSWER SECTION:
nic.cz. 300 IN A 10.127.0.10


But the domains in the allow list are also redirected :(

$ dig www.google.com @127.0.0.1

; <<>> DiG 9.16.1-Ubuntu <<>> www.google.com @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 49284
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

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

;; ANSWER SECTION:
www.google.com. 300 IN A 10.127.0.10


Interestingly, if I add a policy.PASS rule with a list of explicit domains before the policy.all one, it does work properly:

policy.add(policy.suffix(policy.PASS, policy.todnames({'example.com', 'example.net'})))


$ dig www.example.com @127.0.0.1

; <<>> DiG 9.16.1-Ubuntu <<>> www.example.com @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 59322
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

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

;; ANSWER SECTION:
www.example.com. 120 IN A 93.184.216.34


Thus, it looks as if the allowed domains first hit the allow RPZ list, but then they hit the policy.ANSWER policy anyway. Is this the expected behaviour? Is there any way to implement such a default redirect but for a list of allowed or blocked domains?


Regards,
--Manuel