unbound -> knot-resolver

This commit is contained in:
Casper V. Kristensen 2024-04-10 00:38:22 +02:00
parent f8b1cd1a2b
commit b018abc3d8
4 changed files with 83 additions and 107 deletions

View file

@ -2,10 +2,11 @@
security.acme.certs."caspervk.net" = {
domain = "*.caspervk.net";
reloadServices = [
"unbound.service"
"kresd@1.service"
"kresd@2.service"
];
};
users.groups.acme.members = [
"unbound"
"knot-resolver"
];
}

View file

@ -5,8 +5,8 @@
../../modules/server
./acme.nix
./hardware.nix
./knot-resolver.nix
./network.nix
./unbound.nix
];
networking.hostName = "delta";

View file

@ -0,0 +1,79 @@
{
config,
pkgs,
...
}: {
# Knot Resolver is a minimalistic implementation of a caching validating DNS
# resolver. Modular architecture keeps the core tiny and efficient, and it
# provides a state-machine like API for extensions.
# https://knot-resolver.readthedocs.io/en/stable/index.html
#
# Test resolver:
# > nix shell nixpkgs#knot-dns
# > kdig -d @dns.caspervk.net example.com
# > kdig -d +https @dns.caspervk.net example.com
# > kdig -d +tls @dns.caspervk.net example.com
#
# Connect to control socket:
# > nix shell nixpkgs#socat -c sudo socat readline UNIX-CONNECT:/run/knot-resolver/control/1
# >> help()
# https://knot-resolver.readthedocs.io/en/stable/daemon-scripting.html
services.kresd = {
enable = true;
# For maximum performance, there should be as many kresd processes as there
# are available CPU threads.
# https://knot-resolver.readthedocs.io/en/stable/systemd-multiinst.html
instances = 2;
# Explicitly listen to DNS/DoH/DoT on the external interface(s). This
# allows systemd-resolved to listen on localhost as on every other system.
listenPlain = [
"159.69.4.2:53"
"[2a01:4f8:1c0c:70d1::1]:53"
];
listenTLS = [
"159.69.4.2:853"
"[2a01:4f8:1c0c:70d1::1]:853"
];
listenDoH = [
"159.69.4.2:443"
"[2a01:4f8:1c0c:70d1::1]:443"
];
extraConfig = ''
-- TLS certificate for DoT and DoH
-- https://knot-resolver.readthedocs.io/en/stable/daemon-bindings-net_tlssrv.html
net.tls(
"${config.security.acme.certs."caspervk.net".directory}/fullchain.pem",
"${config.security.acme.certs."caspervk.net".directory}/key.pem"
)
-- Cache is stored in /var/cache/knot-resolver, which is mounted as
-- tmpfs. Allow using 90% of the partition for caching.
-- https://knot-resolver.readthedocs.io/en/stable/daemon-bindings-cache.html
cache.size = math.floor(cache.fssize() * 0.9)
-- The predict module helps to keep the cache hot by prefetching
-- records. Any time the resolver answers with records that are about to
-- expire, they get refreshed.
-- https://knot-resolver.readthedocs.io/en/stable/modules-predict.html
modules.load("predict")
-- Block spam and advertising domains
-- https://knot-resolver.readthedocs.io/en/stable/modules-policy.html#response-policy-zones
policy.add(
policy.rpz(
policy.ANSWER({ [kres.type.A] = {rdata=kres.str2ip("0.0.0.0"), ttl = 600} }),
"${pkgs.runCommand "stevenblack-blocklist-rpz" {} ''grep '^0\.0\.0\.0' ${pkgs.stevenblack-blocklist}/hosts | awk '{print $2 " 600 IN CNAME .\n*." $2 " 600 IN CNAME ."}' > $out''}"
)
)
-- Test domain to verify DNS server is being used
policy.add(
policy.domains(
policy.ANSWER({ [kres.type.A] = {rdata = kres.str2ip("192.0.2.0"), ttl = 5} }),
policy.todnames({"test.dns.caspervk.net"})
)
)
'';
};
networking.firewall = {
allowedTCPPorts = [443 853];
allowedUDPPorts = [53];
};
}

View file

