Response Policy Zones (RPZ)

Response Policy Zone is an open standard developed by Paul Vixie (ISC and Farsight) and Vernon Schryver (Rhyolite), to modify DNS responses based on a policy loaded via a zonefile.

Frequently, Response Policy Zones get to be very large and change quickly, so it is customary to update them over IXFR. It allows the use of third-party feeds, and near real-time policy updates.

Evaluation order

If multiple RPZs are loaded, they get consulted in the order they were defined in. It is however possible from Lua to make queries skip specific Response Policy Zones.

The evaluation order of RPZ policies is not always straightforward. Before 4.4.0, the recursor first checked whether the source address of the client matched a “Client IP Address” filter in any RPZ zones, then if the qname matched a “QNAME” trigger. It would then start the regular resolution process and check whether any “NSDNAME” or “NSIP” rule was triggered, then after the resolution process was done check whether any of the final records matched a “Response IP Address” rule. It would stop as soon as a match was found and apply the requested decision immediately, unless the decision was a “passthru”. In that last case it would resume the normal processing but would only evaluate the rules coming from a policy with a higher order than the one that matched.

Since 4.4.0 the behaviour is a bit different, to better follow the RPZ specifications. The source address of the client is still checked first. Then the normal resolution process starts and the initial qname as well as any CNAME part of the chain starting from the qname is checked against “QNAME” rules. “NSDNAME” and “NSIP” rules are still checked during the remaining part of the process, and “Response IP Address” rules are applied to the final records in the end. This matches the precedence rules from the RPZ specifications that specify that “A policy rule match which occurs at an earlier stage of resolution is preferred to a policy rule match which occurs at a later stage”.

For performance and privacy reasons, the order of evaluation does not strictly follow the one mandated by the RPZ specifications. In particular matching on the client IP and qname is done first before any processing, NS IP and NS DNAME matching is done when a nameserver is about to be sent a query, and matching on response records is done then a stage of resolution is done. The RPZ specifications mention that a match on the response record from a higher order RPZ should take precedence on a qname match from a lower one. Doing so would require delaying evaluation of RPZ policies until the whole resolution process has been completed, which would mean that queries might have been sent to a malicious nameserver already, in addition to performance issues.

Note that “RPZ rules do not apply to synthetic data generated by using RPZ rules. For example, if RPZ supplies a CNAME pointing to a walled garden, RPZ policies will not be used while following that CNAME. If RPZ supplies local data giving a particular A record, RPZ policies will not apply to that response IP address”, as stated in section 6.1 of the RPZ specifications.

Configuring RPZ

An RPZ can be loaded from file or transferred from a primary. To load from file, use for example:

rpzFile("dblfilename")

To transfer from a primary and start IXFR to get updates, use for example:

rpzPrimary("192.0.2.4", "policy.rpz")

In this example, ‘policy.rpz’ denotes the name of the zone to query for.

Note

In versions before 4.5.0, rpzPrimary is called rpzMaster. For backwards compatibility, version 4.5.0 does support rpzMaster as a synonym for rpzPrimary.

The action to be taken on a match is defined by the zone itself, but in some cases it might be interesting to be able to override it, and always apply the same action regardless of the one specified in the RPZ zone. To load from file and override the default action with a custom CNAME to badserver.example.com., use for example:

rpzFile("dblfilename", {defpol=Policy.Custom, defcontent="badserver.example.com"})

To instead drop all queries matching a rule, while transferred from a primary.

rpzPrimary("192.0.2.4", "policy.rpz", {defpol=Policy.Drop})

Note that since 4.2.0, it is possible for the override policy specified via ‘defpol’ to no longer be applied to local data entries present in the zone by setting the ‘defpolOverrideLocalData’ parameter to false.

As of version 4.2.0, the first parameter of rpzPrimary() can be a list of addresses for failover:

rpzPrimary({"192.0.2.4","192.0.2.5:5301"}, "policy.rpz", {defpol=Policy.Drop})

In the example above, two addresses are specified and will be tried one after another until a response is obtained. The first address uses the default port (53) while the second one uses port 5301. (If no optional port is set, the default port 53 is used)

Extended Errors

DNS messages can include extended error codes and text in the EDNS part of a reply. If set, the Recursor will add the extended error code and text if resolving a name leads to an RPZ hit. This information is then sent to the client, which can inspect the extended information for diagnosis and other purposes. As an example consider

rpzPrimary("192.0.2.4","policy.rpz", {extendedErrorCode = 15, extendedErrorExtra = "Blocked by policy"})

Resolving a name blocked by this policy will produce dig output containing the following line:

; EDE: 15 (Blocked): 42 6c 6f 63 6b 65 64 20 62 79 20 70 6f 6c 69 63 79 (“Blocked by policy”)

