diff --git a/maintainers/maintainer-list.nix b/maintainers/maintainer-list.nix index af21a01d2d925..3369f1aaf043e 100644 --- a/maintainers/maintainer-list.nix +++ b/maintainers/maintainer-list.nix @@ -6245,6 +6245,12 @@ githubId = 2147649; name = "Euan Kemp"; }; + eum3l = { + email = "eum3l@proton.me"; + githubId = 77971322; + github = "eum3l"; + name = "Emil"; + }; eureka-cpu = { email = "github.eureka@gmail.com"; github = "eureka-cpu"; diff --git a/nixos/doc/manual/release-notes/rl-2411.section.md b/nixos/doc/manual/release-notes/rl-2411.section.md index 1c3865b5f8340..5159ccc609e0e 100644 --- a/nixos/doc/manual/release-notes/rl-2411.section.md +++ b/nixos/doc/manual/release-notes/rl-2411.section.md @@ -58,6 +58,8 @@ - [Improved File Manager](https://github.com/misterunknown/ifm), or IFM, a single-file web-based file manager. +- [OpenGFW](https://github.com/apernet/OpenGFW), an implementation of the Great Firewall on Linux. Available as [services.opengfw](#opt-services.opengfw.enable). + ## Backward Incompatibilities {#sec-release-24.11-incompatibilities} - `transmission` package has been aliased with a `trace` warning to `transmission_3`. Since [Transmission 4 has been released last year](https://github.com/transmission/transmission/releases/tag/4.0.0), and Transmission 3 will eventually go away, it was decided perform this warning alias to make people aware of the new version. The `services.transmission.package` defaults to `transmission_3` as well because the upgrade can cause data loss in certain specific usage patterns (examples: [#5153](https://github.com/transmission/transmission/issues/5153), [#6796](https://github.com/transmission/transmission/issues/6796)). Please make sure to back up to your data directory per your usage: diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 30a11980b7b68..d444b83dd412f 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1137,6 +1137,7 @@ ./services/networking/oink.nix ./services/networking/onedrive.nix ./services/networking/openconnect.nix + ./services/networking/opengfw.nix ./services/networking/openvpn.nix ./services/networking/ostinato.nix ./services/networking/owamp.nix diff --git a/nixos/modules/services/networking/opengfw.nix b/nixos/modules/services/networking/opengfw.nix new file mode 100644 index 0000000000000..1866e75487dd3 --- /dev/null +++ b/nixos/modules/services/networking/opengfw.nix @@ -0,0 +1,414 @@ +{ + lib, + pkgs, + config, + ... +}: +let + inherit (lib) + mkOption + types + mkIf + optionalString + ; + cfg = config.services.opengfw; +in +{ + options.services.opengfw = { + enable = lib.mkEnableOption '' + OpenGFW, A flexible, easy-to-use, open source implementation of GFW on Linux + ''; + + package = lib.mkPackageOption pkgs "opengfw" { default = "opengfw"; }; + + user = mkOption { + default = "opengfw"; + type = types.singleLineStr; + description = "Username of the OpenGFW user."; + }; + + dir = mkOption { + default = "/var/lib/opengfw"; + type = types.singleLineStr; + description = '' + Working directory of the OpenGFW service and home of `opengfw.user`. + ''; + }; + + logFile = mkOption { + default = null; + type = types.nullOr types.path; + example = "/var/lib/opengfw/opengfw.log"; + description = '' + File to write the output to instead of systemd. + ''; + }; + + logFormat = mkOption { + description = '' + Format of the logs. [logFormatMap](https://github.com/apernet/OpenGFW/blob/d7737e92117a11c9a6100d53019fac3b9d724fe3/cmd/root.go#L62) + ''; + default = "json"; + example = "console"; + type = types.enum [ + "json" + "console" + ]; + }; + + pcapReplay = mkOption { + default = null; + example = "./opengfw.pcap"; + type = types.nullOr types.path; + description = '' + Path to PCAP replay file. + In pcap mode, none of the actions in the rules have any effect. + This mode is mainly for debugging. + ''; + }; + + logLevel = mkOption { + description = '' + Level of the logs. [logLevelMap](https://github.com/apernet/OpenGFW/blob/d7737e92117a11c9a6100d53019fac3b9d724fe3/cmd/root.go#L55) + ''; + default = "info"; + example = "warn"; + type = types.enum [ + "debug" + "info" + "warn" + "error" + ]; + }; + + rulesFile = mkOption { + default = null; + type = types.nullOr types.path; + description = '' + Path to file containing OpenGFW rules. + ''; + }; + + settingsFile = mkOption { + default = null; + type = types.nullOr types.path; + description = '' + Path to file containing OpenGFW settings. + ''; + }; + + settings = mkOption { + default = null; + description = '' + Settings passed to OpenGFW. [Example config](https://gfw.dev/docs/build-run/#config-example) + ''; + type = types.nullOr ( + types.submodule { + options = { + replay = mkOption { + description = '' + PCAP replay settings. + ''; + default = { }; + type = types.submodule { + options = { + realtime = mkOption { + description = '' + Whether the packets in the PCAP file should be replayed in "real time" (instead of as fast as possible). + ''; + default = false; + example = true; + type = types.bool; + }; + }; + }; + }; + + io = mkOption { + description = '' + IO settings. + ''; + default = { }; + type = types.submodule { + options = { + queueSize = mkOption { + description = "IO queue size."; + type = types.int; + default = 1024; + example = 2048; + }; + local = mkOption { + description = '' + Set to false if you want to run OpenGFW on FORWARD chain. (e.g. on a router) + ''; + type = types.bool; + default = true; + example = false; + }; + rst = mkOption { + description = '' + Set to true if you want to send RST for blocked TCP connections, needs `local = false`. + ''; + type = types.bool; + default = !cfg.settings.io.local; + defaultText = "`!config.services.opengfw.settings.io.local`"; + example = false; + }; + rcvBuf = mkOption { + description = "Netlink receive buffer size."; + type = types.int; + default = 4194304; + example = 2097152; + }; + sndBuf = mkOption { + description = "Netlink send buffer size."; + type = types.int; + default = 4194304; + example = 2097152; + }; + }; + }; + }; + ruleset = mkOption { + description = '' + The path to load specific local geoip/geosite db files. + If not set, they will be automatically downloaded from (Loyalsoldier/v2ray-rules-dat)[https://github.com/Loyalsoldier/v2ray-rules-dat]. + ''; + default = { }; + type = types.submodule { + options = { + geoip = mkOption { + description = "Path to `geoip.dat`."; + default = null; + type = types.nullOr types.path; + }; + geosite = mkOption { + description = "Path to `geosite.dat`."; + default = null; + type = types.nullOr types.path; + }; + }; + }; + }; + workers = mkOption { + default = { }; + description = "Worker settings."; + type = types.submodule { + options = { + count = mkOption { + type = types.int; + description = '' + Number of workers. + Recommended to be no more than the number of CPU cores + ''; + default = 4; + example = 8; + }; + queueSize = mkOption { + type = types.int; + description = "Worker queue size."; + default = 16; + example = 32; + }; + tcpMaxBufferedPagesTotal = mkOption { + type = types.int; + description = '' + TCP max total buffered pages. + ''; + default = 4096; + example = 8192; + }; + tcpMaxBufferedPagesPerConn = mkOption { + type = types.int; + description = '' + TCP max total bufferd pages per connection. + ''; + default = 64; + example = 128; + }; + tcpTimeout = mkOption { + type = types.str; + description = '' + How long a connection is considered dead when no data is being transferred. + Dead connections are purged from TCP reassembly pools once per minute. + ''; + default = "10m"; + example = "5m"; + }; + udpMaxStreams = mkOption { + type = types.int; + description = "UDP max streams."; + default = 4096; + example = 8192; + }; + }; + }; + }; + }; + } + ); + }; + + rules = mkOption { + default = [ ]; + description = '' + Rules passed to OpenGFW. [Example rules](https://gfw.dev/docs/rules) + ''; + type = types.listOf ( + types.submodule { + options = { + name = mkOption { + description = "Name of the rule."; + example = "block google dns"; + type = types.singleLineStr; + }; + + action = mkOption { + description = '' + Action of the rule. [Supported actions](https://gfw.dev/docs/rules#supported-actions) + ''; + default = "allow"; + example = "block"; + type = types.enum [ + "allow" + "block" + "drop" + "modify" + ]; + }; + + log = mkOption { + description = "Whether to enable logging for the rule."; + default = true; + example = false; + type = types.bool; + }; + + expr = mkOption { + description = '' + [Expr Language](https://expr-lang.org/docs/language-definition) expression using [analyzers](https://gfw.dev/docs/analyzers) and [functions](https://gfw.dev/docs/functions). + ''; + type = types.str; + example = ''dns != nil && dns.qr && any(dns.questions, {.name endsWith "google.com"})''; + }; + + modifier = mkOption { + default = null; + description = '' + Modification of specified packets when using the `modify` action. [Available modifiers](https://github.com/apernet/OpenGFW/tree/master/modifier) + ''; + type = types.nullOr ( + types.submodule { + options = { + name = mkOption { + description = "Name of the modifier."; + type = types.singleLineStr; + example = "dns"; + }; + + args = mkOption { + description = "Arguments passed to the modifier."; + type = types.attrs; + example = { + a = "0.0.0.0"; + aaaa = "::"; + }; + }; + }; + } + ); + }; + }; + } + ); + + example = [ + { + name = "block v2ex http"; + action = "block"; + expr = ''string(http?.req?.headers?.host) endsWith "v2ex.com"''; + } + { + name = "block google socks"; + action = "block"; + expr = ''string(socks?.req?.addr) endsWith "google.com" && socks?.req?.port == 80''; + } + { + name = "v2ex dns poisoning"; + action = "modify"; + modifier = { + name = "dns"; + args = { + a = "0.0.0.0"; + aaaa = "::"; + }; + }; + expr = ''dns != nil && dns.qr && any(dns.questions, {.name endsWith "v2ex.com"})''; + } + ]; + }; + }; + + config = + let + format = pkgs.formats.yaml { }; + + settings = + if cfg.settings != null then + format.generate "opengfw-config.yaml" cfg.settings + else + cfg.settingsFile; + rules = if cfg.rules != [ ] then format.generate "opengfw-rules.yaml" cfg.rules else cfg.rulesFile; + in + mkIf cfg.enable { + security.wrappers.OpenGFW = { + owner = cfg.user; + group = cfg.user; + capabilities = "cap_net_admin+ep"; + source = "${cfg.package}/bin/OpenGFW"; + }; + + systemd.services.opengfw = { + description = "OpenGFW"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + path = with pkgs; [ iptables ]; + + preStart = '' + ${optionalString (rules != null) "ln -sf ${rules} rules.yaml"} + ${optionalString (settings != null) "ln -sf ${settings} config.yaml"} + ''; + + script = '' + ${config.security.wrapperDir}/OpenGFW \ + -f ${cfg.logFormat} \ + -l ${cfg.logLevel} \ + ${optionalString (cfg.pcapReplay != null) "-p ${cfg.pcapReplay}"} \ + -c config.yaml \ + rules.yaml + ''; + + serviceConfig = rec { + WorkingDirectory = cfg.dir; + ExecReload = "kill -HUP $MAINPID"; + Restart = "always"; + User = cfg.user; + StandardOutput = mkIf (cfg.logFile != null) "append:${cfg.logFile}"; + StandardError = StandardOutput; + }; + }; + + users = { + groups.${cfg.user} = { }; + users.${cfg.user} = { + description = "opengfw user"; + isSystemUser = true; + group = cfg.user; + home = cfg.dir; + createHome = true; + homeMode = "750"; + }; + }; + }; + meta.maintainers = with lib.maintainers; [ eum3l ]; +} diff --git a/pkgs/by-name/op/opengfw/package.nix b/pkgs/by-name/op/opengfw/package.nix new file mode 100644 index 0000000000000..ad1b57dcad3a4 --- /dev/null +++ b/pkgs/by-name/op/opengfw/package.nix @@ -0,0 +1,37 @@ +{ + lib, + buildGoModule, + fetchFromGitHub, +}: +let + pname = "opengfw"; + version = "0.4.0"; +in +buildGoModule { + inherit pname version; + CGO_ENABLED = 0; + vendorHash = "sha256-F8jTvgxOhOGVtl6B8u0xAIvjNwVjBtvAhApzjIgykpY="; + + src = fetchFromGitHub { + owner = "apernet"; + repo = "opengfw"; + rev = "refs/tags/v${version}"; + hash = "sha256-kmbG6l5CtZGM/zpvl2pukq5xsOIy28RDyb4sHBsoyOw="; + }; + + meta = { + mainProgram = "OpenGFW"; + description = "Flexible, easy-to-use, open source implementation of GFW on Linux"; + longDescription = '' + OpenGFW is your very own DIY Great Firewall of China, available as a flexible, + easy-to-use open source program on Linux. Why let the powers that be have all the fun? + It's time to give power to the people and democratize censorship. + Bring the thrill of cyber-sovereignty right into your home router + and start filtering like a pro - you too can play Big Brother. + ''; + homepage = "https://gfw.dev/"; + license = lib.licenses.mpl20; + platforms = lib.platforms.linux; + maintainers = with lib.maintainers; [ eum3l ]; + }; +}