{ config, hostName, lib, pkgs, ... }: with lib; let listenPort = 51820; udp2rawPort = 51821; wgSubnet = "2a03:4000:52:ada:1"; wgSubnetLength = 80; wgHostLength = wgSubnetLength + 16; batSubnet = "2a03:4000:52:ada:2"; batSubnetLength = 80; batHostLength = batSubnetLength + 16; links = mkLinks [ { from = "vidhar"; to = "surtr"; endpointHost = "202.61.241.61"; udp2raw = true; PersistentKeepalive = 25; } { from = "sif"; to = "surtr"; endpointHost = "202.61.241.61"; udp2raw = true; PersistentKeepalive = 25; } { from = "sif"; to = "vidhar"; endpointHost = "192.168.2.168"; PersistentKeepalive = 25; } ]; wgHostIPs = { surtr = "${wgSubnet}::/${toString wgHostLength}"; vidhar = "${wgSubnet}:1::/${toString wgHostLength}"; sif = "${wgSubnet}:2::/${toString wgHostLength}"; }; greHostMACPrefixes = { surtr = "02:00:00:00:00"; vidhar = "02:00:00:00:01"; sif = "02:00:00:00:02"; }; batHostIPs = { surtr = ["${batSubnet}::/${toString batHostLength}"]; vidhar = ["${batSubnet}:1::/${toString batHostLength}"]; sif = ["${batSubnet}:2::/${toString batHostLength}"]; }; mkPublicKeyPath = host: ./hosts + "/${host}.pub"; mkPrivateKeyPath = host: ./hosts + "/${host}.priv"; kernel = config.boot.kernelPackages; publicKeyPath = mkPublicKeyPath hostName; privateKeyPath = mkPrivateKeyPath hostName; inNetwork = pathExists privateKeyPath && pathExists publicKeyPath; hostLinks = filter ({ from, to, ... }: from == hostName || to == hostName) links; # hostRoutes = filter ({ from, to, ... }: from == hostName || to == hostName) routes; # isRouter = inNetwork && any ({via, ...}: via == hostName) routes; linkToPeer = opts@{from, to, ...}: let other = if from == hostName then to else from; in { AllowedIPs = wgHostIPs.${other}; # ++ concatMap (rArgs: if rArgs.from != hostName || rArgs.via != to then [] else wgHostIPs.${rArgs.to}) routes; PublicKey = trim (readFile (mkPublicKeyPath other)); } // (optionalAttrs (from == hostName) (filterAttrs (n: _v: !(elem n ["from" "to" "endpointHost" "udp2raw"])) opts // optionalAttrs (opts ? "endpointHost" && from == hostName) (if opts ? "udp2raw" then { Endpoint = "127.0.0.1:${toString (udp2rawPort + opts.udp2raw)}"; } else { Endpoint = "${opts.endpointHost}:${toString listenPort}"; }))); linkToGreDev = opts@{from, to, ...}: let other = if from == hostName then to else from; in nameValuePair "yggre-${other}" { netdevConfig = { Name = "yggre-${other}"; Kind = "ip6gretap"; MTUBytes = toString (1280 + 32); }; tunnelConfig = { Local = stripSubnet wgHostIPs.${hostName}; Remote = stripSubnet wgHostIPs.${other}; }; }; linkToGreNetwork = ix: opts@{from, to, ...}: let other = if from == hostName then to else from; hexIx = let hexIx' = toHexString ix; in if (stringLength hexIx' < 2) then "0${hexIx'}" else hexIx'; in nameValuePair "yggre-${other}" { matchConfig = { Name = "yggre-${other}"; }; linkConfig = { MACAddress = "${greHostMACPrefixes.${hostName}}:${hexIx}"; RequiredForOnline = false; }; networkConfig = { BatmanAdvanced = "yggdrasil"; }; }; trim = str: if hasSuffix "\n" str then trim (removeSuffix "\n" str) else str; stripSubnet = addr: let matchRes = builtins.match "^(.*)/[0-9]+$" addr; in if matchRes == null then addr else elemAt matchRes 0; optIx = optName: xs: let withOpts = listToAttrs (imap0 (ix: x: nameValuePair x.name (x.value // { ${optName} = ix; })) (filter (x: x.value.${optName} or false) (imap0 (ix: nameValuePair (toString ix)) xs))); withoutOpts = listToAttrs (map (nv: nameValuePair nv.name (removeAttrs nv.value [optName])) (filter (x: !(x.value.${optName} or false)) (imap0 (ix: nameValuePair (toString ix)) xs))); in genList (ix: withOpts.${toString ix} or withoutOpts.${toString ix}) (length xs); mkLinks = optIx "udp2raw"; in { config = { assertions = [ { assertion = inNetwork || !(pathExists privateKeyPath || pathExists publicKeyPath); message = "yggdrasil-wg: Either both public and private keys must exist or neither."; } { assertion = !inNetwork || (wgHostIPs ? "${hostName}"); message = "yggdrasil-wg: Entry in wgHostIPs must exist."; } ] ++ map ({from, to, ...}: let other = if from == hostName then to else from; in { assertion = pathExists (mkPublicKeyPath other); message = "yggdrasil-wg: This host (${hostName}) has a link with ‘${other}’, but no public key is available for ‘${other}’."; }) hostLinks; systemd.network = mkIf inNetwork { enable = true; netdevs = { yggdrasil-wg = { netdevConfig = { Name = "yggdrasil-wg"; Kind = "wireguard"; MTUBytes = toString (1280 + 32 + 70); }; wireguardConfig = { PrivateKeyFile = config.sops.secrets."yggdrasil-wg.priv".path; ListenPort = listenPort; }; wireguardPeers = map (opts@{to, from, ...}: { wireguardPeerConfig = linkToPeer opts; }) hostLinks; }; yggdrasil = { netdevConfig = { Name = "yggdrasil"; Kind = "batadv"; MTUBytes = toString 1280; }; }; } // listToAttrs (map linkToGreDev hostLinks); networks = { yggdrasil-wg = { name = "yggdrasil-wg"; matchConfig = { Name = "yggdrasil-wg"; }; address = [wgHostIPs.${hostName}]; routes = [ { routeConfig = { Destination = "${wgSubnet}/${toString wgSubnetLength}"; }; } ]; linkConfig = { RequiredForOnline = false; }; networkConfig = { Tunnel = map (opts@{from, to, ...}: let other = if from == hostName then to else from; in "yggre-${other}") hostLinks; }; }; yggdrasil = { name = "yggdrasil"; matchConfig = { Name = "yggdrasil"; }; address = batHostIPs.${hostName}; routes = [ { routeConfig = { Destination = "${batSubnet}/${toString batSubnetLength}"; }; } ]; linkConfig = { RequiredForOnline = false; }; }; } // listToAttrs (imap0 linkToGreNetwork hostLinks); }; # networking.wireguard.interfaces = mkIf inNetwork { # yggdrasil = { # allowedIPsAsRoutes = false; # inherit listenPort; # ips = wgHostIPs.${hostName}; # peers = filter (value: value != null) (map (opts@{to, from, ...}: if from == hostName || to == hostName then linkToPeer opts else null) links); # privateKeyFile = config.sops.secrets."yggdrasil-wg.priv".path; # postSetup = '' # ip li set mtu 1280 dev yggdrasil # ${concatMapStringsSep "\n" (linkArgs: let other = if linkArgs.from == hostName then linkArgs.to else linkArgs.from; in concatMapStringsSep "\n" (otherIP: "ip route replace \"${otherIP}\" dev \"yggdrasil\" table \"main\"") wgHostIPs.${other}) hostLinks} # ${concatMapStringsSep "\n" (routeArgs: let other = if routeArgs.from == hostName then routeArgs.to else routeArgs.from; in concatMapStringsSep "\n" (otherIP: concatMapStringsSep "\n" (viaIP: "ip route replace \"${otherIP}\" via \"${viaIP}\" dev \"yggdrasil\" table \"main\"") (map stripSubnet wgHostIPs.${routeArgs.via})) wgHostIPs.${other}) hostRoutes} # ''; # }; # }; systemd.services = listToAttrs (filter ({ value, ...}: value != null) (map (opts@{to, from, ...}: let other = if from == hostName then to else from; in nameValuePair "yggdrasil-udp2raw@${other}" (if opts ? "endpointHost" && opts ? "udp2raw" then { path = with pkgs; [iptables]; serviceConfig = { RuntimeDirectory = ["udp2raw-config-${other}"]; RuntimeDirectoryMode = "0700"; ExecStartPre = pkgs.writeShellScript "udp2raw-mkconfig-${other}.sh" '' umask 0077 secret=$(cat ${config.sops.secrets."yggdrasil-udp2raw-secret".path}) cat >''${RUNTIME_DIRECTORY}/udp2raw.conf <