at 24.11-pre 5.4 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.kresd; 7 8 # Convert systemd-style address specification to kresd config line(s). 9 # On Nix level we don't attempt to precisely validate the address specifications. 10 # The optional IPv6 scope spec comes *after* port, perhaps surprisingly. 11 mkListen = kind: addr: let 12 al_v4 = builtins.match "([0-9.]+):([0-9]+)($)" addr; 13 al_v6 = builtins.match "\\[(.+)]:([0-9]+)(%.*|$)" addr; 14 al_portOnly = builtins.match "(^)([0-9]+)" addr; 15 al = findFirst (a: a != null) 16 (throw "services.kresd.*: incorrect address specification '${addr}'") 17 [ al_v4 al_v6 al_portOnly ]; 18 port = elemAt al 1; 19 addrSpec = if al_portOnly == null then "'${head al}${elemAt al 2}'" else "{'::', '0.0.0.0'}"; 20 in # freebind is set for compatibility with earlier kresd services; 21 # it could be configurable, for example. 22 '' 23 net.listen(${addrSpec}, ${port}, { kind = '${kind}', freebind = true }) 24 ''; 25 26 configFile = pkgs.writeText "kresd.conf" ( 27 "" 28 + concatMapStrings (mkListen "dns") cfg.listenPlain 29 + concatMapStrings (mkListen "tls") cfg.listenTLS 30 + concatMapStrings (mkListen "doh2") cfg.listenDoH 31 + cfg.extraConfig 32 ); 33in { 34 meta.maintainers = [ maintainers.vcunat /* upstream developer */ ]; 35 36 imports = [ 37 (mkChangedOptionModule [ "services" "kresd" "interfaces" ] [ "services" "kresd" "listenPlain" ] 38 (config: 39 let value = getAttrFromPath [ "services" "kresd" "interfaces" ] config; 40 in map 41 (iface: if elem ":" (stringToCharacters iface) then "[${iface}]:53" else "${iface}:53") # Syntax depends on being IPv6 or IPv4. 42 value 43 ) 44 ) 45 (mkRemovedOptionModule [ "services" "kresd" "cacheDir" ] "Please use (bind-)mounting instead.") 46 ]; 47 48 ###### interface 49 options.services.kresd = { 50 enable = mkOption { 51 type = types.bool; 52 default = false; 53 description = '' 54 Whether to enable knot-resolver domain name server. 55 DNSSEC validation is turned on by default. 56 You can run `sudo nc -U /run/knot-resolver/control/1` 57 and give commands interactively to kresd@1.service. 58 ''; 59 }; 60 package = mkPackageOption pkgs "knot-resolver" { 61 example = "knot-resolver.override { extraFeatures = true; }"; 62 }; 63 extraConfig = mkOption { 64 type = types.lines; 65 default = ""; 66 description = '' 67 Extra lines to be added verbatim to the generated configuration file. 68 ''; 69 }; 70 listenPlain = mkOption { 71 type = with types; listOf str; 72 default = [ "[::1]:53" "127.0.0.1:53" ]; 73 example = [ "53" ]; 74 description = '' 75 What addresses and ports the server should listen on. 76 For detailed syntax see ListenStream in {manpage}`systemd.socket(5)`. 77 ''; 78 }; 79 listenTLS = mkOption { 80 type = with types; listOf str; 81 default = []; 82 example = [ "198.51.100.1:853" "[2001:db8::1]:853" "853" ]; 83 description = '' 84 Addresses and ports on which kresd should provide DNS over TLS (see RFC 7858). 85 For detailed syntax see ListenStream in {manpage}`systemd.socket(5)`. 86 ''; 87 }; 88 listenDoH = mkOption { 89 type = with types; listOf str; 90 default = []; 91 example = [ "198.51.100.1:443" "[2001:db8::1]:443" "443" ]; 92 description = '' 93 Addresses and ports on which kresd should provide DNS over HTTPS/2 (see RFC 8484). 94 For detailed syntax see ListenStream in {manpage}`systemd.socket(5)`. 95 ''; 96 }; 97 instances = mkOption { 98 type = types.ints.unsigned; 99 default = 1; 100 description = '' 101 The number of instances to start. They will be called kresd@{1,2,...}.service. 102 Knot Resolver uses no threads, so this is the way to scale. 103 You can dynamically start/stop them at will, so this is just system default. 104 ''; 105 }; 106 # TODO: perhaps options for more common stuff like cache size or forwarding 107 }; 108 109 ###### implementation 110 config = mkIf cfg.enable { 111 environment.etc."knot-resolver/kresd.conf".source = configFile; # not required 112 113 networking.resolvconf.useLocalResolver = mkDefault true; 114 115 users.users.knot-resolver = 116 { isSystemUser = true; 117 group = "knot-resolver"; 118 description = "Knot-resolver daemon user"; 119 }; 120 users.groups.knot-resolver.gid = null; 121 122 systemd.packages = [ cfg.package ]; # the units are patched inside the package a bit 123 124 systemd.targets.kresd = { # configure units started by default 125 wantedBy = [ "multi-user.target" ]; 126 wants = [ "kres-cache-gc.service" ] 127 ++ map (i: "kresd@${toString i}.service") (range 1 cfg.instances); 128 }; 129 systemd.services."kresd@".serviceConfig = { 130 ExecStart = "${cfg.package}/bin/kresd --noninteractive " 131 + "-c ${cfg.package}/lib/knot-resolver/distro-preconfig.lua -c ${configFile}"; 132 # Ensure /run/knot-resolver exists 133 RuntimeDirectory = "knot-resolver"; 134 RuntimeDirectoryMode = "0770"; 135 # Ensure /var/lib/knot-resolver exists 136 StateDirectory = "knot-resolver"; 137 StateDirectoryMode = "0770"; 138 # Ensure /var/cache/knot-resolver exists 139 CacheDirectory = "knot-resolver"; 140 CacheDirectoryMode = "0770"; 141 }; 142 # We don't mind running stop phase from wrong version. It seems less racy. 143 systemd.services."kresd@".stopIfChanged = false; 144 }; 145}