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