Avatar
Håkan is a software engineer and DNS fanatic from Sweden. He occasionally blogs about software, networking and related topics.

PowerDNS Recursor RPZ with cacheable results

RPZ is a standardized and fairly convenient way of distributing and applying blocklists in standard nameserver software. For instance, you may have lists with domains used for malware, tracking, ads, etc, to block, much like specialized nameserver software such as PiHole does.

Blocking names in DNS is not unproblematic, it is not a fool-proof way of blocking badness, it does not play nice with DNSSEC (however, the goal is to block, so even triggering a validation failure achieves the end goal), and can be used in abusive ways.
All that said, it can still certainly serve a purpose as a relatively unintrusive yet convenient layer of protection, and as long as it is used as a means of empowering the user, I at least don’t see an ethical problem.

Setting up RPZ in PowerDNS Recursor is pretty straightforward through its Lua-based configuration. See the below example:

recursor.conf:

lua-config-file=/etc/powerdns/recursor.lua
lua-dns-script=/etc/powerdns/dns.lua
extended-resolution-errors=yes

recursor.lua:

local rpz_zones={ "adblocknocoin.hoshsadiq.rpz.qw.se", "adguard.firebog.rpz.qw.se",
"adservers.anudeepnd.rpz.qw.se", "easyprivacy.firebog.rpz.qw.se", "hole.certpl.rpz.qw.se",
"hosts.stevenblack.rpz.qw.se", "kadhosts.stevenblack.rpz.qw.se", "notrack-blocklist.quidsup.rpz.qw.se",
"pgl.yoyo.rpz.qw.se", "prigent-malware.firebog.rpz.qw.se", "rpilist-malware.firebog.rpz.qw.se",
"rpilist-phishing.firebog.rpz.qw.se", "rpz.oisd.nl.rpz.qw.se", "simplead.disconnectme.rpz.qw.se",
"simpletracking.disconnectme.rpz.qw.se", "urlhaus.abusech.rpz.qw.se" }

for i,v in ipairs(rpz_zones) do
    rpzPrimary({ "139.162.158.28", "2a01:7e01::f03c:91ff:fee4:c68b" }, v, { extendedErrorCode = 15,
extendedErrorExtra = "Blocked by policy " .. v, dumpFile = "/tmp/db." .. v, seedFile = "/tmp/db." .. v,
axfrTimeout=60 })
end

This defines a list of RPZ zones to AXFR/IXFR from a server hosting some such zones, also defining an extended error code to include in responses for blocked names.

What you will find, however, is that when PowerDNS Recursor applies an RPZ policy, it responds with empty NXDOMAIN messages (no SOA), while negative caching is done precisely based on the SOA record supposedly found in the AUTHORITY section of negative responses.
This makes for uncacheable responses (or at least with no control at all over any caching behavior that may exist), however, one can use the Lua hooks for changing responses to add a made-up SOA record that at least allows for specifying a TTL to control cache behavior.

dns.lua:

function nxdomain(dq)
    return fixrpz(dq)
end
function nodata(dq)
    return fixrpz(dq)
end

function fixrpz(dq)
    if dq.appliedPolicy.policyKind == pdns.policykinds.NODATA or dq.appliedPolicy.policyKind ==
pdns.policykinds.NXDOMAIN then
        pdnslog("Intercepting RPZ NODATA/NXDOMAIN for: "..dq.qname:toString())
        if dq.appliedPolicy.policyKind == pdns.policykinds.NODATA then
            dq.rcode = pdns.NOERROR
        else
            dq.rcode = pdns.NXDOMAIN
        end
        dq:addRecord(pdns.SOA, "blocked.invalid. blocked.invalid. 1 1 1 1 600", 2, 600)
        -- 2 = AUTHORITY - https://docs.powerdns.com/recursor/lua-scripting/dnsrecord.html#DNSRecord.place

        dq.appliedPolicy.policyKind = pdns.policykinds.NoAction
        return true
    end
    return false
end

This overrides NXDOMAIN/NODATA responses that were introduced by RPZ so that they include an SOA record.

all tags