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