Views and ACLs

The policy module implements policies for global query matching, e.g. solves “how to react to certain query”. This module combines it with query source matching, e.g. “who asked the query”. This allows you to create personalized blacklists, filters and ACLs.

There are two identification mechanisms:

  • addr - identifies the client based on his subnet

  • tsig - identifies the client based on a TSIG key name (only for testing purposes, TSIG signature is not verified!)

View module allows you to combine query source information with policy rules.

view:addr('', policy.suffix(policy.TC, policy.todnames({''})))

This example will force given client to TCP for names in subtree. You can combine view selectors with RPZ to create personalized filters for example.


Beware that cache is shared by all requests. For example, it is safe to refuse answer based on who asks the resolver, but trying to serve different data to different clients will result in unexpected behavior. Setups like split-horizon which depend on isolated DNS caches are explicitly not supported.

Example configuration

-- Load modules
modules = { 'view' }
-- Whitelist queries identified by TSIG key
view:tsig('\5mykey', policy.all(policy.PASS))
-- Block local IPv4 clients (ACL like)
view:addr('', policy.all(policy.DENY))
-- Block local IPv6 clients (ACL like)
view:addr('::1', policy.all(policy.DENY))
-- Drop queries with suffix match for remote client
view:addr('', policy.suffix(policy.DROP, policy.todnames({'xxx'})))
-- RPZ for subset of clients
view:addr('', policy.rpz(policy.PASS, 'whitelist.rpz'))
-- Do not try this - it will pollute cache and surprise you!
-- view:addr('', policy.all(policy.FORWARD('2001:DB8::1')))
-- Drop all IPv4 that hasn't matched
view:addr('', policy.all(policy.DROP))

Rule order

The current implementation is best understood as three separate rule chains: vanilla policy.add, view:tsig and view:addr. For each request the rules in these chains get tried one by one until a non-chain policy action gets executed.

By default policy module acts before view module due to policy being loaded by default. If you want to intermingle universal rules with view:addr, you may simply wrap the universal policy rules in view closure like this:

view:addr('', policy.<rule>) -- and
view:addr('::0/0',     policy.<rule>)


view:addr(subnet, rule)
  • subnet – client subnet, e.g.

  • rule – added rule, e.g. policy.pattern(policy.DENY, '[0-9]+\2cz')

Apply rule to clients in given subnet.

view:tsig(key, rule)
  • key – client TSIG key domain name, e.g. \5mykey

  • rule – added rule, e.g. policy.pattern(policy.DENY, '[0-9]+\2cz')

Apply rule to clients with given TSIG key.


This just selects rule based on the key name, it doesn’t verify the key or signature yet.