New in version 1.10.0: Intial release with QNAME and Response IP Address triggers
Changed in version 1.14.0: Full set of RPZ triggers and actions are supported
Response Policy Zones¶
Response Policy Zones (RPZ) is a mechanism that makes it possible to define your local policies in a standardised way and load your policies from external sources.
Unbound has support for local-zone and local-data. This makes it possible to
give a custom answer back for specified domain names. It also contains the
respip module which makes it possible to rewrite answers containing specified
IP addresses. Although these options are heavily used by users, they are Unbound
specific. If you operate multiple resolvers from multiple vendors you have to maintain
your policies for multiple configurations, which all will have their own syntax.
Using the Unbound specific configuration also makes it challenging to consume
policies from external sources.
To get these external sources to work manually, you have to fetch the external policies in the offered format, reformat it in such a way that Unbound will understand, and keep this list up-to-date, for example using unbound-control(8).
To automate this process in a generic, standardised way, Response Policy Zones (RPZ) is a policy format that will work on different resolver implementations, and that has capabilities to be directly transferred and loaded from external sources.
We’ll first discuss the different policies and RPZ actions with examples, and then show how to implement RPZ in a configuration.
RPZ policies are formatted in DNS zone files. This makes it possible to easily consume and keep them to up-to-date by using DNS zone transfers. Something that Unbound is already capable of doing for its auth-zone feature.
Each policy in the policy zone consists of a trigger and an action. The trigger describes when the policy should be applied. The action describes what action should be taken if the policy needs to be applied. Each trigger and action combination is defined as a Resource Record (RR) in the policy zone. The owner of the RR states the trigger, the type and RDATA state the action.
Unbound supports all the RPZ policies described in the RPZ internet draft:
Description and example
The query name:
The IP address of the client:
response IP address in the answer:
The nameserver name:
The nameserver IP address:
Note that the IP address encoding for RPZ triggers in the IN-ADDR.ARPA naming
192.0.2.24 will be written as
In the implementation step we will go trough all the triggers.
Aside from RPZ triggers, RPZ also specifies actions as a result of these triggers. Unbound currently supports the following actions: NXDOMAIN, NODATA, PASSTHRU, DROP, Local Data, and TCP-only.
The Local Data action responds with a preconfigured resource record. Queries for types that do not exist in the policy zones will result in a NODATA answer.
Other RPZ actions that are supported by Unbound are the NXDOMAIN, NODATA, PASSTHRU, DROP and TCP-Only actions. All of these actions are defined by having a CNAME to a specific name.
The CNAME targets for the other RPZ actions are:
RR type and RDATA
The NODATA action returns a response with no attached data. The DROP action ignores (drops) the query. The TCP-Only action responds to the query over TCP. The PASSTHRU action makes it possible to exclude a domain, or IP address, from your policies so that if the PASSTHRU action is triggered no other policy from any of the available policy zones will be applied.
How to use RPZ with Unbound¶
The RPZ implementation in Unbound depends on the
respip module, this module
needs to be loaded using module-config:.
Each policy zone is configured in
Unbound using the rpz: clause.
The full documentation for RPZ in Unbound can be found in the
A minimal configuration with a single policy zone can look like the following,
where additional elements can be uncommented:
server: module-config: "respip validator iterator" rpz: # The name of the RPZ authority zone name: rpz.nlnetlabs.nl # The filename where the zone is stored. If left empty zonefile: rpz.nlnetlabs.nl # The location of the remote RPZ zonefile. # url: http://www.example.com/example.org.zone (not a real RPZ file) # Always use this RPZ action for matching triggers from this zone. # Possible action are: nxdomain, nodata, passthru, drop, disabled, # and cname. # rpz-action-override: nxdomain # Log all applied RPZ actions for this RPZ zone. Default is no. # rpz-log: yes # Specify a string to be part of the log line. # rpz-log-name: nlnetlabs
In above example the policy zone will be loaded from the file
rpz.nlnetlabs.nl. An example RPZ file with all the triggers and actions
looks like this:
$ORIGIN rpz.nlnetlabs.nl. # QNAME trigger with local data action example.com.rpz.nlnetlabs.nl. TXT "trigger for example.com" *.example.com CNAME . # IPv4 subnet (192.0.2.0/28) which drops clients and IPv6 subnet (2001:db8::3/128) which is not subject to policy 126.96.36.199.192.rpz-client-ip CNAME rpz-drop. 128.3.zz.db8.2001.rpz-client-ip CNAME rpz-passthru. # Clients at 188.8.131.52 only get responses over TCP. 184.108.40.206.rpz-client-ip CNAME rpz-tcp-only. # Fills the responses for these queries with NXDOMAIN and the correct # answers respectively 220.127.116.11.192.rpz-ip CNAME . 18.104.22.168.192.rpz-ip CNAME rpz-passthru. # Answers queries for the nlnetlabs.nl nameserver with NXDOMAIN ns.nlnetlabs.nl.rpz-nsdname CNAME . # Drops queries for the nameserver at 192.0.2.0/24 subnet 22.214.171.124.192.rpz-nsip CNAME rpz-drop.
It is also possible to load the zone using DNS zone transfers. Both AXFR and IXFR is supported, all additions and deletion in the zone will be picked up by Unbound and reflected in the local policies. Transferring the policy using a DNS zone transfer is as easy as specifying the server to get the zone from:
server: module-config: "respip validator iterator" rpz: name: rpz.nlnetlabs.nl master: <ip address of server to transfer from> zonefile: rpz.nlnetlabs.nl
The zone will now be transferred from the configured address and saved to a zonefile on disk. It is possible to have more than one policy zone in Unbound. Having multiple policy zones is as simple as having multiple rpz: clauses:
server: module-config: "respip validator iterator" rpz: name: rpz.nlnetlabs.nl zonefile: rpz.nlnetlabs.nl rpz: name: rpz2.nlnetlabs.nl zonefile: rpz2.nlnetlabs.nl
The policy zones will be applied in the configured order. In the example,
Unbound will only look at the
rpz2.nlnetlabs.nl policies if there is no
match in the
rpz.nlnetlabs.nl zone. If there is no match in any of the
configured zones Unbound will continue to resolve the domain by sending upstream
queries. Note that a PASSTHRU action is considered a match, having that action
in the first zone will therefore stop Unbound from looking further at other
Unbound has the possibility to override the actions that will be used for
policies in a zone that matches the zone’s triggers. This can be done using the
The possible values for the option are:
The first four options of this list will do the same as the RPZ actions with
the same name.
cname override option will make it possible to apply a local data action
using a CNAME for all matching triggers in the policy zone.
The CNAME to use in the answer can be configured using the
Using these overrides is nice if you use an external feed to get a list of
triggers, but would like to redirect all your users to your own domain:
RPZ zone (rpz.nlnetlabs.nl): $ORIGIN rpz.nlnetlabs.nl. drop.example.com.rpz.nlnetlabs.nl. CNAME rpz-drop. 126.96.36.199.93.rpz-ip.rpz.nlnetlabs.nl. A 192.0.2.1
This also requires a change in the Unbound configuration:
server: module-config: "respip validator iterator" rpz: name: rpz.nlnetlabs.nl zonefile: rpz.nlnetlabs.nl rpz-action-override: cname rpz-cname-override: "example.nl."
disabled option will stop Unbound from applying any of the actions in
the zone. This, combined with the
rpz-log option, is a nice way to test what
would happen to your traffic when a policy will be enabled, without directly
impacting your users. The difference between
that disabled is not considered to be a valid match and will therefore not stop
Unbound from looking at the next configured policy zone.
When rpz-log: is set to yes, Unbound will log
all applied actions for a policy zone.
rpz-log enabled you can specify a name for the log using
rpz-log-name:, this way you can easily
find all matches for a specific zone.
It is also possible to get statistics per applied RPZ action using
unbound-control stats or
This requires the extended-statistics:
to be enabled.
Unbound’s RPZ implementation works together with the tags functionality. This makes it possible to enable (some of) the policy zones only for a subset of users. To do this, the tags need to be defined using define-tag:, the correct tags need to be matched either with the client IP prefix using access-control-tag: or the clients on a listening interface using interface-tag:, and the tags need to be specified for the policy zones for which they apply.
server: module-config: "respip validator iterator" interface: eth0 define-tag: "malware social" # Per client IP ... access-control-tag: 127.0.0.10/32 "social" access-control-tag: 127.0.0.20/32 "social malware" access-control-tag: 127.0.0.30/32 "malware" # ... and/or per listening interface interface-tag: eth0 "social" rpz: name: malware.rpz.example.com zonefile: malware.rpz.example.com tags: "malware" rpz: name: social.rpz.example.com zonefile: social.rpz.example.com tags: "social"
Queries from 127.0.0.1 will not be filtered. For queries coming from 127.0.0.10 or the eth0 interface, only the policies from the social.rpz.example.com zone will be used. For queries coming from 127.0.0.30 only the policies from the malware.rpz.example.com zone will be used. Queries coming from 127.0.0.20 will be subjected to the policies from both zones.