1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 inherit (lib)
10 concatStringsSep
11 mkEnableOption
12 mkIf
13 mkOption
14 types
15 ;
16
17 cfg = config.services.https-dns-proxy;
18
19 providers = {
20 cloudflare = {
21 ips = [
22 "1.1.1.1"
23 "1.0.0.1"
24 ];
25 url = "https://cloudflare-dns.com/dns-query";
26 };
27 google = {
28 ips = [
29 "8.8.8.8"
30 "8.8.4.4"
31 ];
32 url = "https://dns.google/dns-query";
33 };
34 quad9 = {
35 ips = [
36 "9.9.9.9"
37 "149.112.112.112"
38 ];
39 url = "https://dns.quad9.net/dns-query";
40 };
41 opendns = {
42 ips = [
43 "208.67.222.222"
44 "208.67.220.220"
45 ];
46 url = "https://doh.opendns.com/dns-query";
47 };
48 custom = {
49 inherit (cfg.provider) ips url;
50 };
51 };
52
53 defaultProvider = "quad9";
54
55 providerCfg = concatStringsSep " " [
56 "-b"
57 (concatStringsSep "," providers."${cfg.provider.kind}".ips)
58 "-r"
59 providers."${cfg.provider.kind}".url
60 ];
61
62in
63{
64 meta.maintainers = with lib.maintainers; [ peterhoeg ];
65
66 ###### interface
67
68 options.services.https-dns-proxy = {
69 enable = mkEnableOption "https-dns-proxy daemon";
70
71 address = mkOption {
72 description = "The address on which to listen";
73 type = types.str;
74 default = "127.0.0.1";
75 };
76
77 port = mkOption {
78 description = "The port on which to listen";
79 type = types.port;
80 default = 5053;
81 };
82
83 provider = {
84 kind = mkOption {
85 description = ''
86 The upstream provider to use or custom in case you do not trust any of
87 the predefined providers or just want to use your own.
88
89 The default is ${defaultProvider} and there are privacy and security
90 trade-offs when using any upstream provider. Please consider that
91 before using any of them.
92
93 Supported providers: ${concatStringsSep ", " (builtins.attrNames providers)}
94
95 If you pick the custom provider, you will need to provide the
96 bootstrap IP addresses as well as the resolver https URL.
97 '';
98 type = types.enum (builtins.attrNames providers);
99 default = defaultProvider;
100 };
101
102 ips = mkOption {
103 description = "The custom provider IPs";
104 type = types.listOf types.str;
105 };
106
107 url = mkOption {
108 description = "The custom provider URL";
109 type = types.str;
110 };
111 };
112
113 preferIPv4 = mkOption {
114 description = ''
115 https_dns_proxy will by default use IPv6 and fail if it is not available.
116 To play it safe, we choose IPv4.
117 '';
118 type = types.bool;
119 default = true;
120 };
121
122 extraArgs = mkOption {
123 description = "Additional arguments to pass to the process.";
124 type = types.listOf types.str;
125 default = [ "-v" ];
126 };
127 };
128
129 ###### implementation
130
131 config = lib.mkIf cfg.enable {
132 systemd.services.https-dns-proxy = {
133 description = "DNS to DNS over HTTPS (DoH) proxy";
134 requires = [ "network.target" ];
135 after = [ "network.target" ];
136 wants = [ "nss-lookup.target" ];
137 before = [ "nss-lookup.target" ];
138 wantedBy = [ "multi-user.target" ];
139 serviceConfig = rec {
140 Type = "exec";
141 DynamicUser = true;
142 ProtectHome = "tmpfs";
143 ExecStart = lib.concatStringsSep " " (
144 [
145 (lib.getExe pkgs.https-dns-proxy)
146 "-a ${toString cfg.address}"
147 "-p ${toString cfg.port}"
148 "-l -"
149 providerCfg
150 ]
151 ++ lib.optional cfg.preferIPv4 "-4"
152 ++ cfg.extraArgs
153 );
154 Restart = "on-failure";
155 };
156 };
157 };
158}