2023-06-07 23:34:00 +00:00
{ config , lib , pkgs , . . . }:
2022-12-15 11:16:07 +00:00
2023-09-22 14:36:56 +00:00
let
2023-09-22 18:54:12 +00:00
bindLan = config . sane . hosts . by-name . " s e r v o " . lan-ip ;
bindHn = config . sane . hosts . by-name . " s e r v o " . wg-home . ip ;
bindOvpn = " 1 0 . 0 . 1 . 5 " ;
2023-09-22 14:36:56 +00:00
in
2022-12-15 11:16:07 +00:00
{
2023-07-13 09:57:11 +00:00
services . trust-dns . enable = true ;
2022-12-15 11:16:07 +00:00
2023-07-13 09:57:11 +00:00
services . trust-dns . settings . listen_addrs_ipv4 = [
2022-12-15 11:16:07 +00:00
# specify each address explicitly, instead of using "*".
# this ensures responses are sent from the address at which the request was received.
2023-09-22 14:36:56 +00:00
# it also allows to respond with different data based on the source of the traffic
" % L I S T E N % "
2022-12-15 11:16:07 +00:00
] ;
2023-07-15 09:07:57 +00:00
# don't bind to IPv6 until i explicitly test that stack
services . trust-dns . settings . listen_addrs_ipv6 = [ ] ;
2023-07-13 09:57:11 +00:00
services . trust-dns . quiet = true ;
# services.trust-dns.debug = true;
2023-07-02 08:21:33 +00:00
sane . ports . ports . " 5 3 " = {
protocol = [ " u d p " " t c p " ] ;
visibleTo . lan = true ;
visibleTo . wan = true ;
description = " c o l i n - d n s - h o s t i n g " ;
} ;
2022-12-15 11:16:07 +00:00
2023-06-07 23:34:00 +00:00
sane . dns . zones . " u n i n s a n e . o r g " . TTL = 900 ;
2022-12-19 04:00:27 +00:00
# SOA record structure: <https://en.wikipedia.org/wiki/SOA_record#Structure>
# SOA MNAME RNAME (... rest)
# MNAME = Master name server for this zone. this is where update requests should be sent.
# RNAME = admin contact (encoded email address)
# Serial = YYYYMMDDNN, where N is incremented every time this file changes, to trigger secondary NS to re-fetch it.
# Refresh = how frequently secondary NS should query master
# Retry = how long secondary NS should wait until re-querying master after a failure (must be < Refresh)
# Expire = how long secondary NS should continue to reply to queries after master fails (> Refresh + Retry)
2023-06-07 23:34:00 +00:00
sane . dns . zones . " u n i n s a n e . o r g " . inet = {
2023-01-02 13:23:52 +00:00
SOA . " @ " = ''
2022-12-19 04:38:41 +00:00
ns1 . uninsane . org . admin-dns . uninsane . org . (
2023-09-22 12:36:48 +00:00
2023092101 ; Serial
2022-12-19 04:38:41 +00:00
4 h ; Refresh
3 0 m ; Retry
7 d ; Expire
5 m ) ; Negative response TTL
2023-01-02 13:23:52 +00:00
'' ;
2023-09-22 12:36:48 +00:00
TXT . " r e v " = " 2 0 2 3 0 9 2 1 0 1 " ;
2023-05-30 12:00:30 +00:00
CNAME . " n a t i v e " = " % C N A M E N A T I V E % " ;
A . " @ " = " % A N A T I V E % " ;
A . " w a n " = " % A W A N % " ;
A . " s e r v o . l a n " = config . sane . hosts . by-name . " s e r v o " . lan-ip ;
2023-09-22 12:36:48 +00:00
A . " s e r v o . h n " = config . sane . hosts . by-name . " s e r v o " . wg-home . ip ;
2022-12-17 01:29:12 +00:00
2022-12-19 04:38:41 +00:00
# XXX NS records must also not be CNAME
# it's best that we keep this identical, or a superset of, what org. lists as our NS.
# so, org. can specify ns2/ns3 as being to the VPN, with no mention of ns1. we provide ns1 here.
2023-05-30 12:00:30 +00:00
A . " n s 1 " = " % A N A T I V E % " ;
2023-01-02 13:23:52 +00:00
A . " n s 2 " = " 1 8 5 . 1 5 7 . 1 6 2 . 1 7 8 " ;
A . " n s 3 " = " 1 8 5 . 1 5 7 . 1 6 2 . 1 7 8 " ;
A . " o v p n s " = " 1 8 5 . 1 5 7 . 1 6 2 . 1 7 8 " ;
2022-12-19 04:38:41 +00:00
NS . " @ " = [
" n s 1 . u n i n s a n e . o r g . "
" n s 2 . u n i n s a n e . o r g . "
" n s 3 . u n i n s a n e . o r g . "
] ;
} ;
2022-12-15 11:16:07 +00:00
2023-07-13 10:04:20 +00:00
services . trust-dns . settings . zones = [ " u n i n s a n e . o r g " ] ;
2023-05-30 12:00:30 +00:00
2023-07-13 09:57:11 +00:00
services . trust-dns . package =
2023-05-30 12:00:30 +00:00
let
sed = " ${ pkgs . gnused } / b i n / s e d " ;
zone-dir = " / v a r / l i b / t r u s t - d n s " ;
zone-wan = " ${ zone-dir } / w a n / u n i n s a n e . o r g . z o n e " ;
zone-lan = " ${ zone-dir } / l a n / u n i n s a n e . o r g . z o n e " ;
2023-09-22 12:36:48 +00:00
zone-hn = " ${ zone-dir } / h n / u n i n s a n e . o r g . z o n e " ;
2023-07-02 08:21:33 +00:00
zone-template = pkgs . writeText " u n i n s a n e . o r g . z o n e . i n " config . sane . dns . zones . " u n i n s a n e . o r g " . rendered ;
2023-09-22 18:54:12 +00:00
hn-resolver-config = pkgs . writeText " h n - r e s o l v e r - c o n f i g . t o m l " ''
# i host a resolver in the wireguard VPN so that clients can resolve DNS through the VPN.
# (that's what this file achieves).
#
# one would expect this resolver could host the authoritative zone for `uninsane.org`, and then forward everything else to the system resolver...
# and while that works for `dig`, it breaks for `nslookup` (and so `ssh`, etc).
#
# DNS responses include a flag for if the responding server is the authority of the zone queried.
# it seems that default Linux stub resolvers either:
# - expect DNSSEC when the response includes that bit, or
# - expect A records to be in the `answer` section instead of `additional` section.
# or perhaps something more nuanced. but for `nslookup` to be reliable, it has to talk to an
# instance of trust-dns which is strictly a resolver, with no authority.
# hence, this config: a resolver which forwards to the actual authority.
listen_addrs_ipv4 = [ " ${ bindHn } " ]
listen_addrs_ipv6 = [ ]
[ [ zones ] ]
zone = " u n i n s a n e . o r g "
zone_type = " F o r w a r d "
stores = { type = " f o r w a r d " , name_servers = [ { socket_addr = " ${ bindHn } : 1 0 5 3 " , protocol = " u d p " , trust_nx_responses = true } ] }
2023-09-22 12:36:48 +00:00
[ [ zones ] ]
# forward the root zone to the local DNS resolver
zone = " . "
zone_type = " F o r w a r d "
stores = { type = " f o r w a r d " , name_servers = [ { socket_addr = " 1 2 7 . 0 . 0 . 5 3 : 5 3 " , protocol = " u d p " , trust_nx_responses = true } ] }
'' ;
2023-09-22 18:54:12 +00:00
# TODO: rework this shell script to be independent systemd services per trust-dns instance.
2023-09-13 02:53:06 +00:00
in pkgs . writeShellScriptBin " t r u s t - d n s " ''
2023-09-22 14:36:56 +00:00
# intercept args meant for the real trust-dns, so i can modify them
2023-09-22 12:36:48 +00:00
_arg__config = " $ 1 " # --config
shift
orig_config = " $ 1 " # /path/to/config.toml
shift
# compute IP address of self for each interface
mkdir - p $ { zone-dir } / { wan , lan , hn }
2023-05-30 12:00:30 +00:00
wan = $ ( cat ' $ { config . sane . services . dyn-dns . ipPath } ' )
2023-09-22 18:54:12 +00:00
lan = $ { bindLan }
hn = $ { bindHn }
lanAndOvpn = ' $ { bindLan } " , " $ { bindOvpn } '
2023-05-30 12:00:30 +00:00
# create specializations that resolve native.uninsane.org to different CNAMEs
2023-09-22 14:36:56 +00:00
$ { sed } \
- e s / % AWAN % / $ wan / \
- e s / % CNAMENATIVE % /wan / \
- e s / % ANATIVE % / $ wan / \
$ { zone-template } > $ { zone-wan }
# to service WAN, listen both on LAN interface and the OVPN interface
sed " s / % L I S T E N % / $ l a n A n d O v p n / " $ orig_config > $ { zone-dir } /wan-config.toml
$ { sed } \
- e s / % AWAN % / $ wan / \
- e s / % CNAMENATIVE % /servo.lan / \
- e s / % ANATIVE % / $ lan / \
$ { zone-template } > $ { zone-lan }
2023-09-22 18:54:12 +00:00
# to service LAN, listen only on the LAN
2023-09-22 14:36:56 +00:00
sed s / % LISTEN % / $ lan / $ orig_config > $ { zone-dir } /lan-config.toml
$ { sed } \
- e s / % AWAN % / $ wan / \
- e s / % CNAMENATIVE % /servo.hn / \
- e s / % ANATIVE % / $ hn / \
$ { zone-template } > $ { zone-hn }
2023-09-22 18:54:12 +00:00
# to service wireguard, listen only on hn
sed s / % LISTEN % / $ hn / $ orig_config > $ { zone-dir } /hn-config.toml
2022-12-15 11:16:07 +00:00
2023-05-30 12:00:30 +00:00
# launch the different interfaces, separately
2023-09-22 14:36:56 +00:00
$ { pkgs . trust-dns } /bin/trust-dns \
2023-09-22 12:36:48 +00:00
- - zonedir " ${ zone-dir } / w a n / " - - config " ${ zone-dir } / w a n - c o n f i g . t o m l " \
" $ @ " &
2023-05-30 12:00:30 +00:00
WANPID = $ !
2023-09-22 12:36:48 +00:00
$ { pkgs . trust-dns } /bin/trust-dns - - port 1053 \
- - zonedir " ${ zone-dir } / l a n / " - - config " ${ zone-dir } / l a n - c o n f i g . t o m l " \
" $ @ " &
2023-05-30 12:00:30 +00:00
LANPID = $ !
2023-09-22 18:54:12 +00:00
ln - sf $ { hn-resolver-config } $ { zone-dir } /hn-resolver-config.toml
2023-09-22 14:36:56 +00:00
$ { pkgs . trust-dns } /bin/trust-dns \
2023-09-22 18:54:12 +00:00
- - config " ${ zone-dir } / h n - r e s o l v e r - c o n f i g . t o m l " \
" $ @ " &
HNRESOLVERPID = $ !
$ { pkgs . trust-dns } /bin/trust-dns - - port 1053 \
2023-09-22 12:36:48 +00:00
- - zonedir " ${ zone-dir } / h n / " - - config " ${ zone-dir } / h n - c o n f i g . t o m l " \
" $ @ " &
HNPID = $ !
2023-05-30 12:00:30 +00:00
# wait until any of the processes exits, then kill them all and exit error
2023-09-22 18:54:12 +00:00
while kill -0 $ WANPID $ LANPID $ HNRESOLVERPID $ HNPID ; do
2023-05-30 12:00:30 +00:00
sleep 5
done
2023-09-22 18:54:12 +00:00
kill $ WANPID $ LANPID $ HNRESOLVERPID $ HNPID
2023-05-30 12:00:30 +00:00
exit 1
'' ;
2022-12-15 11:16:07 +00:00
2023-07-10 09:00:37 +00:00
systemd . services . trust-dns . serviceConfig = {
DynamicUser = lib . mkForce false ;
2023-07-15 09:07:57 +00:00
User = " t r u s t - d n s " ;
Group = " t r u s t - d n s " ;
2023-07-10 09:00:37 +00:00
} ;
users . groups . trust-dns = { } ;
users . users . trust-dns = {
group = " t r u s t - d n s " ;
isSystemUser = true ;
} ;
2022-12-19 13:12:21 +00:00
sane . services . dyn-dns . restartOnChange = [ " t r u s t - d n s . s e r v i c e " ] ;
2023-05-30 12:00:30 +00:00
2023-05-31 00:56:52 +00:00
networking . nat . enable = true ;
networking . nat . extraCommands = ''
# redirect incoming DNS requests from LAN addresses
# to the LAN-specialized DNS service
# N.B.: use the `nixos-*` chains instead of e.g. PREROUTING
# because they get cleanly reset across activations or `systemctl restart firewall`
# instead of accumulating cruft
iptables - t nat - A nixos-nat-pre - p udp - - dport 53 \
- m iprange - - src-range 10 .78 .76 . 0 -10 .78 .79 .255 \
- j DNAT - - to-destination : 1053
iptables - t nat - A nixos-nat-pre - p tcp - - dport 53 \
- m iprange - - src-range 10 .78 .76 . 0 -10 .78 .79 .255 \
- j DNAT - - to-destination : 1053
'' ;
2023-05-31 04:25:39 +00:00
sane . ports . ports . " 1 0 5 3 " = {
# because the NAT above redirects in nixos-nat-pre, LAN requests behave as though they arrived on the external interface at the redirected port.
# TODO: try nixos-nat-post instead?
2023-09-22 14:36:56 +00:00
# TODO: or, don't NAT from port 53 -> port 1053, but rather nat from LAN addr to a loopback addr.
# - this is complicated in that loopback is a different interface than eth0, so rewriting the destination address would cause the packets to just be dropped by the interface
2023-05-31 04:25:39 +00:00
protocol = [ " u d p " " t c p " ] ;
visibleTo . lan = true ;
description = " c o l i n - r e d i r e c t e d - d n s - f o r - l a n - n a m e s p a c e " ;
} ;
2023-09-22 14:36:56 +00:00
2022-12-15 11:16:07 +00:00
}