1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 cfg = config.services.kresd;
8 package = pkgs.knot-resolver;
9
10 configFile = pkgs.writeText "kresd.conf" cfg.extraConfig;
11in
12
13{
14 meta.maintainers = [ maintainers.vcunat /* upstream developer */ ];
15
16 ###### interface
17 options.services.kresd = {
18 enable = mkOption {
19 type = types.bool;
20 default = false;
21 description = ''
22 Whether to enable knot-resolver domain name server.
23 DNSSEC validation is turned on by default.
24 You can run <literal>sudo nc -U /run/kresd/control</literal>
25 and give commands interactively to kresd.
26 '';
27 };
28 extraConfig = mkOption {
29 type = types.lines;
30 default = "";
31 description = ''
32 Extra lines to be added verbatim to the generated configuration file.
33 '';
34 };
35 cacheDir = mkOption {
36 type = types.path;
37 default = "/var/cache/kresd";
38 description = ''
39 Directory for caches. They are intended to survive reboots.
40 '';
41 };
42 interfaces = mkOption {
43 type = with types; listOf str;
44 default = [ "::1" "127.0.0.1" ];
45 description = ''
46 What addresses the server should listen on. (UDP+TCP 53)
47 '';
48 };
49 listenTLS = mkOption {
50 type = with types; listOf str;
51 default = [];
52 example = [ "198.51.100.1:853" "[2001:db8::1]:853" "853" ];
53 description = ''
54 Addresses on which kresd should provide DNS over TLS (see RFC 7858).
55 For detailed syntax see ListenStream in man systemd.socket.
56 '';
57 };
58 # TODO: perhaps options for more common stuff like cache size or forwarding
59 };
60
61 ###### implementation
62 config = mkIf cfg.enable {
63 environment.etc."kresd.conf".source = configFile; # not required
64
65 users.extraUsers = singleton
66 { name = "kresd";
67 uid = config.ids.uids.kresd;
68 group = "kresd";
69 description = "Knot-resolver daemon user";
70 };
71 users.extraGroups = singleton
72 { name = "kresd";
73 gid = config.ids.gids.kresd;
74 };
75
76 systemd.sockets.kresd = rec {
77 wantedBy = [ "sockets.target" ];
78 before = wantedBy;
79 listenStreams = map
80 # Syntax depends on being IPv6 or IPv4.
81 (iface: if elem ":" (stringToCharacters iface) then "[${iface}]:53" else "${iface}:53")
82 cfg.interfaces;
83 socketConfig.ListenDatagram = listenStreams;
84 socketConfig.FreeBind = true;
85 };
86
87 systemd.sockets.kresd-tls = mkIf (cfg.listenTLS != []) rec {
88 wantedBy = [ "sockets.target" ];
89 before = wantedBy;
90 partOf = [ "kresd.socket" ];
91 listenStreams = cfg.listenTLS;
92 socketConfig = {
93 FileDescriptorName = "tls";
94 FreeBind = true;
95 Service = "kresd.service";
96 };
97 };
98
99 systemd.sockets.kresd-control = rec {
100 wantedBy = [ "sockets.target" ];
101 before = wantedBy;
102 partOf = [ "kresd.socket" ];
103 listenStreams = [ "/run/kresd/control" ];
104 socketConfig = {
105 FileDescriptorName = "control";
106 Service = "kresd.service";
107 SocketMode = "0660"; # only root user/group may connect and control kresd
108 };
109 };
110
111 systemd.tmpfiles.rules = [ "d '${cfg.cacheDir}' 0770 kresd kresd - -" ];
112
113 systemd.services.kresd = {
114 description = "Knot-resolver daemon";
115
116 serviceConfig = {
117 User = "kresd";
118 Type = "notify";
119 WorkingDirectory = cfg.cacheDir;
120 Restart = "on-failure";
121 Sockets = [ "kresd.socket" "kresd-control.socket" ]
122 ++ optional (cfg.listenTLS != []) "kresd-tls.socket";
123 };
124
125 # Trust anchor goes from dns-root-data by default.
126 script = ''
127 exec '${package}/bin/kresd' --config '${configFile}' --forks=1
128 '';
129
130 requires = [ "kresd.socket" ];
131 };
132 };
133}