@ -1,104 +0,0 @@
{
config,
pkgs,
...
}: {
# Unbound is a validating, recursive, caching DNS resolver. It is designed to
# be fast and lean and incorporates modern features based on open standards.
# https://unbound.docs.nlnetlabs.nl/en/latest/manpages/unbound.conf.html
# > nix shell nixpkgs#knot-dns
# > kdig -d @dns.caspervk.net example.com
# > kdig -d +https @dns.caspervk.net example.com
# > kdig -d +tls @dns.caspervk.net example.com
services.unbound = {
enable = true;
# Enable `unbound-control` to view stats stats etc.
localControlSocketPath = "/run/unbound/unbound.ctl";
# Don't mess with resolvconf
resolveLocalQueries = false;
settings = {
server = {
# Explicitly listen to DNS/DoH/DoT on the external interface(s). This
# allows systemd-resolved to listen on localhost as on every other
# system. Default is to listen to DNS on localhost only.
interface = [
"159.69.4.2@53"
"159.69.4.2@443"
"159.69.4.2@853"
"2a01:4f8:1c0c:70d1::1@53"
"2a01:4f8:1c0c:70d1::1@443"
"2a01:4f8:1c0c:70d1::1@853"
];
# Allow access from all netblocks. Default is to allow localhost only.
access-control = [
"0.0.0.0/0 allow"
"::0/0 allow"
];
# Provide DNS-over-TLS or DNS-over-HTTPS service
tls-service-key = "${config.security.acme.certs."caspervk.net".directory}/key.pem";
tls-service-pem = "${config.security.acme.certs."caspervk.net".directory}/fullchain.pem";
# Enable global ratelimiting of queries accepted per IP address. This
# does not seem to impact TCP/DoH/DoT queries. Tested by adding +tcp,
# +https, or +tls to the following (run it twice to warm up cache):
# > seq 100 | xargs -n1 -I% dig @dns.caspervk.net %.example.net
ip-ratelimit = 25;
# Use 0x20-encoded random bits in the query to foil spoof attempts.
# This perturbs the lowercase and uppercase of query names sent to
# authority servers and checks if the reply still has the correct
# casing.
use-caps-for-id = true;
# Increase cache-hit ratio by serving old responses from the cache:
# Before trying to resolve, Unbound will also consider expired cached
# records as possible answers. If such a record is found it is
# immediately returned to the client, Unbound then continues resolving
# and hopefully updating the cached record. Used together with
# prefetch, Unbound tries to update a cached record (after first
# replying to the client) when the current TTL is within 10% of the
# original TTL value. Although prefetching comes with a small penalty
# of ~10% in traffic and load from the extra upstream queries, the
# cache is kept up-to-date, at least for popular queries.
#
# Using serve-expired with prefetch is "highly recommended in order to
# try and keep an updated cache". The following allows Unbound to:
# - prioritize (expired) cached replies,
# - keep the cache fairly up-to-date, and
# - in the likelihood that an expired record needs to be served (e.g.,
# rare query, issue with upstream resolving), make sure that the
# record is not older than the specified limit.
# https://unbound.docs.nlnetlabs.nl/en/latest/topics/core/serve-stale.html
prefetch = true;
serve-expired = true;
serve-expired-ttl = 14400; # 4 hours
# Fetch the DNSKEYs earlier in the validation process, when a
# DS record is encountered. This lowers the latency of requests.
prefetch-key = true;
# Increase the memory size of the cache. Use roughly twice as much
# rrset cache memory as you use msg cache memory. Due to malloc
# overhead, the total memory usage is likely to rise to double (or
# 2.5x) the total cache memory that is entered into the configuration.
# https://unbound.docs.nlnetlabs.nl/en/latest/topics/core/performance.html
rrset-cache-size = "512m";
msg-cache-size = "256m";
# Testing domain
local-zone = ["\"test.dns.caspervk.net.\" redirect"];
local-data = ["\"test.dns.caspervk.net. A 192.0.2.0\""];
include = [
(
# The awk magic is from
# https://deadc0de.re/articles/unbound-blocking-ads.html which is
# linked from the StevenBlack GitHub.
# https://nixos.org/manual/nixpkgs/stable/#trivial-builder-runCommand
builtins.toString (pkgs.runCommand "stevenblack-blocklist-unbound" {} ''
grep '^0\.0\.0\.0' ${pkgs.stevenblack-blocklist}/hosts | awk '{if (NR==1) {print "server:"} print " local-zone: \""$2"\" redirect\n local-data: \""$2" A 0.0.0.0\""}' > $out
'')
)
];
};
};
};
networking.firewall = {
allowedTCPPorts = [443 853];
allowedUDPPorts = [53];
};
}