From 60ac560c6447c24d4e75791f0af32507a44a0c60 Mon Sep 17 00:00:00 2001 From: Matthew Salerno Date: Sun, 6 Aug 2023 17:33:16 -0400 Subject: [PATCH] Now maps to config (json compatible) to a recursive attrset that is more ergonomic (not json compatible) --- config-remapper.nix | 99 +++++++++++++++++++++++++++++++++++++++ peer-config-generator.nix | 2 +- peer-info-remapper.nix | 61 ------------------------ test-data.nix | 10 ++-- 4 files changed, 105 insertions(+), 67 deletions(-) create mode 100644 config-remapper.nix delete mode 100644 peer-info-remapper.nix diff --git a/config-remapper.nix b/config-remapper.nix new file mode 100644 index 0000000..e053874 --- /dev/null +++ b/config-remapper.nix @@ -0,0 +1,99 @@ +{lib}: wirenix-config: +let + # Math + # extract-key :: string -> list -> attrSet + # Example: + # listOfSetsToSetByKey "primary" [ {primary = "foo"; secondary = 1; tertiary = "one"} {primary = "bar"; secondary = 2; tertiary = "two"} ] + # {foo = {secondary = 1; tertiary = "one"}; bar = {secondary = 2; tertiary = "two"};} + listOfSetsToSetByKey = key: list: + builtins.listToAttrs ( + lib.lists.forEach list (elem: { + name = elem."${key}"; + value = lib.attrsets.filterAttrs (n: v: n != key) elem; + }) + ); + # returns true if `elem` is in `list` + inList = elem: list: builtins.any (e: e == elem) list; + # adds colons to a string every 4 characters for IPv6 shenanigans + add-colons = string: + if ((builtins.stringLength string) > 4) + then + ((builtins.substring 0 4 string) + ":" + (add-colons (builtins.substring 4 32 string))) + else string; + + # Peer Information + # Helper functions for querying data about peers from the config + peerInfo = { + # last 20 (hardcoded atm) characters (80 bits) of the peer's IPv6 address + IPSuffix = peerName: builtins.substring 0 20 (builtins.hashString "sha256" peerName); + # list of subnets (as subnet attrset in wirenix config) the peer belongs to + subnets = peer: builtins.filter (subnet: inList subnet.name peer.subnets) wirenix-config.subnets; + # list of groups the peer connects to + groupConnections = peer: builtins.catAttrs "group" peer.connections; + # list of peers the peer connects to + directConnections = peer: builtins.catAttrs "peer" peer.connections; + # gets the peer (as peer attrset in wirenix config) from a name + peerFromName = peerName: builtins.head (builtins.filter (peer: peer.name == peerName) wirenix-config.peers); + # gets all peers (as peer attrset in wirenix config) the are in a group + peersInGroup = groupName: builtins.filter (peer: inList groupName peer.groups) wirenix-config.peers; + # gets all peers (as peer attrset in wirenix config) that the given peer connects to, may contain the peer itself and duplicates + connectionsUnfiltered = peer: (builtins.map (peerInfo.peerFromName) (peerInfo.directConnections peer)) ++ (builtins.concatMap (group: peerInfo.peersInGroup group) (peerInfo.groupConnections peer)); + # gets all peers (as peer attrset in wirenix config) that the given peer connects to, will not contain the peer itself or duplicates + connections = peer: lib.lists.remove peer (lib.lists.unique (peerInfo.connectionsUnfiltered peer)); + # returns the peer's IP when given the peer's name and subnet name + IP = subnetName: PeerName: (add-colons ((subnetInfo.prefix subnetName) + (peerInfo.IPSuffix PeerName))) + "/80"; + }; + # Subnet Information + # Helper functions for querying data about subnets from the config + subnetInfo = { + # gets the subnet (as subnet attrset in wirenix config) from a name + subnetFromName = subnetName: builtins.head (builtins.filter (subnet: subnet.name == subnetName) wirenix-config.subnets); + # gets the first 10 characters of the IPV6 address for the subnet + prefix = subnetName: "fd" + (builtins.substring 0 10 (builtins.hashString "sha256" subnetName)); + # gets all peers (as peer attrset in wirenix config) that belong to the subnet + peers = subnet: builtins.filter (peer: inList subnet.name peer.subnets) wirenix-config.peers; + }; + + # Mappers take a peer or subnet from the config and convert it + # into a recursive attrset that is better suited for nix configs + mappers = rec { + + # Maps a wirenix config subnet to a recursive attrset, structure is as follows: + # peers: a set of peers (as similar recursive attrsets), keyed by name + # ip: the subnet ip in CIDR notation + subnetMap = subnet: { + name = subnet.name; + peers = listOfSetsToSetByKey "name" (builtins.map (peerMap) (subnetInfo.peers subnet)); + ip = (add-colons (subnetInfo.prefix subnet.name)) + "::/80"; + }; + + # Maps a wirenix config peer to a recursive attrset, structure is as follows: + # subnets: a set of subnets (as similar recursive attrsets), keyed by name, with the following structure: + # subnet: the subnet as described in the subnetMap function's description + # ip: the peer's ip on the subnet (can only be one IP at the moment) + # peers: peers (as recursive attrset) on the subnet that the current peer has connections with + # connections: a set of all peers (as recursive attrset) which the current peer connects to, keyed by name + # publicKey: the peer's public key + # privateKeyFile: the location of the peer's private key + peerMap = peer: { + name = peer.name; + subnets = listOfSetsToSetByKey "name" ( + builtins.map (subnet: { + name = subnet.name; + subnet = lib.attrsets.filterAttrs (n: v: n != "name") (subnetMap subnet); + ip = peerInfo.IP subnet.name peer.name; + peers = listOfSetsToSetByKey "name" (builtins.map (peerMap) (builtins.filter (otherPeer: inList subnet.name otherPeer.subnets) (peerInfo.connections peer))); + }) (peerInfo.subnets peer) + ); + connections = listOfSetsToSetByKey "name" (builtins.map (peerMap) (peerInfo.connections peer)); + publicKey = peer.publicKey; + privateKeyFile = peer.privateKeyFile; + }; + }; +in +{ + peerFromName = lib.attrsets.filterAttrs (n: v: n != "name") (mappers.peerMap peerInfo.peerFromName); + subnetFromName = lib.attrsets.filterAttrs (n: v: n != "name") (mappers.subnetMap subnetInfo.subnetFromName); + peers = listOfSetsToSetByKey "name" (builtins.map (mappers.peerMap) (wirenix-config.peers)); + subnets = listOfSetsToSetByKey "name" (builtins.map (mappers.subnetMap) (wirenix-config.subnets)); +} \ No newline at end of file diff --git a/peer-config-generator.nix b/peer-config-generator.nix index 959bf0b..82ae138 100644 --- a/peer-config-generator.nix +++ b/peer-config-generator.nix @@ -2,7 +2,7 @@ {config, lib, ...}: let has-rekey = config ? rekey; - infoRemapper = import ./peer-info-remapper.nix {inherit lib;} config.modules.wirenix.config; + infoRemapper = import ./config-remapper.nix {inherit lib;} config.modules.wirenix.config; in { diff --git a/peer-info-remapper.nix b/peer-info-remapper.nix deleted file mode 100644 index 7960fe3..0000000 --- a/peer-info-remapper.nix +++ /dev/null @@ -1,61 +0,0 @@ -{lib}: wirenix-config: -let - # Math - inList = elem: list: builtins.any (e: e == elem) list; - # IP - add-colons = string: - if ((builtins.stringLength string) > 4) - then - ((builtins.substring 0 4 string) + ":" + (add-colons (builtins.substring 4 32 string))) - else string; - - # Peer Information - peerInfo = { - peerSuffix = peerName: builtins.substring 0 20 (builtins.hashString "sha256" peerName); - peerSubnets = peer: builtins.filter (subnet: inList subnet.name peer.subnets) wirenix-config.subnets; - peerGroups = peer: builtins.catAttrs "group" peer.peers; - directPeers = peer: builtins.catAttrs "peer" peer.peers; - peerFromName = peerName: builtins.head (builtins.filter (peer: peer.name == peerName) wirenix-config.peers); - peersInGroup = groupName: builtins.filter (peer: inList groupName peer.groups) wirenix-config.peers; - peerPeersUnfiltered = peer: (builtins.map (peerInfo.peerFromName) (peerInfo.directPeers peer)) ++ (builtins.concatMap (group: peerInfo.peersInGroup group) (peerInfo.peerGroups peer)); - peerIP = subnetName: PeerName: (add-colons ((subnetInfo.subnetPrefix subnetName) + (peerInfo.peerSuffix PeerName))) + "/80"; - }; - # Subnet Information - subnetInfo = { - subnetPrefix = subnetName: "fd" + (builtins.substring 0 10 (builtins.hashString "sha256" subnetName)); - subnetPeers = subnet: builtins.filter (peer: inList subnet.name peer.subnets) wirenix-config.peers; - }; -in -{ - peer = peer: - rec { - subnets = peerInfo.peerSubnets peer; - peers = lib.lists.remove peer (lib.lists.unique (peerInfo.peerPeersUnfiltered peer)); - ip = subnet: peerInfo.peerIP subnet.name peer.name; - peersOnSubnet = subnet: builtins.filter (otherPeer: inList subnet.name otherPeer.subnets) peers; - publicKey = peer.publicKey; - privateKeyFile = peer.privateKeyFile; - }; - subnet = subnet: - { - peers = subnetInfo.subnetPeers subnet; - ip = (add-colons (subnetInfo.subnetPrefix subnet.name)) + "::/80"; - }; -} -# subnets: -# networkOne: -# prefix: "auto" or "none" or canonicalized ipv6 -# ipv4: canonicalized ipv4 or "none" -# ipv6: canonicalized ipv6 or "auto" or "none" -# peers: -# peerOne: -# pubKey: "ABC..." Or "auto" -# subnets: -# networkOne: -# - ipv4: ip -# - ipv6: "auto" or "none" or ip -# groups: -# - "groupOne" -# peers: -# - peer: "peerTwo" -# - group: "groupOne" \ No newline at end of file diff --git a/test-data.nix b/test-data.nix index c76c13b..eaae2ba 100644 --- a/test-data.nix +++ b/test-data.nix @@ -13,7 +13,7 @@ groups = [ ]; - peers = [ + connections = [ { group = "everyoneConnectsToMe"; } ]; privateKeyFile = "/not/yet"; @@ -28,7 +28,7 @@ groups = [ ]; - peers = [ + connections = [ { group = "everyoneConnectsToMe"; } { group = "subnet one group"; } ]; @@ -46,7 +46,7 @@ "everyoneConnectsToMe" "subnet two group" ]; - peers = [ + connections = [ { group = "everyoneConnectsToMe"; } { group = "subnet two group"; } ]; @@ -65,7 +65,7 @@ "everyoneConnectsToMe" "subnet two group" ]; - peers = [ + connections = [ { group = "everyoneConnectsToMe"; } { group = "subnet two group"; } ]; @@ -82,7 +82,7 @@ groups = [ ]; - peers = [ + connections = [ { group = "everyoneConnectsToMe"; } { peer = "peer.one"; } ];