Check RFC 8914 for other extendedErrorCodes.

RPZ Configuration Functions

rpzFile(filename, settings)

Load an RPZ from disk.

Parameters:
  • filename (str) – The filename to load
  • settings ({}) – A table to settings, see below
rpzPrimary(address, name, settings)

Changed in version 4.2.0: The first parameter can be a list of addresses.

Changed in version 4.5.0: This function has been renamed from rpzMaster.

Load an RPZ from AXFR and keep retrieving with IXFR.

Parameters:
  • address (str) – The IP address to transfer the RPZ from. Also accepts a list of addresses since 4.2.0 in which case they will be tried one after another in the submitted order until a response is obtained.
  • name (str) – The name of this RPZ
  • settings ({}) – A table to settings, see below

RPZ settings

These options can be set in the settings of both rpzPrimary() and rpzFile().

defcontent

CNAME field to return in case of defpol=Policy.Custom

defpolOverrideLocalData

New in version 4.2.0: Before 4.2.0 local data entries are always overridden by the default policy.

Whether local data entries should be overridden by the default policy. Default is true.

defttl

the TTL of the CNAME field to be synthesized for the default policy. The default is to use the zone’s TTL,

extendedErrorCode

New in version 4.5.0.

An extended error code (RFC 8914) to set on RPZ hits. See extended-resolution-errors.

extendedErrorExtra

New in version 4.5.0.

An extended error extra text (RFC 8914) to set on RPZ hits. See extended-resolution-errors.

maxTTL

The maximum TTL value of the synthesized records, overriding a higher value from defttl or the zone. Default is unlimited.

policyName

The name logged as ‘appliedPolicy’ in protobuf messages when this policy is applied. Defaults to rpzFile for RPZs loaded by rpzFile() or the name of the zone for RPZs loaded by rpzPrimary().

tags

New in version 4.4.0.

List of tags as string, that will be added to the policy tags exported over protobuf when a policy of this zone matches.

overridesGettag

New in version 4.4.0.

gettag_ffi can set an answer to a query. By default an RPZ hit overrides this answer, unless this option is set to false. The default is true.

zoneSizeHint

An indication of the number of expected entries in the zone, speeding up the loading of huge zones by reserving space in advance.

Extra settings for rpzPrimary

In addition to the settings above the settings for rpzPrimary() may contain:

tsigname

The name of the TSIG key to authenticate to the server. When this is set, tsigalgo and tsigsecret must also be set.

tsigalgo

The name of the TSIG algorithm (like ‘hmac-md5’) used

tsigsecret

Base64 encoded TSIG secret

refresh

An integer describing the interval between checks for updates. By default, the RPZ zone’s default is used

maxReceivedMBytes

The maximum size in megabytes of an AXFR/IXFR update, to prevent resource exhaustion. The default value of 0 means no restriction.

localAddress

The source IP address to use when transferring the RPZ. When unset, query-local-address is used.

axfrTimeout

New in version 4.1.2: Before 4.1.2, the timeout was fixed on 10 seconds.

The timeout in seconds of the total initial AXFR transaction. 20 by default.

dumpFile

New in version 4.2.0.

A path to a file where the recursor will dump the latest version of the RPZ zone after each successful update. This can be used to keep track of changes in the RPZ zone, or to speed up the initial loading of the zone via the seedFile parameter. The format of the generated zone file is the same than the one used with rpzFile(), and can also be generated via:

rec_control dump-rpz zone-name output-file

seedFile

New in version 4.2.0.

A path to a file containing an existing dump of the RPZ zone. The recursor will try to load the zone from this file on startup, then immediately do an IXFR to retrieve any updates. If the file does not exist or is not valid, the normal process of doing a full AXFR will be used instead. This option allows a faster startup by loading an existing zone from a file instead of retrieving it from the network, then retrieving only the needed updates via IXFR. The format of the zone file is the same than the one used with rpzFile(), and can for example be generated via:

rec_control dump-rpz zone-name output-file

It is also possible to use the dumpFile parameter in order to dump the latest version of the RPZ zone after each update.

Policy Actions

If no settings are included, the RPZ is taken literally with no overrides applied. Several Policy Actions exist

Policy.Custom

Will return a NoError, CNAME answer with the value specified with defcontent, when looking up the result of this CNAME, RPZ is not taken into account.

Policy.Drop

Will simply cause the query to be dropped.

Policy.NoAction

Will continue normal processing of the query.

Policy.NODATA

Will return a NoError response with no value in the answer section.

Policy.NXDOMAIN

Will return a response with a NXDomain rcode.

Policy.Truncate

will return a NoError, no answer, truncated response over UDP. Normal processing will continue over TCP