{ config, lib, pkgs, ... }: with lib; let cfg = config.services.tayga; # Converts an address set to a string strAddr = addr: "${addr.address}/${toString addr.prefixLength}"; configFile = pkgs.writeText "tayga.conf" '' tun-device ${cfg.tunDevice} ipv4-addr ${cfg.ipv4.address} ${optionalString (cfg.ipv6.address != null) "ipv6-addr ${cfg.ipv6.address}"} prefix ${strAddr cfg.ipv6.pool} dynamic-pool ${strAddr cfg.ipv4.pool} data-dir ${cfg.dataDir} ${concatStringsSep "\n" (mapAttrsToList (ipv4: ipv6: "map " + ipv4 + " " + ipv6) cfg.mappings)} ''; addrOpts = v: assert v == 4 || v == 6; { options = { address = mkOption { type = types.str; description = "IPv${toString v} address."; }; prefixLength = mkOption { type = types.addCheck types.int (n: n >= 0 && n <= (if v == 4 then 32 else 128)); description = '' Subnet mask of the interface, specified as the number of bits in the prefix ("${if v == 4 then "24" else "64"}"). ''; }; }; }; versionOpts = v: { options = { router = { address = mkOption { type = types.str; description = "The IPv${toString v} address of the router."; }; }; address = mkOption { type = types.nullOr types.str; default = null; description = "The source IPv${toString v} address of the TAYGA server."; }; pool = mkOption { type = with types; nullOr (submodule (addrOpts v)); description = "The pool of IPv${toString v} addresses which are used for translation."; }; }; }; in { options = { services.tayga = { enable = mkEnableOption "Tayga"; package = mkPackageOption pkgs "tayga" { }; ipv4 = mkOption { type = types.submodule (versionOpts 4); description = "IPv4-specific configuration."; example = literalExpression '' { address = "192.0.2.0"; router = { address = "192.0.2.1"; }; pool = { address = "192.0.2.1"; prefixLength = 24; }; } ''; }; ipv6 = mkOption { type = types.submodule (versionOpts 6); description = "IPv6-specific configuration."; example = literalExpression '' { address = "2001:db8::1"; router = { address = "64:ff9b::1"; }; pool = { address = "64:ff9b::"; prefixLength = 96; }; } ''; }; dataDir = mkOption { type = types.path; default = "/var/lib/tayga"; description = "Directory for persistent data."; }; tunDevice = mkOption { type = types.str; default = "nat64"; description = "Name of the nat64 tun device."; }; mappings = mkOption { type = types.attrsOf types.str; default = {}; description = "Static IPv4 -> IPv6 host mappings."; example = literalExpression '' { "192.168.5.42" = "2001:db8:1:4444::1"; "192.168.5.43" = "2001:db8:1:4444::2"; "192.168.255.2" = "2001:db8:1:569::143"; } ''; }; }; }; config = mkIf cfg.enable { assertions = [ { assertion = allUnique (attrValues cfg.mappings); message = "Neither the IPv4 nor the IPv6 addresses must be entered twice in the mappings."; } ]; networking.interfaces."${cfg.tunDevice}" = { virtual = true; virtualType = "tun"; virtualOwner = mkIf config.networking.useNetworkd ""; ipv4 = { addresses = [ { address = cfg.ipv4.router.address; prefixLength = 32; } ]; routes = [ cfg.ipv4.pool ]; }; ipv6 = { addresses = [ { address = cfg.ipv6.router.address; prefixLength = 128; } ]; routes = [ cfg.ipv6.pool ]; }; }; systemd.services.tayga = { description = "Stateless NAT64 implementation"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; serviceConfig = { ExecStart = "${cfg.package}/bin/tayga -d --nodetach --config ${configFile}"; ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID"; Restart = "always"; # Hardening Score: # - nixos-scripts: 2.1 # - systemd-networkd: 1.6 ProtectHome = true; SystemCallFilter = [ "@network-io" "@system-service" "~@privileged" "~@resources" ]; ProtectKernelLogs = true; AmbientCapabilities = [ "CAP_NET_ADMIN" ]; CapabilityBoundingSet = ""; RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_NETLINK" ]; StateDirectory = "tayga"; DynamicUser = mkIf config.networking.useNetworkd true; MemoryDenyWriteExecute = true; RestrictRealtime = true; RestrictSUIDSGID = true; ProtectHostname = true; ProtectKernelModules = true; ProtectKernelTunables = true; RestrictNamespaces = true; NoNewPrivileges = true; ProtectControlGroups = true; SystemCallArchitectures = "native"; PrivateTmp = true; LockPersonality = true; ProtectSystem = true; PrivateUsers = true; ProtectProc = "invisible"; }; }; }; }