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
22 package = lib.mkPackageOption pkgs "opengfw" { default = "opengfw"; };
26 type = types.singleLineStr;
27 description = "Username of the OpenGFW user.";
31 default = "/var/lib/opengfw";
32 type = types.singleLineStr;
34 Working directory of the OpenGFW service and home of `opengfw.user`.
40 type = types.nullOr types.path;
41 example = "/var/lib/opengfw/opengfw.log";
43 File to write the output to instead of systemd.
47 logFormat = mkOption {
49 Format of the logs. [logFormatMap](https://github.com/apernet/OpenGFW/blob/d7737e92117a11c9a6100d53019fac3b9d724fe3/cmd/root.go#L62)
59 pcapReplay = mkOption {
61 example = "./opengfw.pcap";
62 type = types.nullOr types.path;
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.
72 Level of the logs. [logLevelMap](https://github.com/apernet/OpenGFW/blob/d7737e92117a11c9a6100d53019fac3b9d724fe3/cmd/root.go#L55)
84 rulesFile = mkOption {
86 type = types.nullOr types.path;
88 Path to file containing OpenGFW rules.
92 settingsFile = mkOption {
94 type = types.nullOr types.path;
96 Path to file containing OpenGFW settings.
100 settings = mkOption {
103 Settings passed to OpenGFW. [Example config](https://gfw.dev/docs/build-run/#config-example)
105 type = types.nullOr (
110 PCAP replay settings.
113 type = types.submodule {
115 realtime = mkOption {
117 Whether the packets in the PCAP file should be replayed in "real time" (instead of as fast as possible).
132 type = types.submodule {
134 queueSize = mkOption {
135 description = "IO queue size.";
142 Set to false if you want to run OpenGFW on FORWARD chain. (e.g. on a router)
150 Set to true if you want to send RST for blocked TCP connections, needs `local = false`.
153 default = !cfg.settings.io.local;
154 defaultText = "`!config.services.opengfw.settings.io.local`";
158 description = "Netlink receive buffer size.";
164 description = "Netlink send buffer size.";
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].
178 type = types.submodule {
181 description = "Path to `geoip.dat`.";
183 type = types.nullOr types.path;
186 description = "Path to `geosite.dat`.";
188 type = types.nullOr types.path;
195 description = "Worker settings.";
196 type = types.submodule {
202 Recommended to be no more than the number of CPU cores
207 queueSize = mkOption {
209 description = "Worker queue size.";
213 tcpMaxBufferedPagesTotal = mkOption {
216 TCP max total buffered pages.
221 tcpMaxBufferedPagesPerConn = mkOption {
224 TCP max total bufferd pages per connection.
229 tcpTimeout = mkOption {
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.
238 udpMaxStreams = mkOption {
240 description = "UDP max streams.";
255 Rules passed to OpenGFW. [Example rules](https://gfw.dev/docs/rules)
257 type = types.listOf (
261 description = "Name of the rule.";
262 example = "block google dns";
263 type = types.singleLineStr;
268 Action of the rule. [Supported actions](https://gfw.dev/docs/rules#supported-actions)
281 description = "Whether to enable logging for the rule.";
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).
292 example = ''dns != nil && dns.qr && any(dns.questions, {.name endsWith "google.com"})'';
295 modifier = mkOption {
298 Modification of specified packets when using the `modify` action. [Available modifiers](https://github.com/apernet/OpenGFW/tree/master/modifier)
300 type = types.nullOr (
304 description = "Name of the modifier.";
305 type = types.singleLineStr;
310 description = "Arguments passed to the modifier.";
327 name = "block v2ex http";
329 expr = ''string(http?.req?.headers?.host) endsWith "v2ex.com"'';
332 name = "block google socks";
334 expr = ''string(socks?.req?.addr) endsWith "google.com" && socks?.req?.port == 80'';
337 name = "v2ex dns poisoning";
346 expr = ''dns != nil && dns.qr && any(dns.questions, {.name endsWith "v2ex.com"})'';
354 format = pkgs.formats.yaml { };
357 if cfg.settings != null then
358 format.generate "opengfw-config.yaml" cfg.settings
361 rules = if cfg.rules != [ ] then format.generate "opengfw-rules.yaml" cfg.rules else cfg.rulesFile;
364 security.wrappers.OpenGFW = {
367 capabilities = "cap_net_admin+ep";
368 source = "${cfg.package}/bin/OpenGFW";
371 systemd.services.opengfw = {
372 description = "OpenGFW";
373 wantedBy = [ "multi-user.target" ];
374 after = [ "network.target" ];
375 path = with pkgs; [ iptables ];
378 ${optionalString (rules != null) "ln -sf ${rules} rules.yaml"}
379 ${optionalString (settings != null) "ln -sf ${settings} config.yaml"}
383 ${config.security.wrapperDir}/OpenGFW \
384 -f ${cfg.logFormat} \
386 ${optionalString (cfg.pcapReplay != null) "-p ${cfg.pcapReplay}"} \
391 serviceConfig = rec {
392 WorkingDirectory = cfg.dir;
393 ExecReload = "kill -HUP $MAINPID";
396 StandardOutput = mkIf (cfg.logFile != null) "append:${cfg.logFile}";
397 StandardError = StandardOutput;
402 groups.${cfg.user} = { };
403 users.${cfg.user} = {
404 description = "opengfw user";
413 meta.maintainers = with lib.maintainers; [ eum3l ];