Blocking Adverts, Tracking & Malware With RPZ

A while back, I decided I wanted to prevent at least some adverts and tracking, but rather than on a device by device basis, I wanted to achieve this for all devices on the network. Those that know me and my recent work will understand that naturally, DNS blocking sprang to mind, as I’m already very familiar with RPZ.

Originally, I was consuming a bunch of lists with some code, manipulating the entries with some weighting and then outputting an RPZ for my servers to use. However, more recently I found Energized Protect, which has a load of different levels of blocking, and they provide the different levels in a variety of formats, helpfully including RPZ. So, I’ve been trialling their lists for a couple of weeks now.

As with any external feed, you need to be aware of either false positives being added to the list by the curator, as well as things they think should be on the list that you may disagree with. I was recently affected by this with my Amazon devices, whereby one or more domains critical to the correct functioning of the Echo devices found their way onto the block list I’m consuming. To be fair, I’m consuming one of the more extreme variants of the list, and so this was something I was aware could happen (although I admit, it didn’t spring straight to the front of my mind when troubleshooting over the weekend!).

So, let’s talk about how this works.

RPZ is a feature within some DNS servers that allows you to modify the responses given to clients depending on a number of different criteria. BIND from Internet Systems Consortium (ISC) was pretty much first to have RPZ, but others have varying levels of support for the main functionality. The BIND implementation allows you to define a policy that can consist of a number of layers. Within the policy you can override the entire contents of a layer, and within each layer you can have permit and deny actions based on a number of triggers. For this use case, we are interested in two of the triggers:

  • the name being looked up
  • the IP of the client making the request

The file we download from Energized Protect will form the main blocking layer, and we’ll override the entire layer at the policy level with NXDOMAIN. Arguably we could send queries to a web server with a block page, but not all things on the requesting end of this are browsers, and we can get logging from the BIND servers if we want to know what was blocked for a given client for the purposes of troubleshooting. Of course, we will want to be able to override these entries incase something gets on the list that we don’t want to be affected by (see above).

RPZ layers are DNS zone file format (see RFC1035 section 5 if you’re particularly interested in DNS master zone format, or for RPZ you can read the RFC draft (it’s not made it to a full RFC yet…)).

Because they’re DNS zone files, they can be transferred to other DNS servers using the normal notify and transfer mechanisms.

On my network here, there’s a central authoritative server, and then a pair of recursive servers that deal with actual client requests. I’ll get around to writing about the anycast set up of those in another article.

For the purposes of this article, the authoritative master is on, and the two slaves that are actually dealing with the client recursion are on and

Central Authoritative Server

We’ll start with the central authoritative server. There are two bits to this, periodically fetching the RPZ, and serving it to the slave servers.

All of the scripts I talk about below, can be found in the Bitbucket repository. The code is fairly straight foward, but of course, drop me a line if you have questions.

Energized Protect update their feeds every 6 hours, and so there’s no need to poll them any more often than that. Further, the updateblockrpz script keeps an unchanged copy of the downloaded file so that wget can do timestamping and only download the file if it has actually changed on the server.

There are two further scripts, both of which allow you to manipulate an override layer in the policy. The first, rpz-override, allows you to add and remove domains from the override, either to add things you want to block, or allow things blocked in the block layer. The second script, rpz-override-client, allows you to base the action on the client IP instead of on the queried name. Both of these are written in Perl, and more specifically are built on the Net::DNS module to send the changes into the server via a dynamic update.

Next, let’s look at how we configure the server. A base understanding of BIND configuration is assumed.

First, we’ll need to config it to master the two zones, permit dynamic updates on the override zone, and permit slaves to transfer them. Depending on your distro, the location of your named.conf may vary, and also whether it’s a single file or split out with includes. I’ll just include generic config here to try and cover as many bases as possible.

