at 23.11-pre 5.7 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 = lib.mdDoc '' 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 = mkOption { 61 type = types.package; 62 description = lib.mdDoc '' 63 knot-resolver package to use. 64 ''; 65 default = pkgs.knot-resolver; 66 defaultText = literalExpression "pkgs.knot-resolver"; 67 example = literalExpression "pkgs.knot-resolver.override { extraFeatures = true; }"; 68 }; 69 extraConfig = mkOption { 70 type = types.lines; 71 default = ""; 72 description = lib.mdDoc '' 73 Extra lines to be added verbatim to the generated configuration file. 74 ''; 75 }; 76 listenPlain = mkOption { 77 type = with types; listOf str; 78 default = [ "[::1]:53" "127.0.0.1:53" ]; 79 example = [ "53" ]; 80 description = lib.mdDoc '' 81 What addresses and ports the server should listen on. 82 For detailed syntax see ListenStream in {manpage}`systemd.socket(5)`. 83 ''; 84 }; 85 listenTLS = mkOption { 86 type = with types; listOf str; 87 default = []; 88 example = [ "198.51.100.1:853" "[2001:db8::1]:853" "853" ]; 89 description = lib.mdDoc '' 90 Addresses and ports on which kresd should provide DNS over TLS (see RFC 7858). 91 For detailed syntax see ListenStream in {manpage}`systemd.socket(5)`. 92 ''; 93 }; 94 listenDoH = mkOption { 95 type = with types; listOf str; 96 default = []; 97 example = [ "198.51.100.1:443" "[2001:db8::1]:443" "443" ]; 98 description = lib.mdDoc '' 99 Addresses and ports on which kresd should provide DNS over HTTPS/2 (see RFC 8484). 100 For detailed syntax see ListenStream in {manpage}`systemd.socket(5)`. 101 ''; 102 }; 103 instances = mkOption { 104 type = types.ints.unsigned; 105 default = 1; 106 description = lib.mdDoc '' 107 The number of instances to start. They will be called kresd@{1,2,...}.service. 108 Knot Resolver uses no threads, so this is the way to scale. 109 You can dynamically start/stop them at will, so this is just system default. 110 ''; 111 }; 112 # TODO: perhaps options for more common stuff like cache size or forwarding 113 }; 114 115 ###### implementation 116 config = mkIf cfg.enable { 117 environment.etc."knot-resolver/kresd.conf".source = configFile; # not required 118 119 networking.resolvconf.useLocalResolver = mkDefault true; 120 121 users.users.knot-resolver = 122 { isSystemUser = true; 123 group = "knot-resolver"; 124 description = "Knot-resolver daemon user"; 125 }; 126 users.groups.knot-resolver.gid = null; 127 128 systemd.packages = [ cfg.package ]; # the units are patched inside the package a bit 129 130 systemd.targets.kresd = { # configure units started by default 131 wantedBy = [ "multi-user.target" ]; 132 wants = [ "kres-cache-gc.service" ] 133 ++ map (i: "kresd@${toString i}.service") (range 1 cfg.instances); 134 }; 135 systemd.services."kresd@".serviceConfig = { 136 ExecStart = "${cfg.package}/bin/kresd --noninteractive " 137 + "-c ${cfg.package}/lib/knot-resolver/distro-preconfig.lua -c ${configFile}"; 138 # Ensure /run/knot-resolver exists 139 RuntimeDirectory = "knot-resolver"; 140 RuntimeDirectoryMode = "0770"; 141 # Ensure /var/lib/knot-resolver exists 142 StateDirectory = "knot-resolver"; 143 StateDirectoryMode = "0770"; 144 # Ensure /var/cache/knot-resolver exists 145 CacheDirectory = "knot-resolver"; 146 CacheDirectoryMode = "0770"; 147 }; 148 # We don't mind running stop phase from wrong version. It seems less racy. 149 systemd.services."kresd@".stopIfChanged = false; 150 }; 151}