From 3a2161ef205c432db0053e9a82893069b54e55ed Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Sun, 17 Oct 2021 20:50:47 +0200 Subject: yggdrasil-wg: ... --- modules/yggdrasil-wg/default.nix | 234 ++++++++++++++++++++++++++++----------- 1 file changed, 168 insertions(+), 66 deletions(-) (limited to 'modules/yggdrasil-wg') diff --git a/modules/yggdrasil-wg/default.nix b/modules/yggdrasil-wg/default.nix index 8e2ba7a4..80443644 100644 --- a/modules/yggdrasil-wg/default.nix +++ b/modules/yggdrasil-wg/default.nix @@ -5,86 +5,183 @@ with lib; let listenPort = 51820; udp2rawPort = 51821; - subnet = "2a03:4000:52:ada:1"; - subnetLength = 80; - hostLength = subnetLength + 16; + wgSubnet = "2a03:4000:52:ada:1"; + wgSubnetLength = 80; + wgHostLength = wgSubnetLength + 16; + batSubnet = "2a03:4000:52:ada:2"; + batSubnetLength = 80; + batHostLength = batSubnetLength + 16; - links = [ + links = mkLinks [ { from = "vidhar"; to = "surtr"; endpointHost = "202.61.241.61"; - persistentKeepalive = 25; - dynamicEndpointRefreshSeconds = 86400; + udp2raw = true; + PersistentKeepalive = 25; } { from = "sif"; to = "surtr"; endpointHost = "202.61.241.61"; - persistentKeepalive = 25; - dynamicEndpointRefreshSeconds = 86400; + udp2raw = true; + PersistentKeepalive = 25; } - ]; - routes = [ { from = "sif"; to = "vidhar"; - via = "surtr"; - } - { from = "vidhar"; - to = "sif"; - via = "surtr"; + endpointHost = "192.168.2.168"; + PersistentKeepalive = 25; } ]; - hostIPs = { - surtr = ["${subnet}::/${toString hostLength}"]; - vidhar = ["${subnet}:1::/${toString hostLength}"]; - sif = ["${subnet}:2::/${toString hostLength}"]; + 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 = ix: opts@{from, to, ...}: + # 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 = hostIPs.${other} ++ concatMap (rArgs: if rArgs.from != hostName || rArgs.via != to then [] else hostIPs.${rArgs.to}) routes; - publicKey = trim (readFile (mkPublicKeyPath other)); - } // (optionalAttrs (from == hostName) (filterAttrs (n: _v: !(elem n ["from" "to" "endpointHost"])) opts // optionalAttrs (opts ? "endpointHost") { endpoint = "127.0.0.1:${toString (udp2rawPort + ix)}"; })); - + 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") (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 "yggdrasil-gre-${other}" { + netdevConfig = { + Name = "yggdrasil-gre-${other}"; + Kind = "ip6gretap"; + MTUBytes = toString 1280; + }; + 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 "yggdrasil-gre-${other}" { + matchConfig = { + Name = "yggdrasil-gre-${other}"; + }; + linkConfig = { + MACAddress = "${greHostMACPrefixes.${hostName}}:${hexIx}"; + }; + networkConfig = { + Tunnel = "yggdrasil-gre-${other}"; + BatmanAdvanced = "yggdrasil"; + }; + linkConfig = { + RequiredForOnline = false; + }; + }; + 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 || (hostIPs ? "${hostName}"); - message = "yggdrasil-wg: Entry in hostIPs must exist."; + { 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; - networking.wireguard.interfaces = mkIf inNetwork { - yggdrasil = { - allowedIPsAsRoutes = false; - inherit listenPort; - ips = hostIPs.${hostName}; - peers = filter (value: value != null) (imap0 (ix: opts@{to, from, ...}: if from == hostName || to == hostName then linkToPeer ix 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\"") hostIPs.${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 hostIPs.${routeArgs.via})) hostIPs.${other}) hostRoutes} - ''; - }; + systemd.network = mkIf inNetwork { + enable = true; + netdevs = { + yggdrasil-wg = { + netdevConfig = { + Name = "yggdrasil-wg"; + Kind = "wireguard"; + MTUBytes = toString (1280 + 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"; + }; + }; + } // listToAttrs (map linkToGreDev hostLinks); + + networks = { + yggdrasil-wg = { + name = "yggdrasil-wg"; + matchConfig = { + Name = "yggdrasil-wg"; + }; + address = [wgHostIPs.${hostName}]; + linkConfig = { + RequiredForOnline = false; + }; + }; + yggdrasil = { + name = "yggdrasil"; + matchConfig = { + Name = "yggdrasil"; + }; + address = batHostIPs.${hostName}; + linkConfig = { + RequiredForOnline = false; + }; + }; + } // listToAttrs (imap0 linkToGreNetwork hostLinks); }; - systemd.services = listToAttrs (filter ({ value, ...}: value != null) (imap0 (ix: opts@{to, from, ...}: let other = if from == hostName then to else from; in nameValuePair "yggdrasil-udp2raw@${other}" (if opts ? "endpointHost" && (from == hostName || to == hostName) then { + # 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}"]; @@ -95,16 +192,17 @@ in { cat >''${RUNTIME_DIRECTORY}/udp2raw.conf <