zone "block" {
	type master;
	file "rpz/block";
	notify explicit;
	also-notify {;;

zone "override" {
	type master;
	file "rpz/override";
	notify explicit;
	also-notify {;;
	allow-update {; ::1; };

Normal rules apply here; config like also-notify can inherit from the main options section, or can be overridden per zone like we have done here (line 4 to force just the specific entries listed in lines 5-7). We do the same again with the override zone (lines 14 & 15-17), but here we also add the allow-update (line 19), in order to permit the maintenance scripts to work. If your main options section has allow-update specified, you will need to specify allow-update { none; }; in addition for the block zone, to prevent BIND from keeping journals for the zone. If you need other config that will lead to journals, such as ixfr-from-differences, for example, then the updateblockrpz script may need a tweak to freeze and thaw the block zone instead of just reloading the update.

I run the updateblockrpz script from cron at a randomly selected minute after the hour, every 6 hours and lazily capture the output to a tmp file for troubleshooting purposes. Yes, I should likely update this to log properly!

17 */6 * * * /usr/local/bin/updateblockrpz >/tmp/updateblockrpz.tmp

Slave Servers

Having got the RPZ zones set up on the master, we can turn our attention to the slaves that are actually handling the queries from the clients on the network.

First, we’ll slave the RPZ zones from the master:

masters rpzmasters {; };
zone "block" {
    type slave;
    file "rpz/block";
    masters { rpzmasters; };
zone "override" {
    type slave;
    file "rpz/override";
    masters { rpzmasters; };

…and next, we’ll define the policy that’ll apply to the clients:

options {
	response-policy {
		zone "override" policy given;
		zone "block" policy nxdomain;
		break-dnssec yes
		qname-wait-recurse no
		max-policy-ttl 900

As we mentioned before, we’re overriding the block layer at the policy level, forcing anything in that layer to result in a NXDOMAIN response. The override layer is left as given so that the actions in the layer carry. The policy is evaluated top to bottom, with the first action encountered causing an exit from policy, hence the override layer, which could be whitelisting something that’s in the block layer, is listed first.

RPZ Entries

Lastly, we’ll just briefly cover different types of record that you might want to put in the override layer; the scripts will help you mostly with this, but for those that are interested, here’s a little more detail.

Broadly, as we discussed earlier, we’re interested in two main triggers; the name being looked up, and the client making the query.

Entries that affect the domain name being looked up broadly look like this: 300 IN CNAME <action>.

Where <action> is one of the following:

  • rpz-passthru (whitelist)
  • rpz-drop (drop the query – quite unfriendly, will cause the client to wait for a timeout)
  • . (a literal dot, which will cause a NXDOMAIN response)

It’s also possible to do something like this, if you want to override to a block page or honeypot, for example: 300 IN A

…and of course, any of those can be prefixed with *. to cause the action to apply to everything within the bailiwick of

Entries that affect the client look a little different. Firstly, they’re reversed, a bit like zones but they’re prefixed by an additional item specifying the CIDR notation. So, if you want to (using the actions from above) whitelist all queries from single IP, you’d do: 300 IN CNAME rpz-passthru.

However, if you wanted to block the upper /25, you’d do this (note use of the subnet IP, you need to specify the correct subnet boundary IP): 300 IN CNAME rpz-passthru.

Other Trigger Types

We’ve not talked about the other triggers, but briefly, you can also trigger actions based on:

  1. rpz-ip – the IP addresses that are returned in the answer to a query.
  2. rpz-nsdname – the domain name of the nameservers that are authoritative for the domain in the query.
  3. rpz-nsip – the IP addresses of the nameservers that are authoritative for the domain in the query (ie: what the names in (2) resolve to).

Type 1 can lead to data exfiltration, which, if you’re blocking a domain because you want to prevent exfiltration, defeats the object. If you put type 1 or type 3 in a layer, then if BIND reaches that layer as it works through the policy, it will do the recursion to the authority for the zone in order to work out if the trigger is a match. If you’re worried about data exfiltration, you MUST put the domains you’re blocking for that purpose in a RPZ layer above the first layer that includes type 1 or type 3 entries, then BIND will execute your configured action without any recursion.

…but what about DNSSEC

If you’ve read all that, and you’re thinking to yourself “hey, but surely returning modified answers will break DNSSEC” then you’re right. Your client machine stub-resolvers will trust your DNS resolver, and so won’t notice, but if you’re pointing a validating resolver at this setup, you’ll need to make sure you keep the break-dnssec yes; option I included above. Possibly counter-intuitively, this causes your RPZ server to lie to the downstream validating resolver. If is DNSSEC signed, and is on your block list, the downstream validating resolver will usually be sending queries with CD set instead of trusting your validation, expecting your server to send all the required DS, DNSKEY, etc. with break-dnssec yes; the RPZ server will lie; it’ll pretend isn’t signed and will strip all DNSSEC data in responses to the downstream resolver(s).

It’s important to note that this has an edge case. Let’s imagine you have, which is signed, and is not being modified by your policy at all. Now let’s imagine you have which is not at a zone split boundary, and is just a regular non-delegation entry in If you add specifically to your RPZ for modification, the server can’t deal with lying about just that entry, and the downstream validator will spot the lie, returning SERVFAIL to its downstream client(s).


Automatic Key Rolling

I recently moved my test domains onto a separate DNS master so that I could more freely tinker with these domains without risk to my regular stable domains.

I use catalog zones (maybe this is for another post!) to distribute the zones to save myself the bother of having to configure all the slaves, particularly since I’ve recently started spinning up an experimental anycast network of virtual machines, and so I added a second catalog to my slave servers, and away we went.

I’m a keen user of debian, and so I built the test master on ‘sid’ so I could run bleeding edge.

The benefit, primarily, was that whereas Debian 10 gets BIND 9.11.5, sid gets 9.16.8 (at the time of writing) as well as a newer version of openssl, meaning I could sign a zone with algorithm 16, ED448. DS digest algorithm 4 (SHA-384) is also supported.

The biggy for me, though, and the main driver for having the newer version of BIND, was dnssec-policy; getting BIND to automatically roll your keys.

I’m not aware of an ability in this version to support either a hook to run a script to interact with your registrar to update a DS record when your KSK rolls, nor an ability to automate CDS or CDNSKEY, but they’ll be coming at some point in the future.

I decided to test this by auto-rolling my ZSK, so, I added the following to /etc/bind/named.conf.options :

dnssec-policy normal {
	dnskey-ttl PT1H;
	keys {
		ksk lifetime unlimited algorithm ecdsa384;
		zsk lifetime 90D algorithm ecdsa384;
	max-zone-ttl P1D;
	parent-ds-ttl P1D;
	parent-propagation-delay PT1H;
	parent-registration-delay P1D;
	publish-safety PT1H;
	retire-safety PT1H;
	signatures-refresh P5D;
	signatures-validity P2W;
	signatures-validity-dnskey P2W;
	zone-propagation-delay PT5M;

You don’t need to create initial keys or anything; BIND will do all the key juggling automatically, and will store them wherever you set key-directory to in your options section.

It doesn’t matter how you add your domains; I use rndc addzone as I have scripts that automate this and adding the zone to the catalog (again, that’ll be in another post at some point), the key thing being you just specify the policy in the zone. I’m also using inline-signing; whether you do will depend on your setup. Here’s a sample:

zone "" {
    type master;
    file "/path/to/";
    dnssec-policy "normal";
    inline-signing yes;

…and as if by magic, rndc reconfig, and the keyfiles are created, and the zone signed.


A New Key…

Further to my post on ICANN’s automated KSK testlab, ICANN generated a new key on the 19th, and added it to the test zone that we’re using, and we can see it below:

$ dig +multiline @::1 dnskey

; <<>> DiG 9.9.5-9+deb8u6-Debian <<>> +multiline @::1 dnskey
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 36605
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1

; EDNS: version: 0, flags:; udp: 4096

				) ; KSK; alg = RSASHA256; key id = 3934 60 IN	DNSKEY 256 3 8 (
				) ; ZSK; alg = RSASHA256; key id = 19401 60 IN	DNSKEY 257 3 8 (
				) ; KSK; alg = RSASHA256; key id = 19741

;; Query time: 285 msec
;; SERVER: ::1#53(::1)
;; WHEN: Tue Mar 21 20:17:12 GMT 2017
;; MSG SIZE  rcvd: 905

Key 19741 is a new KSK in the zone.

If you look in managed-keys.bind (I’m running Debian, and so that’s in /var/cache/bind/) you’ll now see the new key is visible while BIND is observing the new key. RFC5011 defines the period that the resolver must observe the new key for as either at least two times the TTL of the keyset containing the new key, or 30 days; whichever is the longer.

I’m cheating, slightly, and taking a look at managed-keys.bind from a different server, because my Debian box is running BIND 9.9.5, whereas I have access to a 9.11 box; you’ll see why below:

$ cat /var/named/managed-keys.bind
$TTL 0	; 0 seconds
@			IN SOA	. . (
				284        ; serial
				0          ; refresh (0 seconds)
				0          ; retry (0 seconds)
				0          ; expire (0 seconds)
				0          ; minimum (0 seconds)
			KEYDATA	20170322222551 20170210095625 19700101000000 257 3 8 (
				) ; KSK; alg = RSASHA256; key id = 19036
				; next refresh: Wed, 22 Mar 2017 22:25:51 GMT
				; trusted since: Fri, 10 Feb 2017 09:56:25 GMT KEYDATA 20170321232551 20170317172529 19700101000000 257 3 8 (
				) ; KSK; alg = RSASHA256; key id = 3934
				; next refresh: Tue, 21 Mar 2017 23:25:51 GMT
				; trusted since: Fri, 17 Mar 2017 17:25:29 GMT
			KEYDATA	20170321232551 20170418002534 19700101000000 257 3 8 (
				) ; KSK; alg = RSASHA256; key id = 19741
				; next refresh: Tue, 21 Mar 2017 23:25:51 GMT
				; trust pending: Tue, 18 Apr 2017 00:25:34 GMT

On my 9.9.5 server, I don’t have the helpful comments. We can see, helpfully, that the root key (19036), and our original testlab key (3934) are trusted. We can also see that the server observing key 19741 because the instead of trusted since we can see trust pending

If you remember from the original post, whereas BIND keeps a track in managed-keys.bind, Unbound tracks the metadata in the external file we specified with auto-trust-anchor-file:. The file has been updated in a similar way to BIND’s:

$ cat /var/lib/unbound/
; autotrust trust anchor file
;;id: 1
;;last_queried: 1490135144 ;;Tue Mar 21 22:25:44 2017
;;last_success: 1490135144 ;;Tue Mar 21 22:25:44 2017
;;next_probe_time: 1490138421 ;;Tue Mar 21 23:20:21 2017
;;query_failed: 0
;;query_interval: 3600
;;retry_time: 3600	60	IN	DNSKEY	257 3 8
;{id = 3934 (ksk), size = 2048b} ;;state=2 [  VALID  ] ;;count=0 ;;lastchange=1489997718 ;;Mon Mar 20 08:15:18 2017	60	IN	DNSKEY	257 3 8
;{id = 19741 (ksk), size = 2048b} ;;state=1 [ ADDPEND ] ;;count=34 ;;lastchange=1489997718 ;;Mon Mar 20 08:15:18 2017

In line 15, we see the original key (3934) with a status of VALID, whereas in line 22 we see the newly spotted key 19741 is ADDPEND.

What’s next…?

Now we wait; 30 days, and as long as the key is observed throughout, the key should become trusted at the end of this…


Rolling, rolling, rolling…


In October 2017, ICANN are going to roll the key signing key in the root of the DNS.

If you’re not technical and don’t know what I just said, this post isn’t for you.

If, however, you run a validating recursive resolver, read on…

In October (the 11th to be exact), the key will roll and you’ll need to have done one of two things…

  1. Update your root trust anchor manually
  2. Check your resolver is RFC5011 compliant.

But first, a little…


So you know how DNSSEC works…

…you sign a zone. More specifically, you generate two keys, a key to sign the zone (ZSK), and a key to sign the keys (KSK). The zone gets bigger because for each record set, a signature is generated and added (RRSIG records). The public part of the keyset is also added to the zone (DNSKEY records). Some form of proof of non-existance is added (NSEC or NSEC3).

Next, once the keys and signatures have made it to all of the nameservers for the zone, you generate a delegated signer record (DS) from the KSK, and you publish that in the parent. The parent then signs the DS record, and hey presto, your chain of trust is made.

So, where’s the DS record for the root… To make this chain of trust work, resolvers that want to validate the DNSSEC chain of trust need a starting point in the root…

Your resolver has a trust anchor for the root. Depending on what you’re using for a resolver, this will either be the DS of the root KSK, or the public part of the KSK.

Your resolver will have this built in, but then, if configured correctly, will use an automatic mechanism to keep that key up to date and roll it when required.


RFC5011 defines how a resolver can automatically update a trust anchor for a zone.

So that you can check whether your resolver will follow this process, ICANN have an automated testbed for the KSK roll, which I encourage you to look at.

ICANN’s Automated Test

Each week, they create a new zone, and they sign it with a set of newly generated keys. Purposefully broken DS records are published in the parent zone, so that a normal validating resolver will SERVFAIL (because validation fails).

By adding a trust anchor to your resolver, the zone will validate.

If correctly configured, your resolver will now look for new key signing keys, and will observe them, and use them as per RFC5011.

So, lets take a look at this. Before I add a trust anchor, I can check that the zone doesn’t validate:

$ dig @::1 soa

; <<>> DiG 9.9.5-9+deb8u6-Debian <<>> @::1 soa
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 39100
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

; EDNS: version: 0, flags:; udp: 4096

;; Query time: 1908 msec
;; SERVER: ::1#53(::1)
;; WHEN: Mon Mar 20 13:22:57 GMT 2017
;; MSG SIZE  rcvd: 77

We can see in line 7, that we have a SERVFAIL response.

This server is running BIND. So, first we check that the server is configured manage keys using RFC5011:

options {
    dnssec-validation auto;

If you’re just adding this, don’t forget to rndc reconfig

Trust Anchor

Now, we need to add a trust anchor:


managed-keys { initial-key 257 3 8

This is added in your named.conf file.

Once again, don’t forget to rndc reconfig


If you’re running Unbound, then you can add the DNSKEY or DS records to a file in a location that Unbound can read and write to (so, somewhere like /var/lib/unbound/ and then add a auto-trust-anchor-file line in the server: section of your unbound.conf file.

cat /var/lib/unbound/ IN DS 3934 8 1 47AA8AAF4D75B3D9C58448F241F793EBC4977821 IN DS 3934 8 2 0D27F2E6EA9CA548F1896A71FB07CED86074D3462F2A720D6177F3C5CEC15F0D

Note; the file doesn’t look like this once you’ve told Unbound about it, as it uses the file to store metadata related to the RFC5011 process.

    auto-trust-anchor-file: "/var/lib/unbound/"

After adding those, you’ll want to unbound-control reload to pick up the changes.


$ dig @::1 soa

; <<>> DiG 9.9.5-9+deb8u6-Debian <<>> @::1 soa
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30413
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 3

; EDNS: version: 0, flags:; udp: 4096

;; ANSWER SECTION: 60 IN	SOA 1489968062 3600 600 86400 60



;; Query time: 428 msec
;; SERVER: ::1#53(::1)
;; WHEN: Mon Mar 20 13:44:24 GMT 2017
;; MSG SIZE  rcvd: 181

This time, we can see that on line 7, we have a NOERROR response, and on line 8, we can see that we have ad in the flags.

What’s next…

Now, we wait. The next step is that ICANN’s automated test lab will generate and publish a new KSK into the zone on the 19th.


DNSSEC BIND Configuration Summary & Cool Stuff


With the recent signing of the root, I’ve discovered a sudden interest in DNSSEC, and decided to have a go myself to aid my understanding of it.

This article is written as an aid-memoir to me, and summary of the bits I’ve read. Of course, I’ve provided links to the whole blog entries I found the information in, in case you want to read more than I’ve written.

Whilst the root is signed, only certain TLDs are signed, and so if you want the full chain of trust experience, you want a domain with a signed TLD.

At the moment, .uk is signed, but etc are not, so that rules them out. .net is scheduled for around Nov 2010, and .com sometime around March 2011.

.org, however, is already signed, and so I thought I’d grab one to play with.

Not All Registrars Are Equal

I used my regular registrar, and registered to go with my collection of .com and versions.

This was my first sticking point, because their upstream (Tucows) aren’t accredited for DNSSEC yet (and, it would appear, have no plans on doing so).

I’d need my domain to be with a registrar that is accredited.

My registrar helpfully supplied me with a list of registrars that are, so I could choose one and either register a domain there, or move my new one.

I registered another .org to add to another set, this time with GoDaddy. They’re on the list.

Signing The Zone

I had told GoDaddy that I wanted to use my own nameservers during sign up, and so after creating a regular zonefile for bind, I had a look through the blog entry I found at

Essentially, the steps are (all completed whilst IN the zonefile directory):

  • Generate a zone signing key (ZSK) :
    dnssec-keygen -a RSASHA1 -b 1024 -n ZONE
  • Generate a key signing key:
    dnssec-keygen -a RSASHA1 -b 2048 -n ZONE -f KSK
  • Concatenate the created public keys into the zone file:
    cat*.key >>
  • Sign any child zones first: 
    dnssec-signzone -N INCREMENT
  • Concatenate the DS records for the child into the parent zone:
    cat >>
  • Sign the zone:
    dnssec-signzone -N INCREMENT

Generating the ZSK and KSK took ages on my Atom 330 dedicated server, and so I can recommend a good book, or some other talk while you wait for this to finish!

Like the child zone signing, you will get DS records for the parent zone. These need to be supplied to your registrar to maintain the chain of trust. GoDaddy has a nice interface for submitting these, you just need to know what the different bits of the DS records are. They’re detailed in RFC4034 but to save you some time, and sanity….

Your DS Records 86400 IN DS 60485 5 1 2BB183AF5F22588179A53B0A98631FAD1A292118

The first four text fields specify the name, TTL, Class, and RR type (DS).

Value 60485 is the key tag for the corresponding “” DNSKEY RR

Value 5 denotes the algorithm used by this “” DNSKEY RR.

Value 1 is the algorithm used to construct the digest.

The rest of the RDATA text is the digest in hexadecimal.

Your Caching Resolver

Your caching resolver will need DNSSEC enabled for queries. I added the following to my bind server’s options section:

dnssec-enable yes;
dnssec-validation yes;

Your System Resolver

With your local system pointed at your caching resolver, it would appear you’ll need EDNS0 enabled. This is achieved by adding the following option to your /etc/resolv.conf.

options edns0

This appears to be supported on newer versions of libresolv – my Debian 5 system doesn’t appear to support it, whereas my Ubuntu 10.04 system does.

So, on to the cool stuff… SSH

..and so, at last, on to the cool stuff.

Given you can now trust DNS, you can do something interesting. Rather than need to verify all the SSH fingerprints, you can store them in DNS and have your SSH client automagically verify that all is well. I followed a set of instructions I found at, and as before, here’s a summary. Run the following two comands on each host you’d like to generate fingerprints for:

ssh-keygen -r `hostname`. -f /etc/ssh/ssh_host_rsa_key
ssh-keygen -r `hostname`. -f /etc/ssh/ssh_host_dsa_key

This will generate two SSHFP records that you will need to include in the zonefile, then you can re-sign and re-publish the zone.

In my case, the records generated were for variants of the hostname, but I found no problems changing them to .org

You’ll then need to persuade SSH to perform verification using DNS. I did this by adding the relevant option to /etc/ssh/ssh_config

VerifyHostKeyDNS yes

There, you’re done. You should now be able to ssh to the host(s) concerned without needing to manually verify the fingerprints.