Commit 3a33dcf9 authored by Vladimír Čunát's avatar Vladimír Čunát

Merge branch 'master' into flags-refactor

There were just simple conflicts in NEWS and docs.
parents f0bc5ea9 20f3b92a
Pipeline #12449 failed with stages
in 4 minutes and 39 seconds
......@@ -30,7 +30,11 @@ test:linux:amd64:
deckard:linux:amd64:
stage: test
script:
- git submodule update --init --recursive
- apt purge -y python-dnspython python3-dnspython
- apt update
- apt install python-pip libffi-dev libaugeas-dev -y
- pip install --upgrade pip
- pip install --user dnspython pyyaml python-augeas
- PREFIX=$(pwd)/.local MAKEFLAGS="--jobs $(nproc) --keep-going" make check-integration
dependencies:
- build:linux:amd64
......@@ -43,21 +47,22 @@ respdiff:linux:amd64:
image: cznic/ubuntu-respdif:16.04
stage: test
script:
- LD_LIBRARY_PATH=$(pwd)/.local/lib /home/kresdbench/run.sh $(pwd)/.local/sbin/kresd
- /home/kresdbench/resolver-benchmarking/response_differences/nightly.sh
- cp -r /home/kresdbench/resolver-benchmarking/response_differences/results $(pwd)
- PREFIX=$(pwd)/.local ./ci/respdiff/start-resolvers.sh
- ./ci/respdiff/run-respdiff-tests.sh
- cat ./results/respdiff.txt
- echo 'test if mismatch rate >= 1 %'
- grep -q '^target diagrees.*0\.[0-9][0-9] %' ./results/respdiff.txt
artifacts:
when: always
expire_in: '1 week'
paths:
- results/*.json
- results/*.out
- results/*.log
- results/*.txt
tags:
- docker
- linux
- amd64
allow_failure: true
dependencies:
- build:linux:amd64
#arm_build:
# image: cznic/armhf-ubuntu:16.04
......
......@@ -9,6 +9,27 @@ Incompatible changes
You can instead write code like qry.flags.NO_0X20 = true.
Knot Resolver 1.3.3 (2017-08-09)
================================
Security
--------
- Fix a critical DNSSEC flaw. Signatures might be accepted as valid
even if the signed data was not in bailiwick of the DNSKEY used to
sign it, assuming the trust chain to that DNSKEY was valid.
Bugfixes
--------
- iterate: skip RRSIGs with bad label count instead of immediate SERVFAIL
- utils: fix possible incorrect seeding of the random generator
- modules/http: fix compatibility with the Prometheus text format
Improvements
------------
- policy: implement remaining special-use domain names from RFC6761 (#205),
and make these rules apply only if no other non-chain rule applies
Knot Resolver 1.3.2 (2017-07-28)
================================
......
-- Refer to manual: https://knot-resolver.readthedocs.io/en/latest/daemon.html#configuration
-- Listen on localhost and external interface
net.listen('127.0.0.1', 5353)
net.listen('::1', 5353)
-- Auto-maintain root TA
trust_anchors.file = '.local/etc/kresd/root.keys'
-- Large cache size, so we don't need to flush often
-- This can be larger than available RAM, least frequently accessed
-- records will be paged out
cache.size = 1024 * MB
-- Load Useful modules
modules = {
'workarounds < iterate',
'policy', -- Block queries to local zones/bad sites
'view', -- Views for certain clients
'hints', -- Load /etc/hosts and allow custom root hints
'stats', -- Track internal statistics
}
verbose(false)
[sendrecv]
# in seconds
timeout = 5
# number of queries to run simultaneously
jobs = 64
[servers]
names = kresd, bind, unbound
# symbolic names of DNS servers under test
# separate multiple values by ,
# each symbolic name in [servers] section refers to config section
# containing IP address and port of particular server
[kresd]
ip = ::1
port = 5353
[bind]
ip = 127.0.0.1
port = 53533
[unbound]
ip = 127.0.0.1
port = 53535
[diff]
# symbolic name of server under test
# other servers are used as reference when comparing answers from the target
target = kresd
# fields and comparison methods used when comparing two DNS messages
criteria = opcode, rcode, flags, question, qname, qtype, answertypes, answerrrsigs
# other supported criteria values: authority, additional, edns, nsid
[report]
# diffsum reports mismatches in field values in this order
# if particular message has multiple mismatches, it is counted only once into category with highest weight
field_weights = opcode, qcase, qtype, rcode, flags, answertypes, answerrrsigs, answer, authority, additional, edns, nsid
wget https://gitlab.labs.nic.cz/knot/knot-resolver/snippets/69/raw?inline=false -O /tmp/queries.txt
mkdir results;
rm -rf /tmp/respdiff;
python3 /var/opt/respdiff/qprep.py /tmp/respdiff < /tmp/queries.txt && \
python3 /var/opt/respdiff/orchestrator.py /tmp/respdiff -c $(pwd)/ci/respdiff/respdiff.conf && \
python3 /var/opt/respdiff/msgdiff.py /tmp/respdiff -c $(pwd)/ci/respdiff/respdiff.conf && \
python3 /var/opt/respdiff/diffsum.py /tmp/respdiff -c $(pwd)/ci/respdiff/respdiff.conf > results/respdiff.txt
#run unbound
service unbound start && service unbound status;
# dig @localhost -p 53535
#run bind
service bind9 start && service bind9 status;
# dig @localhost -p 53533
#run kresd
LD_LIBRARY_PATH=$PREFIX/lib $PREFIX/sbin/kresd -f 1 -q -c $(pwd)/ci/respdiff/kresd.config &
# dig @localhost -p 5353
# Project
MAJOR := 1
MINOR := 3
PATCH := 2
PATCH := 3
EXTRA :=
ABIVER := 4
BUILDMODE := dynamic
......
......@@ -147,6 +147,7 @@ The daemon also supports `systemd socket activation`_, it is automatically detec
To run the daemon by hand, such as under ``nohup``, use ``-f 1`` to start a single fork. For example:
.. code-block:: bash
$ nohup ./daemon/kresd -a 127.0.0.1 -f 1 &
......
......@@ -199,8 +199,11 @@ struct kr_context {
struct kr_zonecut root_hints;
char _stub[];
};
int knot_dname_size(const knot_dname_t *);
knot_dname_t *knot_dname_from_str(uint8_t *, const char *, size_t);
_Bool knot_dname_is_equal(const knot_dname_t *, const knot_dname_t *);
_Bool knot_dname_is_sub(const knot_dname_t *, const knot_dname_t *);
int knot_dname_labels(const uint8_t *, const uint8_t *);
int knot_dname_size(const knot_dname_t *);
char *knot_dname_to_str(char *, const knot_dname_t *, size_t);
uint16_t knot_rdata_rdlen(const knot_rdata_t *);
uint8_t *knot_rdata_data(const knot_rdata_t *);
......@@ -223,7 +226,7 @@ struct kr_query *kr_rplan_push(struct kr_rplan *, struct kr_query *, const knot_
int kr_rplan_pop(struct kr_rplan *, struct kr_query *);
struct kr_query *kr_rplan_resolved(struct kr_rplan *);
int kr_nsrep_set(struct kr_query *, size_t, const struct sockaddr *);
unsigned int kr_rand_uint(unsigned int);
uint32_t kr_rand_uint(uint32_t);
void kr_pkt_make_auth_header(knot_pkt_t *);
int kr_pkt_put(knot_pkt_t *, const knot_dname_t *, uint32_t, uint16_t, uint16_t, const uint8_t *, uint16_t);
int kr_pkt_recycle(knot_pkt_t *);
......
......@@ -79,8 +79,11 @@ printf "\tchar _stub[];\n};\n"
## libknot API
./scripts/gen-cdefs.sh libknot functions <<-EOF
# Domain names
knot_dname_size
knot_dname_from_str
knot_dname_is_equal
knot_dname_is_sub
knot_dname_labels
knot_dname_size
knot_dname_to_str
# Resource records
knot_rdata_rdlen
......
......@@ -125,6 +125,12 @@ When you have all the dependencies ready, you can build and install.
Production code should be compiled with ``-DNDEBUG``.
If you build the binary with ``-DNOVERBOSELOG``, it won't be possible to turn on verbose logging; we advise packagers against using that flag.
.. note:: If you build with ``PREFIX``, you may need to also set the ``LDFLAGS`` for the libraries:
.. code-block:: bash
make LDFLAGS="-Wl,-rpath=/usr/local/lib" PREFIX="/usr/local"
Alternatively you can build only specific parts of the project, i.e. ``library``.
.. code-block:: bash
......
......@@ -36,6 +36,10 @@
#include "lib/dnssec.h"
#include "lib/resolve.h"
/* forward */
static int kr_rrset_validate_with_key(kr_rrset_validation_ctx_t *vctx,
const knot_rrset_t *covered, size_t key_pos, const struct dseckey *key);
void kr_crypto_init(void)
{
dnssec_crypto_init();
......@@ -147,7 +151,16 @@ int kr_rrset_validate(kr_rrset_validation_ctx_t *vctx, const knot_rrset_t *cover
return kr_error(ENOENT);
}
int kr_rrset_validate_with_key(kr_rrset_validation_ctx_t *vctx,
/**
* Validate RRSet using a specific key.
* @param vctx Pointer to validation context.
* @param covered RRSet covered by a signature. It must be in canonical format.
* @param key_pos Position of the key to be validated with.
* @param key Key to be used to validate.
* If NULL, then key from DNSKEY RRSet is used.
* @return 0 or error code, same as vctx->result.
*/
static int kr_rrset_validate_with_key(kr_rrset_validation_ctx_t *vctx,
const knot_rrset_t *covered,
size_t key_pos, const struct dseckey *key)
{
......@@ -157,6 +170,14 @@ int kr_rrset_validate_with_key(kr_rrset_validation_ctx_t *vctx,
uint32_t timestamp = vctx->timestamp;
bool has_nsec3 = vctx->has_nsec3;
struct dseckey *created_key = NULL;
/* It's just caller's approximation that the RR is in that particular zone.
* We MUST guard against attempts of zones signing out-of-bailiwick records. */
if (!knot_dname_in(zone_name, covered->owner)) {
vctx->result = kr_error(ENOENT);
return vctx->result;
}
if (key == NULL) {
const knot_rdata_t *krr = knot_rdataset_at(&keys->rrs, key_pos);
int ret = kr_dnssec_key_from_rdata(&created_key, keys->owner,
......
......@@ -71,18 +71,6 @@ typedef struct kr_rrset_validation_ctx kr_rrset_validation_ctx_t;
int kr_rrset_validate(kr_rrset_validation_ctx_t *vctx,
const knot_rrset_t *covered);
/**
* Validate RRSet using a specific key.
* @param vctx Pointer to validation context.
* @param covered RRSet covered by a signature. It must be in canonical format.
* @param key_pos Position of the key to be validated with.
* @param key Key to be used to validate.
* If NULL, then key from DNSKEY RRSet is used.
* @return 0 or error code, same as vctx->result.
*/
int kr_rrset_validate_with_key(kr_rrset_validation_ctx_t *vctx,
const knot_rrset_t *covered,
size_t key_pos, const struct dseckey *key);
/**
* Check whether the DNSKEY rrset matches the supplied trust anchor RRSet.
* @param vctx Pointer to validation context.
......
......@@ -465,7 +465,10 @@ static int unroll_cname(knot_pkt_t *pkt, struct kr_request *req, bool referral,
if (rr->type == KNOT_RRTYPE_RRSIG) {
int rrsig_labels = knot_rrsig_labels(&rr->rrs, 0);
if (rrsig_labels > cname_labels) {
return KR_STATE_FAIL;
/* clearly wrong RRSIG, don't pick it.
* don't fail immediately,
* let validator work. */
continue;
}
if (rrsig_labels < cname_labels) {
query->flags.DNSSEC_WEXPAND = true;
......@@ -820,7 +823,8 @@ int kr_make_query(struct kr_query *query, knot_pkt_t *pkt)
}
/* Query built, expect answer. */
query->id = kr_rand_uint(UINT16_MAX);
uint32_t rnd = kr_rand_uint(0);
query->id = rnd ^ (rnd >> 16); /* cheap way to strengthen unpredictability */
knot_wire_set_id(pkt->wire, query->id);
pkt->parsed = pkt->size;
WITH_VERBOSE {
......
......@@ -1421,7 +1421,7 @@ int kr_resolve_produce(struct kr_request *request, struct sockaddr **dst, int *t
/* Randomize query case (if not in safemode or turned off) */
qry->secret = (qry->flags.SAFEMODE || qry->flags.NO_0X20)
? 0 : kr_rand_uint(UINT32_MAX);
? 0 : kr_rand_uint(0);
knot_dname_t *qname_raw = (knot_dname_t *)knot_pkt_qname(packet);
randomized_qname_case(qname_raw, qry->secret);
......
......@@ -127,16 +127,25 @@ char* kr_strcatdup(unsigned n, ...)
return result;
}
static int seed_file(FILE *fp, char *buf, size_t buflen)
static int seed_file(const char *fname, char *buf, size_t buflen)
{
auto_fclose FILE *fp = fopen(fname, "r");
if (!fp) {
return -1;
}
/* Read whole buffer even if interrupted */
ssize_t readb = 0;
while (!ferror(fp) && readb < buflen) {
readb += fread(buf, 1, buflen - readb, fp);
return kr_error(EINVAL);
}
/* Disable buffering to conserve randomness but ignore failing to do so. */
setvbuf(fp, NULL, _IONBF, 0);
do {
if (feof(fp)) {
return kr_error(ENOENT);
}
if (ferror(fp)) {
return kr_error(ferror(fp));
}
if (fread(buf, buflen, 1, fp) == 1) { /* read in one chunk for simplicity */
return kr_ok();
}
} while (true);
return 0;
}
......@@ -147,13 +156,13 @@ static int randseed(char *buf, size_t buflen)
"/dev/srandom", "/dev/urandom", "/dev/random", NULL
};
for (unsigned i = 0; filenames[i]; ++i) {
auto_fclose FILE *fp = fopen(filenames[i], "r");
if (seed_file(fp, buf, buflen) == 0) {
if (seed_file(filenames[i], buf, buflen) == 0) {
return 0;
}
}
/* Seed from time, this is not going to be secure. */
kr_log_error("failed to obtain randomness, falling back to current time\n");
struct timeval tv;
gettimeofday(&tv, NULL);
memcpy(buf, &tv, buflen < sizeof(tv) ? buflen : sizeof(tv));
......@@ -168,13 +177,15 @@ int kr_rand_reseed(void)
return kr_ok();
}
unsigned kr_rand_uint(unsigned max)
uint32_t kr_rand_uint(uint32_t max)
{
if (!isaac_seeded) {
if (unlikely(!isaac_seeded)) {
kr_rand_reseed();
isaac_seeded = true;
}
return isaac_next_uint(&ISAAC, max);
return max == 0
? isaac_next_uint32(&ISAAC)
: isaac_next_uint(&ISAAC, max);
}
int kr_memreserve(void *baton, char **mem, size_t elm_size, size_t want, size_t *have)
......
......@@ -149,9 +149,12 @@ char* kr_strcatdup(unsigned n, ...);
/** Reseed CSPRNG context. */
int kr_rand_reseed(void);
/** Get pseudo-random value. */
/** Get pseudo-random value between zero and max-1 (inclusive).
*
* Passing zero means that any uint32_t should be returned (it's also faster).
*/
KR_EXPORT
unsigned kr_rand_uint(unsigned max);
uint32_t kr_rand_uint(uint32_t max);
/** Memory reservation routine for knot_mm_t */
KR_EXPORT
......
......@@ -414,7 +414,7 @@ int check_request(kr_layer_t *ctx)
};
struct kr_nonce_input nonce = {
.rand = kr_rand_uint(UINT32_MAX),
.rand = kr_rand_uint(0),
.time = req->current_query->timestamp.tv_sec
};
......
......@@ -6,7 +6,7 @@ Static hints
This is a module providing static hints for forward records (A/AAAA) and reverse records (PTR).
The records can be loaded from ``/etc/hosts``-like files and/or added directly.
You can also use the module to change root hints. They are used as a safety belt or if the root NS
You can also use the module to change the root hints; they are used as a safety belt or if the root NS
drops out of cache.
Examples
......@@ -14,16 +14,19 @@ Examples
.. code-block:: lua
-- Load hints after iterator
-- Load hints after iterator (so hints take precedence before caches)
modules = { 'hints > iterate' }
-- Load hints before rrcache, custom hosts file
modules = { ['hints < rrcache'] = 'hosts.custom' }
-- Add root hints
-- Add a custom hosts file
hints.add_hosts('hosts.custom')
-- Override the root hints
hints.root({
['j.root-servers.net.'] = { '2001:503:c27::2:30', '192.58.128.30' }
})
-- Add a custom hint
hints['localhost'] = '127.0.0.1'
hints['foo.bar'] = '127.0.0.1'
.. note:: The ``policy`` module applies before ``hints``, meaning e.g. that hints for special names (:rfc:`6761#section-6`) like ``localhost`` or ``test`` will get shadowed by ``policy`` rules by default.
That can be worked around e.g. by explicit ``policy.PASS`` action.
Properties
^^^^^^^^^^
......@@ -38,7 +41,7 @@ Properties
.. function:: hints.add_hosts([path])
:param string path: path to hosts-like file, default: `/etc/hosts`
:param string path: path to hosts-like file, default: ``/etc/hosts``
Add hints from a host-like file.
......
......@@ -133,11 +133,11 @@ local function serve_prometheus()
-- as a timeout (3000ms) for metrics purposes
count = count + e[2]
sum = sum + e[2] * (math.min(tonumber(e[1]), 3000.0))
table.insert(render, string.format('latency_bucket{le=%s} %f', e[1], count))
table.insert(render, string.format('latency_bucket{le="%s"} %f', e[1], count))
end
table.insert(render, string.format('latency_count %f', count))
table.insert(render, string.format('latency_sum %f', sum))
return table.concat(render, '\n')
return table.concat(render, '\n') .. '\n'
end
-- Export endpoints
......
......@@ -4,41 +4,47 @@ 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 <https://blog.secure64.com/?p=377>`_ for example, or gray-list resolution of misbehaving zones.
By default, if no rule applies to a query, rules for special-use domain names are applied, as required by :rfc:`6761`.
There are several policies implemented:
You can however extend it e.g. to deflect `Slow drip DNS attacks <https://secure64.com/water-torture-slow-drip-dns-ddos-attack>`_ or gray-list resolution of misbehaving zones.
* ``pattern``
- applies action if QNAME matches `regular expression <http://lua-users.org/wiki/PatternsTutorial>`_
* ``suffix``
- applies action if QNAME suffix matches given list of suffixes (useful for "is domain in zone" rules),
There are several policy filters available in the ``policy.`` table:
* ``all(action)``
- always applies the action
* ``pattern(action, pattern)``
- applies the action if QNAME matches a `regular expression <http://lua-users.org/wiki/PatternsTutorial>`_
* ``suffix(action, table)``
- applies the action if QNAME suffix matches one of suffixes in the table (useful for "is domain in zone" rules),
uses `Aho-Corasick`_ string matching algorithm implemented by `@jgrahamc`_ (CloudFlare, Inc.) (BSD 3-clause)
* :any:`policy.suffix_common`
* ``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.
- implements a subset of RPZ_ in zonefile format. See below for details: :any:`policy.rpz`.
* custom filter function
There are several defined actions:
There are several actions available in the ``policy.`` table:
* ``PASS`` - let the query pass through
* ``DENY`` - return NXDOMAIN answer
* ``DROP`` - terminate query resolution, returns SERVFAIL to requestor
* ``PASS`` - let the query pass through; it's useful to make exceptions before wider rules
* ``DENY`` - reply NXDOMAIN authoritatively
* ``DROP`` - terminate query resolution and return SERVFAIL to the requestor
* ``TC`` - set TC=1 if the request came through UDP, forcing client to retry with TCP
* ``FORWARD(ip)`` - solve a query via forwarding to an IP while validating and caching locally;
the parameter can be a single IP (string) or a lua list of up to four IPs.
* ``STUB(ip)`` - similar to ``FORWARD(ip)`` but *without* attempting DNSSEC validation.
Each request may be either answered from cache or simply sent to one of the IPs with proxying back the answer.
* ``MIRROR(ip)`` - mirror query to given IP and continue solving it (useful for partial snooping)
* ``MIRROR(ip)`` - mirror query to given IP and continue solving it (useful for partial snooping); it's a chain action
* ``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 :ref:`renumber module <mod-renumber>` for more information.
* ``QTRACE`` - pretty-print DNS response packets into the log (useful for debugging weird DNS servers).
* ``FLAGS(set, clear)`` - set and/or clear some flags for the query. There can be multiple flags to set/clear. You can just pass a single flag name (string) or a set of names.
* ``QTRACE`` - pretty-print DNS response packets into the log for the query and its sub-queries. It's useful for debugging weird DNS servers. It's a chain action.
* ``FLAGS(set, clear)`` - set and/or clear some flags for the query. There can be multiple flags to set/clear. You can just pass a single flag name (string) or a set of names. It's a chain action.
Most actions stop the policy matching on the query, but "chain actions" allow to keep trying to match other rules, until a non-chain action is triggered.
.. warning:: The policy module currently only looks at whole DNS requests. The rules won't be re-applied e.g. when following CNAMEs.
.. 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
^^^^^^^^^^^^^^^^^^^^^
Examples
^^^^^^^^
.. code-block:: lua
......@@ -66,101 +72,48 @@ Example configuration
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')))
-- Forward all queries (to public resolvers https://www.nic.cz/odvr)
policy.add(policy.all(policy.FORWARD({'2001:678:1::206', '193.29.206.206'})))
-- 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
^^^^^^^^^^
.. envvar:: policy.PASS
Pass-through all queries matching the rule.
.. envvar:: policy.DENY
Respond with NXDOMAIN to all queries matching the rule.
.. envvar:: policy.DROP
Drop all queries matching the rule.
.. envvar:: policy.TC
Respond with empty answer with TC bit set (if the query came through UDP).
.. envvar:: policy.FORWARD (address)
Forward query to given IP address.
.. envvar:: policy.MIRROR (address)
Forward query to given IP address.
.. envvar:: 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'.
.. envvar:: 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)
Additional properties
^^^^^^^^^^^^^^^^^^^^^
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.
Most properties (actions, filters) are described above.
.. function:: policy.add(rule, postrule)
:param rule: added rule, i.e. ``policy.pattern(policy.DENY, '[0-9]+\2cz')``
:param postrule: boolean, if true the rule will be evaluated on answer instead of query
:return: 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.
.. function:: policy.del(id)
:param id: identifier of a given rule
:return: boolean
Remove a rule from policy list.
.. function:: policy.all(action)
:param action: executed action for all queries
Perform action for all queries (no filtering).
.. function:: policy.pattern(action, pattern)
:param action: action if the pattern matches QNAME
:param pattern: regular expression
Policy to block queries based on the QNAME regex matching.
.. function:: policy.suffix(action, suffix_table)
:param action: action if the pattern matches QNAME
:param suffix_table: table of valid suffixes
Policy to block queries based on the QNAME suffix match.
Remove a rule from policy list.
.. function:: policy.suffix_common(action, suffix_table[, common_suffix])
:param action: action if the pattern matches QNAME
:param suffix_table: table of valid suffixes
:param 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").
......@@ -168,7 +121,7 @@ Properties
:param action: the default action for match in the zone (e.g. RH-value `.`)
:param 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:
......@@ -195,7 +148,7 @@ Properties
.. function:: policy.todnames({name, ...})
:param: names table of domain names in textual format
Returns table of domain names in wire format converted from strings.
.. code-block:: lua
......
......@@ -2,6 +2,8 @@ local kres = require('kres')
local bit = require('bit')
local ffi = require('ffi')
local todname = kres.str2dname -- not available during module load otherwise
-- Counter of unique rules
local nextid = 0
local function getruleid()
......@@ -141,6 +143,97 @@ local function flags(opts_set, opts_clear)
end
end
local function mkauth_soa(answer, dname, mname)
if mname == nil then
mname = dname
end
return answer:put(dname, 900, answer:qclass(), kres.type.SOA,
mname .. '\6nobody\7invalid\0\0\0\0\0\0\0\14\16\0\0\3\132\0\9\58\128\0\0\3\132')
end
local dname_localhost = todname('localhost.')
-- Rule for localhost. zone; see RFC6303, sec. 3
local function localhost(state, req)
local qry = req:current()
local answer = req.answer
ffi.C.kr_pkt_make_auth_header(answer)
local is_exact = ffi.C.knot_dname_is_equal(qry.sname, dname_localhost)
answer:rcode(kres.rcode.NOERROR)
answer:begin(kres.section.ANSWER)
if qry.stype == kres.type.AAAA then
answer:put(qry.sname, 900, answer:qclass(), kres.type.AAAA,
'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1')
elseif qry.stype == kres.type.A then
answer:put(qry.sname, 900, answer:qclass(), kres.type.A, '\127\0\0\1')
elseif is_exact and qry.stype == kres.type.SOA then
mkauth_soa(answer, dname_localhost)
elseif is_exact and qry.stype == kres.type.NS then
answer:put(dname_localhost, 900, answer:qclass(), kres.type.NS, dname_localhost)
else
answer:begin(kres.section.AUTHORITY)
mkauth_soa(answer, dname_localhost)
end
return kres.DONE
end
local dname_rev4_localhost = todname('1.0.0.127.in-addr.arpa');
local dname_rev4_localhost_apex = todname('127.in-addr.arpa');
-- Rule for reverse localhost.
-- Answer with locally served minimal 127.in-addr.arpa domain, only having
-- a PTR record in 1.0.0.127.in-addr.arpa, and with 1.0...0.ip6.arpa. zone.
-- TODO: much of this would better be left to the hints module (or coordinated).
local function localhost_reversed(state, req)
local qry = req:current()
local answer = req.answer
-- classify qry.sname:
local is_exact -- exact dname for localhost
local is_apex -- apex of a locally-served localhost zone
local is_nonterm -- empty non-terminal name
if ffi.C.knot_dname_is_sub(qry.sname, todname('ip6.arpa.')) then
-- exact ::1 query (relying on the calling rule)
is_exact = true
is_apex = true
else
-- within 127.in-addr.arpa.
local labels = ffi.C.knot_dname_labels(qry.sname, nil)
if labels == 3 then
is_exact = false
is_apex = true
elseif labels == 4+2 and ffi.C.knot_dname_is_equal(
qry.sname, dname_rev4_localhost) then
is_exact = true
else