add fastd module
parent
8e0dfe87e2
commit
70580fad10
@ -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;
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue