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