1{
2 lib,
3 config,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.zapret;
9
10 whitelist = lib.optionalString (
11 (builtins.length cfg.whitelist) != 0
12 ) "--hostlist ${pkgs.writeText "zapret-whitelist" (lib.concatStringsSep "\n" cfg.whitelist)}";
13
14 blacklist =
15 lib.optionalString ((builtins.length cfg.blacklist) != 0)
16 "--hostlist-exclude ${pkgs.writeText "zapret-blacklist" (lib.concatStringsSep "\n" cfg.blacklist)}";
17
18 params = lib.concatStringsSep " " cfg.params;
19
20 qnum = toString cfg.qnum;
21in
22{
23 options.services.zapret = {
24 enable = lib.mkEnableOption "the Zapret DPI bypass service.";
25 package = lib.mkPackageOption pkgs "zapret" { };
26 params = lib.mkOption {
27 default = [ ];
28 type = with lib.types; listOf str;
29 example = ''
30 [
31 "--dpi-desync=fake,disorder2"
32 "--dpi-desync-ttl=1"
33 "--dpi-desync-autottl=2"
34 ]
35 '';
36 description = ''
37 Specify the bypass parameters for Zapret binary.
38 There are no universal parameters as they vary between different networks, so you'll have to find them yourself.
39
40 This can be done by running the `blockcheck` binary from zapret package, i.e. `nix-shell -p zapret --command blockcheck`.
41 It'll try different params and then tell you which params are working for your network.
42 '';
43 };
44 whitelist = lib.mkOption {
45 default = [ ];
46 type = with lib.types; listOf str;
47 example = ''
48 [
49 "youtube.com"
50 "googlevideo.com"
51 "ytimg.com"
52 "youtu.be"
53 ]
54 '';
55 description = ''
56 Specify a list of domains to bypass. All other domains will be ignored.
57 You can specify either whitelist or blacklist, but not both.
58 If neither are specified, then bypass all domains.
59
60 It is recommended to specify the whitelist. This will make sure that other resources won't be affected by this service.
61 '';
62 };
63 blacklist = lib.mkOption {
64 default = [ ];
65 type = with lib.types; listOf str;
66 example = ''
67 [
68 "example.com"
69 ]
70 '';
71 description = ''
72 Specify a list of domains NOT to bypass. All other domains will be bypassed.
73 You can specify either whitelist or blacklist, but not both.
74 If neither are specified, then bypass all domains.
75 '';
76 };
77 qnum = lib.mkOption {
78 default = 200;
79 type = lib.types.int;
80 description = ''
81 Routing queue number.
82 Only change this if you already use the default queue number somewhere else.
83 '';
84 };
85 configureFirewall = lib.mkOption {
86 default = true;
87 type = lib.types.bool;
88 description = ''
89 Whether to setup firewall routing so that system http(s) traffic is forwarded via this service.
90 Disable if you want to set it up manually.
91 '';
92 };
93 httpSupport = lib.mkOption {
94 default = true;
95 type = lib.types.bool;
96 description = ''
97 Whether to route http traffic on port 80.
98 Http bypass rarely works and you might want to disable it if you don't utilise http connections.
99 '';
100 };
101 httpMode = lib.mkOption {
102 default = "first";
103 type = lib.types.enum [
104 "first"
105 "full"
106 ];
107 example = "full";
108 description = ''
109 By default this service only changes the first packet sent, which is enough in most cases.
110 But there are DPIs that monitor the whole traffic within a session.
111 That requires full processing of every packet, which increases the CPU usage.
112
113 Set the mode to `full` if http doesn't work.
114 '';
115 };
116 udpSupport = lib.mkOption {
117 default = false;
118 type = lib.types.bool;
119 description = ''
120 Enable UDP routing.
121 This requires you to specify `udpPorts` and `--dpi-desync-any-protocol` parameter.
122 '';
123 };
124 udpPorts = lib.mkOption {
125 default = [ ];
126 type = with lib.types; listOf str;
127 example = ''
128 [
129 "50000:50099"
130 "1234"
131 ]
132 '';
133 description = ''
134 List of UDP ports to route.
135 Port ranges are delimited with a colon like this "50000:50099".
136 '';
137 };
138 };
139
140 config = lib.mkIf cfg.enable (
141 lib.mkMerge [
142 {
143 assertions = [
144 {
145 assertion = (builtins.length cfg.whitelist) == 0 || (builtins.length cfg.blacklist) == 0;
146 message = "Can't specify both whitelist and blacklist.";
147 }
148 {
149 assertion = (builtins.length cfg.params) != 0;
150 message = "You have to specify zapret parameters. See the params option's description.";
151 }
152 {
153 assertion = cfg.udpSupport -> (builtins.length cfg.udpPorts) != 0;
154 message = "You have to specify UDP ports or disable UDP support.";
155 }
156 {
157 assertion = !cfg.configureFirewall || !config.networking.nftables.enable;
158 message = "You need to manually configure you firewall for Zapret service when using nftables.";
159 }
160 ];
161
162 systemd.services.zapret = {
163 description = "DPI bypass service";
164 wantedBy = [ "multi-user.target" ];
165 after = [ "network.target" ];
166 serviceConfig = {
167 ExecStart = "${cfg.package}/bin/nfqws --pidfile=/run/nfqws.pid ${params} ${whitelist} ${blacklist} --qnum=${qnum}";
168 Type = "simple";
169 PIDFile = "/run/nfqws.pid";
170 Restart = "always";
171 RuntimeMaxSec = "1h"; # This service loves to crash silently or cause network slowdowns. It also restarts instantly. Restarting it at least hourly provided the best experience.
172
173 # Hardening.
174 DevicePolicy = "closed";
175 KeyringMode = "private";
176 PrivateTmp = true;
177 PrivateMounts = true;
178 ProtectHome = true;
179 ProtectHostname = true;
180 ProtectKernelModules = true;
181 ProtectKernelTunables = true;
182 ProtectSystem = "strict";
183 ProtectProc = "invisible";
184 RemoveIPC = true;
185 RestrictNamespaces = true;
186 RestrictRealtime = true;
187 RestrictSUIDSGID = true;
188 SystemCallArchitectures = "native";
189 };
190 };
191 }
192
193 # Route system traffic via service for specified ports.
194 (lib.mkIf cfg.configureFirewall {
195 networking.firewall.extraCommands =
196 let
197 httpParams = lib.optionalString (
198 cfg.httpMode == "first"
199 ) "-m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6";
200
201 udpPorts = lib.concatStringsSep "," cfg.udpPorts;
202 in
203 ''
204 ip46tables -t mangle -I POSTROUTING -p tcp --dport 443 -m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num ${qnum} --queue-bypass
205 ''
206 + lib.optionalString (cfg.httpSupport) ''
207 ip46tables -t mangle -I POSTROUTING -p tcp --dport 80 ${httpParams} -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num ${qnum} --queue-bypass
208 ''
209 + lib.optionalString (cfg.udpSupport) ''
210 ip46tables -t mangle -A POSTROUTING -p udp -m multiport --dports ${udpPorts} -m mark ! --mark 0x40000000/0x40000000 -j NFQUEUE --queue-num ${qnum} --queue-bypass
211 '';
212 })
213 ]
214 );
215
216 meta.maintainers = with lib.maintainers; [
217 nishimara
218 ];
219}