1{ config, lib, pkgs, ...}:
2
3with lib;
4
5let
6 cfg = config.services.stubby;
7
8 fallbacks = concatMapStringsSep "\n " (x: "- ${x}") cfg.fallbackProtocols;
9 listeners = concatMapStringsSep "\n " (x: "- ${x}") cfg.listenAddresses;
10
11 # By default, the recursive resolvers maintained by the getdns
12 # project itself are enabled. More information about both getdns's servers,
13 # as well as third party options for upstream resolvers, can be found here:
14 # https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Test+Servers
15 #
16 # You can override these values by supplying a yaml-formatted array of your
17 # preferred upstream resolvers in the following format:
18 #
19 # 106 # - address_data: IPv4 or IPv6 address of the upstream
20 # port: Port for UDP/TCP (default is 53)
21 # tls_auth_name: Authentication domain name checked against the server
22 # certificate
23 # tls_pubkey_pinset: An SPKI pinset verified against the keys in the server
24 # certificate
25 # - digest: Only "sha256" is currently supported
26 # value: Base64 encoded value of the sha256 fingerprint of the public
27 # key
28 # tls_port: Port for TLS (default is 853)
29
30 defaultUpstream = ''
31 - address_data: 145.100.185.15
32 tls_auth_name: "dnsovertls.sinodun.com"
33 tls_pubkey_pinset:
34 - digest: "sha256"
35 value: 62lKu9HsDVbyiPenApnc4sfmSYTHOVfFgL3pyB+cBL4=
36 - address_data: 145.100.185.16
37 tls_auth_name: "dnsovertls1.sinodun.com"
38 tls_pubkey_pinset:
39 - digest: "sha256"
40 value: cE2ecALeE5B+urJhDrJlVFmf38cJLAvqekONvjvpqUA=
41 - address_data: 185.49.141.37
42 tls_auth_name: "getdnsapi.net"
43 tls_pubkey_pinset:
44 - digest: "sha256"
45 value: foxZRnIh9gZpWnl+zEiKa0EJ2rdCGroMWm02gaxSc9Q=
46 - address_data: 2001:610:1:40ba:145:100:185:15
47 tls_auth_name: "dnsovertls.sinodun.com"
48 tls_pubkey_pinset:
49 - digest: "sha256"
50 value: 62lKu9HsDVbyiPenApnc4sfmSYTHOVfFgL3pyB+cBL4=
51 - address_data: 2001:610:1:40ba:145:100:185:16
52 tls_auth_name: "dnsovertls1.sinodun.com"
53 tls_pubkey_pinset:
54 - digest: "sha256"
55 value: cE2ecALeE5B+urJhDrJlVFmf38cJLAvqekONvjvpqUA=
56 - address_data: 2a04:b900:0:100::38
57 tls_auth_name: "getdnsapi.net"
58 tls_pubkey_pinset:
59 - digest: "sha256"
60 value: foxZRnIh9gZpWnl+zEiKa0EJ2rdCGroMWm02gaxSc9Q=
61 '';
62
63 # Resolution type is not changeable here because it is required per the
64 # stubby documentation:
65 #
66 # "resolution_type: Work in stub mode only (not recursive mode) - required for Stubby
67 # operation."
68 #
69 # https://dnsprivacy.org/wiki/display/DP/Configuring+Stubby
70
71 confFile = pkgs.writeText "stubby.yml" ''
72 resolution_type: GETDNS_RESOLUTION_STUB
73 dns_transport_list:
74 ${fallbacks}
75 appdata_dir: "/var/cache/stubby"
76 tls_authentication: ${cfg.authenticationMode}
77 tls_query_padding_blocksize: ${toString cfg.queryPaddingBlocksize}
78 edns_client_subnet_private: ${if cfg.subnetPrivate then "1" else "0"}
79 idle_timeout: ${toString cfg.idleTimeout}
80 listen_addresses:
81 ${listeners}
82 round_robin_upstreams: ${if cfg.roundRobinUpstreams then "1" else "0"}
83 ${cfg.extraConfig}
84 upstream_recursive_servers:
85 ${cfg.upstreamServers}
86 '';
87in
88
89{
90 options = {
91 services.stubby = {
92
93 enable = mkEnableOption "Stubby DNS resolver";
94
95 fallbackProtocols = mkOption {
96 default = [ "GETDNS_TRANSPORT_TLS" ];
97 type = with types; listOf (enum [
98 "GETDNS_TRANSPORT_TLS"
99 "GETDNS_TRANSPORT_TCP"
100 "GETDNS_TRANSPORT_UDP"
101 ]);
102 description = ''
103 Ordered list composed of one or more transport protocols.
104 Strict mode should only use <literal>GETDNS_TRANSPORT_TLS</literal>.
105 Other options are <literal>GETDNS_TRANSPORT_UDP</literal> and
106 <literal>GETDNS_TRANSPORT_TCP</literal>.
107 '';
108 };
109
110 authenticationMode = mkOption {
111 default = "GETDNS_AUTHENTICATION_REQUIRED";
112 type = types.enum [
113 "GETDNS_AUTHENTICATION_REQUIRED"
114 "GETDNS_AUTHENTICATION_NONE"
115 ];
116 description = ''
117 Selects the Strict or Opportunistic usage profile.
118 For strict, set to <literal>GETDNS_AUTHENTICATION_REQUIRED</literal>.
119 for opportunistic, use <literal>GETDNS_AUTHENTICATION_NONE</literal>.
120 '';
121 };
122
123 queryPaddingBlocksize = mkOption {
124 default = 128;
125 type = types.int;
126 description = ''
127 EDNS0 option to pad the size of the DNS query to the given blocksize.
128 '';
129 };
130
131 subnetPrivate = mkOption {
132 default = true;
133 type = types.bool;
134 description = ''
135 EDNS0 option for ECS client privacy. Default is
136 <literal>true</literal>. If set, this option prevents the client
137 subnet from being sent to authoritative nameservers.
138 '';
139 };
140
141 idleTimeout = mkOption {
142 default = 10000;
143 type = types.int;
144 description = "EDNS0 option for keepalive idle timeout expressed in
145 milliseconds.";
146 };
147
148 listenAddresses = mkOption {
149 default = [ "127.0.0.1" "0::1" ];
150 type = with types; listOf str;
151 description = ''
152 Sets the listen address for the stubby daemon.
153 Uses port 53 by default.
154 Ise IP@port to specify a different port.
155 '';
156 };
157
158 roundRobinUpstreams = mkOption {
159 default = true;
160 type = types.bool;
161 description = ''
162 Instructs stubby to distribute queries across all available name
163 servers. Default is <literal>true</literal>. Set to
164 <literal>false</literal> in order to use the first available.
165 '';
166 };
167
168 upstreamServers = mkOption {
169 default = defaultUpstream;
170 type = types.lines;
171 description = ''
172 Replace default upstreams. See <citerefentry><refentrytitle>stubby
173 </refentrytitle><manvolnum>1</manvolnum></citerefentry> for an
174 example of the entry formatting. In Strict mode, at least one of the
175 following settings must be supplied for each nameserver:
176 <literal>tls_auth_name</literal> or
177 <literal>tls_pubkey_pinset</literal>.
178 '';
179 };
180
181 debugLogging = mkOption {
182 default = false;
183 type = types.bool;
184 description = "Enable or disable debug level logging.";
185 };
186
187 extraConfig = mkOption {
188 default = "";
189 type = types.lines;
190 description = ''
191 Add additional configuration options. see <citerefentry>
192 <refentrytitle>stubby</refentrytitle><manvolnum>1</manvolnum>
193 </citerefentry>for more options.
194 '';
195 };
196 };
197 };
198
199 config = mkIf cfg.enable {
200 environment.systemPackages = [ pkgs.stubby ];
201 systemd.services.stubby = {
202 description = "Stubby local DNS resolver";
203 after = [ "network.target" ];
204 before = [ "nss-lookup.target" ];
205 wantedBy = [ "multi-user.target" ];
206
207 serviceConfig = {
208 Type = "notify";
209 AmbientCapabilities = "CAP_NET_BIND_SERVICE";
210 CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
211 ExecStart = "${pkgs.stubby}/bin/stubby -C ${confFile} ${optionalString cfg.debugLogging "-l"}";
212 DynamicUser = true;
213 CacheDirectory = "stubby";
214 };
215 };
216 };
217}