DNS Application Firewall

This module is a high-level interface for other powerful filtering modules and DNS views. It provides an easy interface to apply and monitor DNS filtering rules and a persistent memory for them. It also provides a restful service interface and an HTTP interface.

Example configuration

Firewall rules are declarative and consist of filters and actions. Filters have field operator operand notation (e.g. qname = example.com), and may be chained using AND/OR keywords. Actions may or may not have parameters after the action name.

-- Let's write some daft rules!
modules = { 'daf' }

-- Block all queries with QNAME = example.com
daf.add('qname = example.com deny')

-- Filters can be combined using AND/OR...
-- Block all queries with QNAME match regex and coming from given subnet
daf.add('qname ~ %w+.example.com AND src = 192.0.2.0/24 deny')

-- We also can reroute addresses in response to alternate target
-- This reroutes 192.0.2.1 to localhost
daf.add('src = 127.0.0.0/8 reroute 192.0.2.1-127.0.0.1')

-- Subnets work too, this reroutes a whole subnet
-- e.g. 192.0.2.55 to 127.0.0.55
daf.add('src = 127.0.0.0/8 reroute 192.0.2.0/24-127.0.0.0')

-- This rewrites all A answers for 'example.com' from
-- whatever the original address was to 127.0.0.2
daf.add('src = 127.0.0.0/8 rewrite example.com A 127.0.0.2')

-- Mirror queries matching given name to DNS logger
daf.add('qname ~ %w+.example.com mirror 127.0.0.2')
daf.add('qname ~ example-%d.com mirror 127.0.0.3@5353')

-- Forward queries from subnet
daf.add('src = 127.0.0.1/8 forward 127.0.0.1@5353')
-- Forward to multiple targets
daf.add('src = 127.0.0.1/8 forward 127.0.0.1@5353,127.0.0.2@5353')

-- Truncate queries based on destination IPs
daf.add('dst = 192.0.2.51 truncate')

-- Disable a rule
daf.disable(2)
-- Enable a rule
daf.enable(2)
-- Delete a rule
daf.del(2)

-- Delete all rules and start from scratch
daf.clear()

Warning

Only the first matching rule’s action is executed. Defining additional actions for the same matching rule, e.g. src = 127.0.0.1/8, will have no effect.

If you’re not sure what firewall rules are in effect, see daf.rules:

-- Show active rules
> daf.rules
[1] => {
    [rule] => {
        [count] => 42
        [id] => 1
        [cb] => function: 0x1a3eda38
    }
    [info] => qname = example.com AND src = 127.0.0.1/8 deny
    [policy] => function: 0x1a3eda38
}
[2] => {
    [rule] => {
        [suspended] => true
        [count] => 123522
        [id] => 2
        [cb] => function: 0x1a3ede88
    }
    [info] => qname ~ %w+.facebook.com AND src = 127.0.0.1/8 deny...
    [policy] => function: 0x1a3ede88
}

Web interface

If you have HTTP/2 loaded, the firewall automatically loads as a snippet. You can create, track, suspend and remove firewall rules from the web interface. If you load both modules, you have to load daf after http.

RESTful interface

The module also exports a RESTful API for operations over rule chains.

URL

HTTP Verb

Action

/daf

GET

Return JSON list of active rules.

/daf

POST

Insert new rule, rule string is expected in body. Returns rule information in JSON.

/daf/<id>

GET

Retrieve a rule matching given ID.

/daf/<id>

DELETE

Delete a rule matching given ID.

/daf/<id>/<prop>/<val>

PATCH

Modify given rule, for example /daf/3/active/false suspends rule 3.

This interface is used by the web interface for all operations, but you can also use it directly for testing.

# Get current rule set
$ curl -s -X GET http://localhost:8453/daf | jq .
{}

# Create new rule
$ curl -s -X POST -d "src = 127.0.0.1 pass" http://localhost:8453/daf | jq .
{
  "count": 0,
  "active": true,
  "info": "src = 127.0.0.1 pass",
  "id": 1
}

# Disable rule
$ curl -s -X PATCH http://localhost:8453/daf/1/active/false | jq .
true

# Retrieve a rule information
$ curl -s -X GET http://localhost:8453/daf/1 | jq .
{
  "count": 4,
  "active": true,
  "info": "src = 127.0.0.1 pass",
  "id": 1
}

# Delete a rule
$ curl -s -X DELETE http://localhost:8453/daf/1 | jq .
true