{ config, lib, pkgs, ... }: let cfg = config.local.services.byedpi; exe = lib.getExe cfg.package; baseArgs = lib.cli.toGNUCommandLineShell { } { inherit (cfg.settings) ip port; buf-size = cfg.settings.bufferSize; debug = cfg.settings.debugLevel; max-conn = cfg.settings.connectionLimit; tfo = cfg.settings.tcpFastOpen.enable; no-udp = !cfg.settings.udp.enable; no-domain = !cfg.settings.domain.enable; }; groupArgs = lib.flip map cfg.groupSettings (gs: lib.concatStringsSep " " [ (lib.cli.toGNUCommandLineShell { } ( if gs.proto == [ ] && gs.hostsFile == null then { auto = gs.name; } else { proto = lib.optionalDrvAttr (gs.proto != [ ]) (lib.concatStringsSep "," gs.proto); hosts = lib.optionalDrvAttr (gs.hostsFile != null) gs.hostsFile; } )) (lib.cli.toGNUCommandLineShell { } { inherit (gs) ttl split disorder oob disoob fake tlsrec md5sig; }) ] ); cliArgs = lib.concatStringsSep " " ([ baseArgs ] ++ groupArgs); mkSplitOption = let splitType = with lib.types; let strOrInt = either str int; in nullOr (either strOrInt (listOf strOrInt)); in description: lib.mkOption { inherit description; type = splitType; default = null; }; in { options.local.services.byedpi = with lib; { enable = mkEnableOption "byedpi"; package = mkPackageOption pkgs "byedpi" { }; openFirewall = mkEnableOption "Whether to open the required firewall ports in the firewall."; enableProxy = mkEnableOption "Whether to enable systemwide networking proxy"; settings = { ip = mkOption { type = types.str; description = "Listening IP"; default = "0.0.0.0"; }; port = mkOption { type = types.ints.u16; description = "Listening port"; default = 1080; }; bufferSize = mkOption { type = types.int; description = "Buffer size"; default = 16384; }; debugLevel = mkOption { type = types.ints.between 0 2; default = 0; }; connectionLimit = mkOption { type = types.int; description = "Connection count limit"; default = 512; }; domain.enable = mkEnableOption "Enable domain resolving" // { default = true; }; udp.enable = mkEnableOption "Enable UDP association" // { default = true; }; tcpFastOpen.enable = mkEnableOption "Enable TCP Fast Open"; }; groupSettings = lib.mkOption { type = types.listOf (types.submodule ({ config, ... }: { options = { enable = mkEnableOption "Enable configs for hosts"; name = mkOption { type = types.str; }; hostsFile = mkOption { type = types.nullOr types.package; internal = true; readOnly = true; }; hosts = mkOption { type = types.lines; default = ""; }; proto = mkOption { type = types.listOf (types.enum [ "tls" "http" "udp" "ipv4" ]); default = [ ]; }; ttl = mkOption { type = types.int; default = 8; }; split = mkSplitOption "Split packet at n"; disorder = mkSplitOption "Split and send reverse order"; oob = mkSplitOption "Split and send as OOB data"; disoob = mkSplitOption "Split and send reverse order as OOB data"; fake = mkSplitOption "Split and send fake packet"; tlsrec = mkSplitOption "Make TLS record at position"; md5sig = mkEnableOption "Add MD5 Signature option for fake packets"; }; config = { hostsFile = if config.hosts == "" then null else pkgs.writeText config.name config.hosts; }; })); }; }; config = lib.mkIf cfg.enable { users.groups.byedpi = { }; users.users.byedpi = { isSystemUser = true; group = "byedpi"; }; networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.settings.port ]; networking.proxy = lib.mkIf cfg.enableProxy rec { allProxy = "http://${cfg.settings.ip}:${toString cfg.settings.port}"; httpProxy = allProxy; httpsProxy = allProxy; }; systemd.services.byedpi = { description = "Byedpi (Bypass DPI)"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; path = [ cfg.package ]; serviceConfig = { Type = "simple"; User = "byedpi"; Group = "byedpi"; ExecStart = "${exe} ${cliArgs}"; }; }; }; }