From 70580fad10d1e38cbaf7f4b87c0df42df3eec269 Mon Sep 17 00:00:00 2001 From: Ember 'n0emis' Keske Date: Sun, 14 Nov 2021 17:06:46 +0100 Subject: [PATCH] add fastd module --- default.nix | 10 +- modules/fastd.nix | 346 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 modules/fastd.nix diff --git a/default.nix b/default.nix index b41c09c..a3bb85c 100644 --- a/default.nix +++ b/default.nix @@ -8,7 +8,14 @@ in { options.ffnix = { enable = mkEnableOption "ffnix"; - batman-legacy = mkEnableOption "batman-adv-legacy"; + batman-legacy = mkOption { + default = false; + example = true; + type = types.bool; + description = '' + Use batman-adv-legacy - do not use in new communities! + ''; + }; }; config = mkIf cfg.enable { @@ -19,5 +26,6 @@ in imports = [ ./modules/batman.nix + ./modules/fastd.nix ]; } diff --git a/modules/fastd.nix b/modules/fastd.nix new file mode 100644 index 0000000..ed173ec --- /dev/null +++ b/modules/fastd.nix @@ -0,0 +1,346 @@ +# Heavily inspired by https://git.petabyte.dev/petabyteboy/freifunk-nixfiles/src/branch/master/fastd.nix + +{ config, pkgs, lib, ...}: + +with lib; + +let + cfg = config.ffnix.fastd; + + toYesNo = b: if b then "yes" else "no"; + + createDir = name: + runCommand name + { passAsFile = [ "text" ]; + # Pointless to do this on a remote machine. + preferLocalBuild = true; + allowSubstitutes = false; + } + '' + mkdir -p "$out/" + ''; + + peerOpts = { ... }: { + options = { + + name = mkOption { + type = types.str; + }; + + remotes = mkOption { + default = []; + example = [ "[2001:db8::1]:10000" ''ipv4 "fastd.example.com" port 10000'' ]; + type = with types; listOf str; + }; + + publicKey = mkOption { + default = null; + example = "8208b2f0efa580e4115ee4fcf7b1a64d85fa68dc9ef1a6072f6f4e712b542b2c"; + type = with types; nullOr str; + description = '' + Private key + ''; + }; + + float = mkOption { + default = false; + type = types.bool; + description = '' + Allows this peer to connect from other IPs + ''; + }; + }; + }; + + instanceOpts = { ... }: { + options = { + privateKeyFile = mkOption { + default = null; + example = "/private/fastd_key"; + type = with types; nullOr str; + description = '' + Path to file containing private key + ''; + }; + + privateKey = mkOption { + default = null; + example = "907f458e1c0577023845122df678281994e9642a5f5ff4126546479f5fa64e74"; + type = with types; nullOr str; + description = '' + Private key + ''; + }; + + peerLimit = mkOption { + default = 300; + example = 20; + type = types.int; + description = '' + Maximum number of peers + ''; + }; + + mode = mkOption { + default = "tap"; + type = types.enum [ "tun" "tap" ]; + description = '' + Mode of operation: tun (layer 3) or tap (layer 2) + ''; + }; + + mtu = mkOption { + default = 1406; + example = 1280; + type = types.int; + description = '' + Maximum transmission unit + ''; + }; + + methods = mkOption { + default = [ + "null+salsa2012+umac" + "salsa2012+umac" + "null" + ]; + example = [ "aes128-ctr+umac" ]; + type = with types; listOf str; + description = '' + Encrpytion and authentication ciphers offered to connecting peers + ''; + }; + + bind = mkOption { + default = [ "any:10000 default ipv4" ]; + example = [ "10.0.0.1:7777" "[fda0::1]:7777" ]; + type = with types; listOf str; + description = '' + IPs and ports to bind to + ''; + }; + + statusSocket = mkOption { + default = null; + example = "/run/fastd.sock"; + type = with types; nullOr str; + description = '' + Status socket + ''; + }; + + blacklistedKeys = mkOption { + default = null; + example = '' + 71c926485be08ac0ddc9783cec0487a9f5d4211fae634f9b7467030161b05409 + 6250851453f34ec4520dcdf3ae3aa4d0d62fad0c6f573d5e7a78b0a8359dc6ea + ''; + type = with types; nullOr lines; + description = '' + If specified, incoming connections will be accepted, unless their public key is in the blacklist. + Otherwise, unknown peers will be rejected. + Mutually exclusive with verifyScript. + ''; + }; + + verifyScript = mkOption { + default = null; + example = '' + #!${pkgs.stdenv.shell} + PEER_KEY=$1 + echo peer "$PEER_KEY" joining + exit 0 + ''; + type = with types; nullOr str; + description = '' + If specified, incoming connections will be accepted, if this custom verify script returns a 0 exit code. + Otherwise, unknown peers will be rejected. + Mutually exclusive with blacklistedKeys. + ''; + }; + + peersDir = mkOption { + default = null; + example = "/path/to/fastd/peers/"; + type = with types; nullOr str; + }; + + peers = mkOption { + default = []; + example = [ + { + name = "server01"; + publicKey = "8208b2f0efa580e4115ee4fcf7b1a64d85fa68dc9ef1a6072f6f4e712b542b2c"; + remotes = [ "ipv4 example.org:10000" ]; + } + ]; + type = with types; listOf (submodule peerOpts); + }; + + logLevel = mkOption { + default = "verbose"; + type = types.enum [ "fatal" "error" "warn" "info" "verbose" "debug" "debug2" ]; + }; + + hideIPAddresses = mkOption { + default = true; + type = types.bool; + description = '' + Hide sensitive data in logs + ''; + }; + + packetMark = mkOption { + default = null; + example = "0x42"; + type = with types; nullOr str; + }; + + package = mkOption { + default = pkgs.fastd; + type = types.package; + }; + + extraConfig = mkOption { + default = ""; + type = types.lines; + }; + + }; + }; + + generateUnit = name: values: + # exactly one way to specify the private key must be set + assert (values.privateKey != null) != (values.privateKeyFile != null); + assert (values.blacklistedKeys == null) || (values.verifyScript == null); + # TODO: more asserts + let + privKey = if (values.privateKeyFile != null) then values.privateKeyFile else pkgs.writeText "fastd-key-${name}" ''secret "${values.privateKey}";''; + + dummyDir = if (values.peersDir == null && values.peers == {}) then createDir "fastd-dummy-peers-${name}" else null; + + blacklist = if (values.blacklistedKeys == null) then null else pkgs.writeText "fastd-blacklist-${name}" values.blacklistedKeys; + + verifyScript = if (values.verifyScript != null) then pkgs.writeScript "fastd-verify-${name}.sh" + else if (values.blacklistedKeys != null) then pkgs.writeScript "fastd-verify-${name}.sh" '' + #!${pkgs.stdenv.shell} + PEER_KEY=$1 + echo peer "$PEER_KEY" joining + if /bin/grep -Fq $PEER_KEY ${blacklist}; then + exit 1 + else + exit 0 + fi + '' else null; + + cfgFile = pkgs.writeText "fastd-cfg-${name}" '' + interface "${name}"; + mode ${values.mode}; + mtu ${toString values.mtu}; + peer limit ${toString values.peerLimit}; + log to syslog level ${values.logLevel}; + hide ip addresses ${toYesNo values.hideIPAddresses}; + include "${privKey}"; + + ${optionalString (values.statusSocket != null) '' + status socket "${values.statusSocket}"; + ''} + + ${concatMapStringsSep "\n" (bind: '' + bind ${bind}; + '') values.bind} + + ${concatMapStringsSep "\n" (method: '' + method "${method}"; + '') values.methods} + + ${optionalString (values.peersDir != null) '' + include peers from "${values.peersDir}"; + ''} + + ${optionalString (values.peers != {}) concatMapStringsSep "\n" (peer: '' + peer "${peer.name}" { + key "${peer.publicKey}"; + float ${toYesNo peer.float}; + ${concatMapStringsSep "\n" (remote: '' + remote ${remote}; + '') peer.remotes} + } + '') values.peers} + + ${optionalString (values.peersDir == null && values.peers == {}) '' + include peers from "${dummyDir}"; + ''} + + ${optionalString (verifyScript != null) '' + on verify "${verifyScript} $PEER_KEY"; + ''} + + ${optionalString (values.packetMark != null) '' + packet mark ${values.packetMark}; + ''} + + ${values.extraConfig} + ''; + + in nameValuePair "fastd-${name}" { + description = "fastd instance - ${name}"; + requires = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "notify"; + ExecStart = "${values.package}/bin/fastd -c ${cfgFile}"; + ExecReload = "/bin/kill -HUP $MAINPID"; + Restart = "always"; + RestartSec = "10s"; + User = cfg.user; + Group = "nogroup"; + AmbientCapabilities = "CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_NET_RAW"; + }; + unitConfig = { + StartLimitInterval = "1min"; + }; + }; +in { + + options = { + ffnix.fastd = { + instances = mkOption { + description = "fastd instances"; + default = {}; + example = { + my-fastd = { + privateKey = "907f458e1c0577023845122df678281994e9642a5f5ff4126546479f5fa64e74"; + peers = [ + { + name = "server01"; + publicKey = "8208b2f0efa580e4115ee4fcf7b1a64d85fa68dc9ef1a6072f6f4e712b542b2c"; + remotes = [ "ipv4 example.org:10000" ]; + } + ]; + }; + }; + type = with types; attrsOf (submodule instanceOpts); + }; + + user = mkOption { + default = "fastd"; + type = types.str; + }; + + }; + }; + + config = mkIf (cfg.instances != {}) { + users.users = optionalAttrs (cfg.user == "fastd") ({ + fastd = { + name = "fastd"; + group = "nogroup"; + isSystemUser = true; + }; + } + ); + + systemd.services = mapAttrs' generateUnit cfg.instances; + }; +}