python312Packages.aiohomeconnect: 0.10.0 -> 0.11.0 (#374011)
[NixPkgs.git] / nixos / modules / services / networking / opengfw.nix
blob1866e75487dd3adf4d9de8e90afa7c7c7dffb769
2   lib,
3   pkgs,
4   config,
5   ...
6 }:
7 let
8   inherit (lib)
9     mkOption
10     types
11     mkIf
12     optionalString
13     ;
14   cfg = config.services.opengfw;
17   options.services.opengfw = {
18     enable = lib.mkEnableOption ''
19       OpenGFW, A flexible, easy-to-use, open source implementation of GFW on Linux
20     '';
22     package = lib.mkPackageOption pkgs "opengfw" { default = "opengfw"; };
24     user = mkOption {
25       default = "opengfw";
26       type = types.singleLineStr;
27       description = "Username of the OpenGFW user.";
28     };
30     dir = mkOption {
31       default = "/var/lib/opengfw";
32       type = types.singleLineStr;
33       description = ''
34         Working directory of the OpenGFW service and home of `opengfw.user`.
35       '';
36     };
38     logFile = mkOption {
39       default = null;
40       type = types.nullOr types.path;
41       example = "/var/lib/opengfw/opengfw.log";
42       description = ''
43         File to write the output to instead of systemd.
44       '';
45     };
47     logFormat = mkOption {
48       description = ''
49         Format of the logs. [logFormatMap](https://github.com/apernet/OpenGFW/blob/d7737e92117a11c9a6100d53019fac3b9d724fe3/cmd/root.go#L62)
50       '';
51       default = "json";
52       example = "console";
53       type = types.enum [
54         "json"
55         "console"
56       ];
57     };
59     pcapReplay = mkOption {
60       default = null;
61       example = "./opengfw.pcap";
62       type = types.nullOr types.path;
63       description = ''
64         Path to PCAP replay file.
65         In pcap mode, none of the actions in the rules have any effect.
66         This mode is mainly for debugging.
67       '';
68     };
70     logLevel = mkOption {
71       description = ''
72         Level of the logs. [logLevelMap](https://github.com/apernet/OpenGFW/blob/d7737e92117a11c9a6100d53019fac3b9d724fe3/cmd/root.go#L55)
73       '';
74       default = "info";
75       example = "warn";
76       type = types.enum [
77         "debug"
78         "info"
79         "warn"
80         "error"
81       ];
82     };
84     rulesFile = mkOption {
85       default = null;
86       type = types.nullOr types.path;
87       description = ''
88         Path to file containing OpenGFW rules.
89       '';
90     };
92     settingsFile = mkOption {
93       default = null;
94       type = types.nullOr types.path;
95       description = ''
96         Path to file containing OpenGFW settings.
97       '';
98     };
100     settings = mkOption {
101       default = null;
102       description = ''
103         Settings passed to OpenGFW. [Example config](https://gfw.dev/docs/build-run/#config-example)
104       '';
105       type = types.nullOr (
106         types.submodule {
107           options = {
108             replay = mkOption {
109               description = ''
110                 PCAP replay settings.
111               '';
112               default = { };
113               type = types.submodule {
114                 options = {
115                   realtime = mkOption {
116                     description = ''
117                       Whether the packets in the PCAP file should be replayed in "real time" (instead of as fast as possible).
118                     '';
119                     default = false;
120                     example = true;
121                     type = types.bool;
122                   };
123                 };
124               };
125             };
127             io = mkOption {
128               description = ''
129                 IO settings.
130               '';
131               default = { };
132               type = types.submodule {
133                 options = {
134                   queueSize = mkOption {
135                     description = "IO queue size.";
136                     type = types.int;
137                     default = 1024;
138                     example = 2048;
139                   };
140                   local = mkOption {
141                     description = ''
142                       Set to false if you want to run OpenGFW on FORWARD chain. (e.g. on a router)
143                     '';
144                     type = types.bool;
145                     default = true;
146                     example = false;
147                   };
148                   rst = mkOption {
149                     description = ''
150                       Set to true if you want to send RST for blocked TCP connections, needs `local = false`.
151                     '';
152                     type = types.bool;
153                     default = !cfg.settings.io.local;
154                     defaultText = "`!config.services.opengfw.settings.io.local`";
155                     example = false;
156                   };
157                   rcvBuf = mkOption {
158                     description = "Netlink receive buffer size.";
159                     type = types.int;
160                     default = 4194304;
161                     example = 2097152;
162                   };
163                   sndBuf = mkOption {
164                     description = "Netlink send buffer size.";
165                     type = types.int;
166                     default = 4194304;
167                     example = 2097152;
168                   };
169                 };
170               };
171             };
172             ruleset = mkOption {
173               description = ''
174                 The path to load specific local geoip/geosite db files.
175                 If not set, they will be automatically downloaded from (Loyalsoldier/v2ray-rules-dat)[https://github.com/Loyalsoldier/v2ray-rules-dat].
176               '';
177               default = { };
178               type = types.submodule {
179                 options = {
180                   geoip = mkOption {
181                     description = "Path to `geoip.dat`.";
182                     default = null;
183                     type = types.nullOr types.path;
184                   };
185                   geosite = mkOption {
186                     description = "Path to `geosite.dat`.";
187                     default = null;
188                     type = types.nullOr types.path;
189                   };
190                 };
191               };
192             };
193             workers = mkOption {
194               default = { };
195               description = "Worker settings.";
196               type = types.submodule {
197                 options = {
198                   count = mkOption {
199                     type = types.int;
200                     description = ''
201                       Number of workers.
202                       Recommended to be no more than the number of CPU cores
203                     '';
204                     default = 4;
205                     example = 8;
206                   };
207                   queueSize = mkOption {
208                     type = types.int;
209                     description = "Worker queue size.";
210                     default = 16;
211                     example = 32;
212                   };
213                   tcpMaxBufferedPagesTotal = mkOption {
214                     type = types.int;
215                     description = ''
216                       TCP max total buffered pages.
217                     '';
218                     default = 4096;
219                     example = 8192;
220                   };
221                   tcpMaxBufferedPagesPerConn = mkOption {
222                     type = types.int;
223                     description = ''
224                       TCP max total bufferd pages per connection.
225                     '';
226                     default = 64;
227                     example = 128;
228                   };
229                   tcpTimeout = mkOption {
230                     type = types.str;
231                     description = ''
232                       How long a connection is considered dead when no data is being transferred.
233                       Dead connections are purged from TCP reassembly pools once per minute.
234                     '';
235                     default = "10m";
236                     example = "5m";
237                   };
238                   udpMaxStreams = mkOption {
239                     type = types.int;
240                     description = "UDP max streams.";
241                     default = 4096;
242                     example = 8192;
243                   };
244                 };
245               };
246             };
247           };
248         }
249       );
250     };
252     rules = mkOption {
253       default = [ ];
254       description = ''
255         Rules passed to OpenGFW. [Example rules](https://gfw.dev/docs/rules)
256       '';
257       type = types.listOf (
258         types.submodule {
259           options = {
260             name = mkOption {
261               description = "Name of the rule.";
262               example = "block google dns";
263               type = types.singleLineStr;
264             };
266             action = mkOption {
267               description = ''
268                 Action of the rule. [Supported actions](https://gfw.dev/docs/rules#supported-actions)
269               '';
270               default = "allow";
271               example = "block";
272               type = types.enum [
273                 "allow"
274                 "block"
275                 "drop"
276                 "modify"
277               ];
278             };
280             log = mkOption {
281               description = "Whether to enable logging for the rule.";
282               default = true;
283               example = false;
284               type = types.bool;
285             };
287             expr = mkOption {
288               description = ''
289                 [Expr Language](https://expr-lang.org/docs/language-definition) expression using [analyzers](https://gfw.dev/docs/analyzers) and [functions](https://gfw.dev/docs/functions).
290               '';
291               type = types.str;
292               example = ''dns != nil && dns.qr && any(dns.questions, {.name endsWith "google.com"})'';
293             };
295             modifier = mkOption {
296               default = null;
297               description = ''
298                 Modification of specified packets when using the `modify` action. [Available modifiers](https://github.com/apernet/OpenGFW/tree/master/modifier)
299               '';
300               type = types.nullOr (
301                 types.submodule {
302                   options = {
303                     name = mkOption {
304                       description = "Name of the modifier.";
305                       type = types.singleLineStr;
306                       example = "dns";
307                     };
309                     args = mkOption {
310                       description = "Arguments passed to the modifier.";
311                       type = types.attrs;
312                       example = {
313                         a = "0.0.0.0";
314                         aaaa = "::";
315                       };
316                     };
317                   };
318                 }
319               );
320             };
321           };
322         }
323       );
325       example = [
326         {
327           name = "block v2ex http";
328           action = "block";
329           expr = ''string(http?.req?.headers?.host) endsWith "v2ex.com"'';
330         }
331         {
332           name = "block google socks";
333           action = "block";
334           expr = ''string(socks?.req?.addr) endsWith "google.com" && socks?.req?.port == 80'';
335         }
336         {
337           name = "v2ex dns poisoning";
338           action = "modify";
339           modifier = {
340             name = "dns";
341             args = {
342               a = "0.0.0.0";
343               aaaa = "::";
344             };
345           };
346           expr = ''dns != nil && dns.qr && any(dns.questions, {.name endsWith "v2ex.com"})'';
347         }
348       ];
349     };
350   };
352   config =
353     let
354       format = pkgs.formats.yaml { };
356       settings =
357         if cfg.settings != null then
358           format.generate "opengfw-config.yaml" cfg.settings
359         else
360           cfg.settingsFile;
361       rules = if cfg.rules != [ ] then format.generate "opengfw-rules.yaml" cfg.rules else cfg.rulesFile;
362     in
363     mkIf cfg.enable {
364       security.wrappers.OpenGFW = {
365         owner = cfg.user;
366         group = cfg.user;
367         capabilities = "cap_net_admin+ep";
368         source = "${cfg.package}/bin/OpenGFW";
369       };
371       systemd.services.opengfw = {
372         description = "OpenGFW";
373         wantedBy = [ "multi-user.target" ];
374         after = [ "network.target" ];
375         path = with pkgs; [ iptables ];
377         preStart = ''
378           ${optionalString (rules != null) "ln -sf ${rules} rules.yaml"}
379           ${optionalString (settings != null) "ln -sf ${settings} config.yaml"}
380         '';
382         script = ''
383           ${config.security.wrapperDir}/OpenGFW \
384             -f ${cfg.logFormat} \
385             -l ${cfg.logLevel} \
386             ${optionalString (cfg.pcapReplay != null) "-p ${cfg.pcapReplay}"} \
387             -c config.yaml \
388             rules.yaml
389         '';
391         serviceConfig = rec {
392           WorkingDirectory = cfg.dir;
393           ExecReload = "kill -HUP $MAINPID";
394           Restart = "always";
395           User = cfg.user;
396           StandardOutput = mkIf (cfg.logFile != null) "append:${cfg.logFile}";
397           StandardError = StandardOutput;
398         };
399       };
401       users = {
402         groups.${cfg.user} = { };
403         users.${cfg.user} = {
404           description = "opengfw user";
405           isSystemUser = true;
406           group = cfg.user;
407           home = cfg.dir;
408           createHome = true;
409           homeMode = "750";
410         };
411       };
412     };
413   meta.maintainers = with lib.maintainers; [ eum3l ];