Knot DNS Resolver modules¶
Static hints¶
This is a module providing static hints from /etc/hosts
like file for forward records (A/AAAA) and reverse records (PTR).
You can also use it to change root hints that are used as a safety belt, or if the root NS
drops out of cache.
Examples¶
-- Load hints after iterator
modules = { 'hints > iterate' }
-- Load hints before rrcache, custom hosts file
modules = { ['hints < rrcache'] = 'hosts.custom' }
-- Add root hints
hints.root({
['j.root-servers.net.'] = { '2001:503:c27::2:30', '192.58.128.30' }
})
-- Set custom hint
hints['localhost'] = '127.0.0.1'
Properties¶
-
hints.config
([path])¶ Parameters: - path (string) – path to hosts file, default: no file
Returns: { result: bool }
Clear any configured hints and load specified hosts file. (Root hints are not touched.)
-
hints.get
(hostname)¶ Parameters: - hostname (string) – i.e.
"localhost"
Returns: { result: [address1, address2, ...] }
Return list of address record matching given name. If no hostname is specified, all hints are returned in the table format used by
hints.root()
.- hostname (string) – i.e.
-
hints.set
(pair)¶ Parameters: - pair (string) –
hostname address
i.e."localhost 127.0.0.1"
Returns: { result: bool }
Add a hostname - address pair hint.
- pair (string) –
-
hints.del
(pair)¶ Parameters: - pair (string) –
hostname address
i.e."localhost 127.0.0.1"
, or justhostname
Returns: { result: bool }
Remove a hostname - address pair hint. If address is omitted, all addresses for the given name are deleted.
- pair (string) –
-
hints.root
()¶ Returns: { ['a.root-servers.net.'] = { '1.2.3.4', '5.6.7.8', ...}, ... }
Tip
If no parameters are passed, returns current root hints set.
-
hints.root
(root_hints) Parameters: - root_hints (table) – new set of root hints i.e.
{['name'] = 'addr', ...}
Returns: { ['a.root-servers.net.'] = { '1.2.3.4', '5.6.7.8', ...}, ... }
Replace current root hints and return the current table of root hints.
Example:
> hints.root({ ['l.root-servers.net.'] = '199.7.83.42', ['m.root-servers.net.'] = '202.12.27.33' }) [l.root-servers.net.] => { [1] => 199.7.83.42 } [m.root-servers.net.] => { [1] => 202.12.27.33 }
Tip
A good rule of thumb is to select only a few fastest root hints. The server learns RTT and NS quality over time, and thus tries all servers available. You can help it by preselecting the candidates.
- root_hints (table) – new set of root hints i.e.
Statistics collector¶
This modules gathers various counters from the query resolution and server internals, and offers them as a key-value storage. Any module may update the metrics or simply hook in new ones.
-- Enumerate metrics
> stats.list()
[answer.cached] => 486178
[iterator.tcp] => 490
[answer.noerror] => 507367
[answer.total] => 618631
[iterator.udp] => 102408
[query.concurrent] => 149
-- Query metrics by prefix
> stats.list('iter')
[iterator.udp] => 105104
[iterator.tcp] => 490
-- Set custom metrics from modules
> stats['filter.match'] = 5
> stats['filter.match']
5
-- Fetch most common queries
> stats.frequent()
[1] => {
[type] => 2
[count] => 4
[name] => cz.
}
-- Fetch most common queries (sorted by frequency)
> table.sort(stats.frequent(), function (a, b) return a.count > b.count end)
-- Show recently contacted authoritative servers
> stats.upstreams()
[2a01:618:404::1] => {
[1] => 26 -- RTT
}
[128.241.220.33] => {
[1] => 31 - RTT
}
Properties¶
-
stats.get
(key)¶ Parameters: - key (string) – i.e.
"answer.total"
Returns: number
- key (string) – i.e.
Return nominal value of given metric.
-
stats.set
(key, val)¶ Parameters: - key (string) – i.e.
"answer.total"
- val (number) – i.e.
5
- key (string) – i.e.
Set nominal value of given metric.
-
stats.list
([prefix])¶ Parameters: - prefix (string) – optional metric prefix, i.e.
"answer"
shows only metrics beginning with “answer”
- prefix (string) – optional metric prefix, i.e.
Outputs collected metrics as a JSON dictionary.
-
stats.upstreams
()¶
Outputs a list of recent upstreams and their RTT. It is sorted by time and stored in a ring buffer of
a fixed size. This means it’s not aggregated and readable by multiple consumers, but also that
you may lose entries if you don’t read quickly enough. The default ring size is 512 entries, and may be overriden on compile time by -DUPSTREAMS_COUNT=X
.
-
stats.frequent
()¶
Outputs list of most frequent iterative queries as a JSON array. The queries are sampled probabilistically, and include subrequests. The list maximum size is 5000 entries, make diffs if you want to track it over time.
-
stats.clear_frequent
()¶
Clear the list of most frequent iterative queries.
-
stats.expiring
()¶
Outputs list of soon-to-expire records as a JSON array. The list maximum size is 5000 entries, make diffs if you want to track it over time.
-
stats.clear_expiring
()¶
Clear the list of soon expiring records.
Built-in statistics¶
answer.total
- total number of answered queriesanswer.cached
- number of queries answered from cacheanswer.noerror
- number of NOERROR answersanswer.nodata
- number of NOERROR, but empty answersanswer.nxdomain
- number of NXDOMAIN answersanswer.servfail
- number of SERVFAIL answersanswer.1ms
- number of answers completed in 1msanswer.10ms
- number of answers completed in 10msanswer.50ms
- number of answers completed in 50msanswer.100ms
- number of answers completed in 100msanswer.250ms
- number of answers completed in 250msanswer.500ms
- number of answers completed in 500msanswer.1000ms
- number of answers completed in 1000msanswer.1500ms
- number of answers completed in 1500msanswer.slow
- number of answers that took more than 1500msquery.edns
- number of queries with EDNSquery.dnssec
- number of queries with DNSSEC DO=1
Query policies¶
This module can block, rewrite, or alter inbound queries based on user-defined policies. By default, it blocks queries to reverse lookups in private subnets as per RFC 1918, RFC 5735 and RFC 5737. You can however extend it to deflect Slow drip DNS attacks for example, or gray-list resolution of misbehaving zones.
There are several policies implemented:
pattern
- applies action if QNAME matches regular expressionsuffix
- applies action if QNAME suffix matches given list of suffixes (useful for “is domain in zone” rules), uses Aho-Corasick string matching algorithm implemented by @jgrahamc (CloudFlare, Inc.) (BSD 3-clause)rpz
- implementes a subset of the RPZ format. Currently it can be used with a zonefile, a binary database support is on the way. Binary database can be updated by an external process on the fly.- custom filter function
There are several defined actions:
PASS
- let the query pass throughDENY
- return NXDOMAIN answerDROP
- terminate query resolution, returns SERVFAIL to requestorTC
- set TC=1 if the request came through UDP, forcing client to retry with TCPFORWARD(ip)
- forward query to given IP and proxy back response (stub mode); it can be a single IP (string) or a list of up to four IPs.MIRROR(ip)
- mirror query to given IP and continue solving it (useful for partial snooping)REROUTE({{subnet,target}, ...})
- reroute addresses in response matching given subnet to given target, e.g.{'192.0.2.0/24', '127.0.0.0'}
will rewrite ‘192.0.2.55’ to ‘127.0.0.55’, see renumber module for more information.QTRACE
- pretty-print DNS response packets into the log (useful for debugging weird DNS servers)
Warning
The policy module only looks at the inbound DNS queries. Thus the FORWARD(ip)
policy does only forward inbound query to the specified IP address(es) and it doesn’t and it can’t do DNSSEC validation. If you need DNSSEC validation, you either need to disable FORWARD(ip)
policy or use an upstream DNSSEC-validating resolver.
Note
The module (and kres
) expects domain names in wire format, not textual representation. So each label in name is prefixed with its length, e.g. “example.com” equals to "\7example\3com"
. You can use convenience function todname('example.com')
for automatic conversion.
Example configuration¶
-- Load default policies
modules = { 'policy' }
-- Whitelist 'www[0-9].badboy.cz'
policy.add(policy.pattern(policy.PASS, '\4www[0-9]\6badboy\2cz'))
-- Block all names below badboy.cz
policy.add(policy.suffix(policy.DENY, {todname('badboy.cz.')}))
-- Custom rule
policy.add(function (req, query)
if query:qname():find('%d.%d.%d.224\7in-addr\4arpa') then
return policy.DENY
end
end)
-- Disallow ANY queries
policy.add(function (req, query)
if query.stype == kres.type.ANY then
return policy.DROP
end
end)
-- Enforce local RPZ
policy.add(policy.rpz(policy.DENY, 'blacklist.rpz'))
-- Forward all queries below 'company.se' to given resolver
policy.add(policy.suffix(policy.FORWARD('192.168.1.1'), {todname('company.se')}))
-- Forward all queries matching pattern
policy.add(policy.pattern(policy.FORWARD('2001:DB8::1'), '\4bad[0-9]\2cz'))
-- Forward all queries (complete stub mode)
policy.add(policy.all(policy.FORWARD('2001:DB8::1')))
-- Print all responses with matching suffix
policy.add(policy.suffix(policy.QTRACE, {todname('rhybar.cz.')}))
-- Print all responses
policy.add(policy.all(policy.QTRACE))
-- Mirror all queries and retrieve information
local rule = policy.add(policy.all(policy.MIRROR('127.0.0.2')))
-- Print information about the rule
print(string.format('id: %d, matched queries: %d', rule.id, rule.count)
-- Reroute all addresses found in answer from 192.0.2.0/24 to 127.0.0.x
-- this policy is enforced on answers, therefore 'postrule'
local rule = policy.add(policy.REROUTE({'192.0.2.0/24', '127.0.0.0'}), true)
-- Delete rule that we just created
policy.del(rule.id)
Properties¶
-
policy.PASS
¶ Pass-through all queries matching the rule.
-
policy.DENY
¶ Respond with NXDOMAIN to all queries matching the rule.
-
policy.DROP
¶ Drop all queries matching the rule.
-
policy.TC
¶ Respond with empty answer with TC bit set (if the query came through UDP).
-
policy.FORWARD (address)
¶ Forward query to given IP address.
-
policy.MIRROR (address)
¶ Forward query to given IP address.
-
policy.REROUTE({{subnet,target}, ...})
¶ Reroute addresses in response matching given subnet to given target, e.g.
{'192.0.2.0/24', '127.0.0.0'}
will rewrite ‘192.0.2.55’ to ‘127.0.0.55’.
-
policy.QTRACE
¶ Print pretty-formate (dig-like) DNS answers for current query and all its subqueries that Knot Resolver receive from upstream (authoritative) DNS servers. Very useful when dealing with non-compliant DNS servers that violate DNS protocol.
-
policy.add
(rule, postrule)¶ Parameters: - rule – added rule, i.e.
policy.pattern(policy.DENY, '[0-9]+\2cz')
- postrule – boolean, if true the rule will be evaluated on answer instead of query
Returns: rule description
Add a new policy rule that is executed either or queries or answers, depending on the
postrule
parameter. You can then use the returned rule description to get information and unique identifier for the rule, as well as match count.- rule – added rule, i.e.
-
policy.del
(id)¶ Parameters: - id – identifier of a given rule
Returns: boolean
Remove a rule from policy list.
-
policy.all
(action)¶ Parameters: - action – executed action for all queries
Perform action for all queries (no filtering).
-
policy.pattern
(action, pattern)¶ Parameters: - action – action if the pattern matches QNAME
- pattern – regular expression
Policy to block queries based on the QNAME regex matching.
-
policy.suffix
(action, suffix_table)¶ Parameters: - action – action if the pattern matches QNAME
- suffix_table – table of valid suffixes
Policy to block queries based on the QNAME suffix match.
-
policy.suffix_common
(action, suffix_table[, common_suffix])¶ Parameters: - action – action if the pattern matches QNAME
- suffix_table – table of valid suffixes
- common_suffix – common suffix of entries in suffix_table
Like suffix match, but you can also provide a common suffix of all matches for faster processing (nil otherwise). This function is faster for small suffix tables (in the order of “hundreds”).
-
policy.rpz
(action, path[, format])¶ Parameters: - action – the default action for match in the zone (e.g. RH-value .)
- path – path to zone file | database
Enforce RPZ rules. This can be used in conjunction with published blocklist feeds. The RPZ operation is well described in this Jan-Piet Mens’s post, or the Pro DNS and BIND book. Here’s compatibility table:
Policy Action RH Value Support NXDOMAIN .
yes NODATA *.
partial, implemented as NXDOMAIN Unchanged rpz-passthru.
yes Nothing rpz-drop.
yes Truncated rpz-tcp-only.
yes Modified anything no Policy Trigger Support QNAME yes CLIENT-IP partial, may be done with views IP no NSDNAME no NS-IP no
-
policy.todnames
({name, ...})¶ Param: names table of domain names in textual format Returns table of domain names in wire format converted from strings.
-- Convert single name assert(todname('example.com') == '\7example\3com\0') -- Convert table of names policy.todnames({'example.com', 'me.cz'}) { '\7example\3com\0', '\2me\2cz\0' }
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, sort of like ISC BIND views.
There are two identification mechanisms:
subnet
- identifies the client based on his subnettsig
- identifies the client based on a TSIG key
You can combine this information with policy rules.
view:addr('10.0.0.1', policy.suffix(policy.TC, {'\7example\3com'}))
This fill force given client subnet to TCP for names in example.com
.
You can combine view selectors with RPZ to create personalized filters for example.
Example configuration¶
-- Load modules
modules = { 'policy', 'view' }
-- Whitelist queries identified by TSIG key
view:tsig('\5mykey', function (req, qry) return policy.PASS end)
-- Block local clients (ACL like)
view:addr('127.0.0.1', function (req, qry) return policy.DENY end))
-- Drop queries with suffix match for remote client
view:addr('10.0.0.0/8', policy.suffix(policy.DROP, {'\3xxx'}))
-- RPZ for subset of clients
view:addr('192.168.1.0/24', policy.rpz(policy.PASS, 'whitelist.rpz'))
-- Forward all queries from given subnet to proxy
view:addr('10.0.0.0/8', policy.all(policy.FORWARD('2001:DB8::1')))
Properties¶
-
view:addr
(subnet, rule)¶ Parameters: - subnet – client subnet, i.e.
10.0.0.1
- rule – added rule, i.e.
policy.pattern(policy.DENY, '[0-9]+\2cz')
Apply rule to clients in given subnet.
- subnet – client subnet, i.e.
-
view:tsig
(key, rule)¶ Parameters: - key – client TSIG key domain name, i.e.
\5mykey
- rule – added rule, i.e.
policy.pattern(policy.DENY, '[0-9]+\2cz')
Apply rule to clients with given TSIG key.
Warning
This just selects rule based on the key name, it doesn’t verify the key or signature yet.
- key – client TSIG key domain name, i.e.
Prefetching records¶
The module tracks expiring records (having less than 5% of original TTL) and batches them for predict. This improves latency for frequently used records, as they are fetched in advance.
It is also able to learn usage patterns and repetitive queries that the server makes. For example, if it makes a query every day at 18:00, the resolver expects that it is needed by that time and prefetches it ahead of time. This is helpful to minimize the perceived latency and keeps the cache hot.
Tip
The tracking window and period length determine memory requirements. If you have a server with relatively fast query turnover, keep the period low (hour for start) and shorter tracking window (5 minutes). For personal slower resolver, keep the tracking window longer (i.e. 30 minutes) and period longer (a day), as the habitual queries occur daily. Experiment to get the best results.
Example configuration¶
Warning
This module requires ‘stats’ module to be present and loaded.
modules = {
predict = {
window = 15, -- 15 minutes sampling window
period = 6*(60/15) -- track last 6 hours
}
}
Defaults are 15 minutes window, 6 hours period.
Tip
Use period 0 to turn off prediction and just do prefetching of expiring records.
Exported metrics¶
To visualize the efficiency of the predictions, the module exports following statistics.
predict.epoch
- current prediction epoch (based on time of day and sampling window)predict.queue
- number of queued queries in current windowpredict.learned
- number of learned queries in current window
Properties¶
-
predict.config
({ window = 15, period = 24})¶ Reconfigure the predictor to given tracking window and period length. Both parameters are optional. Window length is in minutes, period is a number of windows that can be kept in memory. e.g. if a
window
is 15 minutes, aperiod
of “24” means 6 hours.
HTTP/2 services¶
This is a module that does the heavy lifting to provide an HTTP/2 enabled server that supports TLS by default and provides endpoint for other modules in order to enable them to export restful APIs and websocket streams. One example is statistics module that can stream live metrics on the website, or publish metrics on request for Prometheus scraper.
The server allows other modules to either use default endpoint that provides built-in webpage, restful APIs and websocket streams, or create new endpoints.
Example configuration¶
By default, the web interface starts HTTPS/2 on port 8053 using an ephemeral certificate that is valid for 90 days and is automatically renewed. It is of course self-signed, so you should use your own judgement before exposing it to the outside world. Why not use something like Let’s Encrypt for starters?
-- Load HTTP module with defaults
modules = {
http = {
host = 'localhost',
port = 8053,
geoip = 'GeoLite2-City.mmdb' -- Optional
}
}
Now you can reach the web services and APIs, done!
$ curl -k https://localhost:8053
$ curl -k https://localhost:8053/stats
It is possible to disable HTTPS altogether by passing cert = false
option.
While it’s not recommended, it could be fine for localhost tests as, for example,
Safari doesn’t allow WebSockets over HTTPS with a self-signed certificate.
Major drawback is that current browsers won’t do HTTP/2 over insecure connection.
http = {
host = 'localhost',
port = 8053,
cert = false,
}
If you want to provide your own certificate and key, you’re welcome to do so:
http = {
host = 'localhost',
port = 8053,
cert = 'mycert.crt',
key = 'mykey.key',
}
The format of both certificate and key is expected to be PEM, e.g. equivallent to the outputs of following:
openssl ecparam -genkey -name prime256v1 -out mykey.key
openssl req -new -key mykey.key -out csr.pem
openssl req -x509 -days 90 -key mykey.key -in csr.pem -out mycert.crt
Built-in services¶
The HTTP module has several built-in services to use.
Endpoint | Service | Description |
---|---|---|
/stats |
Statistics/metrics | Exported metrics in JSON. |
/metrics |
Prometheus metrics | Exported metrics for Prometheus |
/feed |
Most frequent queries | List of most frequent queries in JSON. |
Enabling Prometheus metrics endpoint¶
The module exposes /metrics
endpoint that serves internal metrics in Prometheus text format.
You can use it out of the box:
$ curl -k https://localhost:8053/metrics | tail
# TYPE latency histogram
latency_bucket{le=10} 2.000000
latency_bucket{le=50} 2.000000
latency_bucket{le=100} 2.000000
latency_bucket{le=250} 2.000000
latency_bucket{le=500} 2.000000
latency_bucket{le=1000} 2.000000
latency_bucket{le=1500} 2.000000
latency_bucket{le=+Inf} 2.000000
latency_count 2.000000
latency_sum 11.000000
How to expose services over HTTP¶
The module provides a table endpoints
of already existing endpoints, it is free for reading and
writing. It contains tables describing a triplet - {mime, on_serve, on_websocket}
.
In order to register a new service, simply add it to the table:
http.endpoints['/health'] = {'application/json',
function (h, stream)
-- API call, return a JSON table
return {state = 'up', uptime = 0}
end,
function (h, ws)
-- Stream current status every second
local ok = true
while ok do
local push = tojson('up')
ok = ws:send(tojson({'up'}))
require('cqueues').sleep(1)
end
-- Finalize the WebSocket
ws:close()
end}
Then you can query the API endpoint, or tail the WebSocket using curl.
$ curl -k http://localhost:8053/health
{"state":"up","uptime":0}
$ curl -k -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Host: localhost:8053/health" -H "Sec-Websocket-Key: nope" -H "Sec-Websocket-Version: 13" https://localhost:8053/health
HTTP/1.1 101 Switching Protocols
upgrade: websocket
sec-websocket-accept: eg18mwU7CDRGUF1Q+EJwPM335eM=
connection: upgrade
?["up"]?["up"]?["up"]
Since the stream handlers are effectively coroutines, you are free to keep state and yield using cqueues. This is especially useful for WebSockets, as you can stream content in a simple loop instead of chains of callbacks.
Last thing you can publish from modules are “snippets”. Snippets are plain pieces of HTML code that are rendered at the end of the built-in webpage. The snippets can be extended with JS code to talk to already exported restful APIs and subscribe to WebSockets.
http.snippets['/health'] = {'Health service', '<p>UP!</p>'}
How to expose RESTful services¶
A RESTful service is likely to respond differently to different type of methods and requests,
there are three things that you can do in a service handler to send back results.
First is to just send whatever you want to send back, it has to respect MIME type that the service
declared in the endpoint definition. The response code would then be 200 OK
, any non-string
responses will be packed to JSON. Alternatively, you can respond with a number corresponding to
the HTTP response code or send headers and body yourself.
-- Our upvalue
local value = 42
-- Expose the service
http.endpoints['/service'] = {'application/json',
function (h, stream)
-- Get request method and deal with it properly
local m = h:get(':method')
local path = h:get(':path')
log('[service] method %s path %s', m, path)
-- Return table, response code will be '200 OK'
if m == 'GET' then
return {key = path, value = value}
-- Save body, perform check and either respond with 505 or 200 OK
elseif m == 'POST' then
local data = stream:get_body_as_string()
if not tonumber(data) then
return 500, 'Not a good request'
end
value = tonumber(data)
-- Unsupported method, return 405 Method not allowed
else
return 405, 'Cannot do that'
end
end}
In some cases you might need to send back your own headers instead of default provided by HTTP handler,
you can do this, but then you have to return false
to notify handler that it shouldn’t try to generate
a response.
local headers = require('http.headers')
function (h, stream)
-- Send back headers
local hsend = headers.new()
hsend:append(':status', '200')
hsend:append('content-type', 'binary/octet-stream')
assert(stream:write_headers(hsend, false))
-- Send back data
local data = 'binary-data'
assert(stream:write_chunk(data, true))
-- Disable default handler action
return false
end
How to expose more interfaces¶
Services exposed in the previous part share the same external interface. This means that it’s either accessible to the outside world or internally, but not one or another. This is not always desired, i.e. you might want to offer DNS/HTTPS to everyone, but allow application firewall configuration only on localhost. http
module allows you to create additional interfaces with custom endpoints for this purpose.
http.interface('127.0.0.1', 8080, {
['/conf'] = {'application/json', function (h, stream) print('configuration API') end},
['/private'] = {'text/html', static_page},
})
This way you can have different internal-facing and external-facing services at the same time.
Dependencies¶
lua-http (>= 0.1) available in LuaRocks
If you’re installing via Homebrew on OS X, you need OpenSSL too.
$ brew update $ brew install openssl $ brew link openssl --force # Override system OpenSSL
Any other system can install from LuaRocks directly:
$ luarocks install http
mmdblua available in LuaRocks
$ luarocks install --server=https://luarocks.org/dev mmdblua $ curl -O https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz $ gzip -d GeoLite2-City.mmdb.gz
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 1.2.3.4 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
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:8053/daf | jq .
{}
# Create new rule
$ curl -s -X POST -d "src = 127.0.0.1 pass" http://localhost:8053/daf | jq .
{
"count": 0,
"active": true,
"info": "src = 127.0.0.1 pass",
"id": 1
}
# Disable rule
$ curl -s -X PATCH http://localhost:8053/daf/1/active/false | jq .
true
# Retrieve a rule information
$ curl -s -X GET http://localhost:8053/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:8053/daf/1 | jq .
true
Graphite module¶
The module sends statistics over the Graphite protocol to either Graphite, Metronome, InfluxDB or any compatible storage. This allows powerful visualization over metrics collected by Knot DNS Resolver.
Tip
The Graphite server is challenging to get up and running, InfluxDB combined with Grafana are much easier, and provide richer set of options and available front-ends. Metronome by PowerDNS alternatively provides a mini-graphite server for much simpler setups.
Example configuration¶
Only the host
parameter is mandatory.
By default the module uses UDP so it doesn’t guarantee the delivery, set tcp = true
to enable Graphite over TCP. If the TCP consumer goes down or the connection with Graphite is lost, resolver will periodically attempt to reconnect with it.
modules = {
graphite = {
prefix = hostname(), -- optional metric prefix
host = '127.0.0.1', -- graphite server address
port = 2003, -- graphite server port
interval = 5 * sec, -- publish interval
tcp = false -- set to true if want TCP mode
}
}
The module supports sending data to multiple servers at once.
modules = {
graphite = {
host = { '127.0.0.1', '1.2.3.4', '::1' },
}
}
Memcached cache storage¶
Module providing a cache storage backend for memcached, which makes a good fit for making a shared cache between resolvers.
After loading you can see the storage backend registered and useable.
> modules.load 'kmemcached'
> cache.backends()
[memcached://] => true
And you can use it right away, see the libmemcached configuration reference for configuration string options, the most essential ones are –SERVER or –SOCKET. Here’s an example for connecting to UNIX socket.
> cache.storage = 'memcached://--SOCKET="/var/sock/memcached"'
Note
The memcached instance MUST support binary protocol, in order to make it work with binary keys. You can pass other options to the configuration string for performance tuning.
Warning
The memcached server is responsible for evicting entries out of cache, the pruning function is not implemented, and neither is aborting write transactions.
Dependencies¶
Depends on the libmemcached library.
Redis cache storage¶
This modules provides Redis backend for cache storage. Redis is a BSD-license key-value cache and storage server. Like memcached backend, Redis provides master-server replication, but also weak-consistency clustering.
After loading you can see the storage backend registered and useable.
> modules.load 'redis'
> cache.backends()
[redis://] => true
Redis client support TCP or UNIX sockets.
> cache.storage = 'redis://127.0.0.1'
> cache.storage = 'redis://127.0.0.1:6398'
> cache.storage = 'redis:///tmp/redis.sock'
It also supports indexed databases if you prefix the configuration string with DBID@
.
> cache.storage = 'redis://9@127.0.0.1'
Warning
The Redis client doesn’t really support transactions nor pruning. Cache eviction policy shoud be left upon Redis server, see the Using Redis as an LRU cache.
Build distributed cache¶
See Redis Cluster tutorial.
Etcd module¶
The module connects to Etcd peers and watches for configuration change.
By default, the module looks for the subtree under /kresd
directory,
but you can change this in the configuration.
The subtree structure corresponds to the configuration variables in the declarative style.
$ etcdctl set /kresd/net/127.0.0.1 53
$ etcdctl set /kresd/cache/size 10000000
Configures all listening nodes to following configuration:
net = { '127.0.0.1' }
cache.size = 10000000
Example configuration¶
modules = {
ketcd = {
prefix = '/kresd',
peer = 'http://127.0.0.1:7001'
}
}
Warning
Work in progress!
DNS64¶
The module for RFC 6147 DNS64 AAAA-from-A record synthesis, it is used to enable client-server communication between an IPv6-only client and an IPv4-only server. See the well written introduction in the PowerDNS documentation.
Tip
The A record sub-requests will be DNSSEC secured, but the synthetic AAAA records can’t be. Make sure the last mile between stub and resolver is secure to avoid spoofing.
Example configuration¶
-- Load the module with a NAT64 address
modules = { dns64 = 'fe80::21b:77ff:0:0' }
-- Reconfigure later
dns64.config('fe80::21b:aabb:0:0')
Renumber¶
The module renumbers addresses in answers to different address space. e.g. you can redirect malicious addresses to a blackhole, or use private address ranges in local zones, that will be remapped to real addresses by the resolver.
Warning
While requests are still validated using DNSSEC, the signatures are stripped from final answer. The reason is that the address synthesis breaks signatures. You can see whether an answer was valid or not based on the AD flag.
Example configuration¶
modules = {
renumber = {
-- Source subnet, destination subnet
{'10.10.10.0/24', '192.168.1.0'},
-- Remap /16 block to localhost address range
{'166.66.0.0/16', '127.0.0.0'}
}
}
DNS Cookies¶
The module performs most of the RFC 7873 DNS cookies functionality. Its main purpose is to check the cookies of inbound queries and responses. It is also used to alter the behaviour of the cookie functionality.
Example Configuration¶
-- Load the module before the 'iterate' layer.
modules = {
'cookies < iterate'
}
-- Configure the client part of the resolver. Set 8 bytes of the client
-- secret and choose the hashing algorithm to be used.
-- Use a string composed of hexadecimal digits to set the secret.
cookies.config { client_secret = '0123456789ABCDEF',
client_cookie_alg = 'FNV-64' }
-- Configure the server part of the resolver.
cookies.config { server_secret = 'FEDCBA9876543210',
server_cookie_alg = 'FNV-64' }
-- Enable client cookie functionality. (Add cookies into outbound
-- queries.)
cookies.config { client_enabled = true }
-- Enable server cookie functionality. (Handle cookies in inbound
-- requests.)
cookies.config { server_enabled = true }
Tip
If you want to change several parameters regarding the client or server configuration then do it within a single cookies.config()
invocation.
Warning
The module must be loaded before any other module that has direct influence on query processing and response generation. The module must be able to intercept an incoming query before the processing of the actual query starts. It must also be able to check the cookies of inbound responses and eventually discard them before they are handled by other functional units.
Properties¶
Parameters: - configuration (table) – part of cookie configuration to be changed, may be called without parameter
Returns: JSON dictionary containing current configuration
The function may be called without any parameter. In such case it only returns current configuration. The returned JSON also contains available algorithm choices.
Dependencies¶
- Nettle required for HMAC-SHA256
- development version of libknot (master branch) for DNS cookies handling