1
1
Fork 1
mirror of https://github.com/oddlama/nix-config.git synced 2025-10-11 07:10:39 +02:00

feat: add assignIps function that generates semi-stable ips for a list of

hostnames by (ab-)using hashes with linear probing. Useful for automatic
ip assignments in wireguard.
This commit is contained in:
oddlama 2023-05-27 23:28:50 +02:00
parent 21e88619b7
commit 1a0225336f
No known key found for this signature in database
GPG key ID: 14EFE510775FE39A

View file

@ -5,17 +5,20 @@
}: let }: let
inherit inherit
(nixpkgs.lib) (nixpkgs.lib)
assertMsg
attrNames attrNames
attrValues attrValues
concatMap concatMap
concatMapStrings concatMapStrings
concatStringsSep concatStringsSep
elem
escapeShellArg escapeShellArg
filter filter
flatten flatten
foldAttrs foldAttrs
foldl' foldl'
genAttrs genAttrs
genList
head head
mapAttrs' mapAttrs'
mergeAttrs mergeAttrs
@ -25,18 +28,20 @@
partition partition
recursiveUpdate recursiveUpdate
removeSuffix removeSuffix
stringToCharacters
substring substring
unique unique
warnIf
; ;
in rec { in rec {
# Counts how often each element occurrs in xs # Counts how often each element occurrs in xs
countOccurrences = xs: let countOccurrences = let
addOrUpdate = acc: x: addOrUpdate = acc: x:
if builtins.hasAttr x acc if builtins.hasAttr x acc
then acc // {${x} = acc.${x} + 1;} then acc // {${x} = acc.${x} + 1;}
else acc // {${x} = 1;}; else acc // {${x} = 1;};
in in
foldl' addOrUpdate {} xs; foldl' addOrUpdate {};
# Returns all elements in xs that occur at least twice # Returns all elements in xs that occur at least twice
duplicates = xs: let duplicates = xs: let
@ -55,6 +60,90 @@ in rec {
mergeToplevelConfigs = keys: attrs: mergeToplevelConfigs = keys: attrs:
genAttrs keys (attr: mkMerge (map (x: x.${attr} or {}) attrs)); genAttrs keys (attr: mkMerge (map (x: x.${attr} or {}) attrs));
# Calculates base^exp, but careful, this overflows for results > 2^62
pow = base: exp: foldl' (a: x: x * a) 1 (genList (_: base) exp);
# Converts the given hex string to an integer. Only reliable for inputs in [0, 2^63),
# after that the sign bit will overflow.
hexToDec = v: let
literalValues = {
"0" = 0;
"1" = 1;
"2" = 2;
"3" = 3;
"4" = 4;
"5" = 5;
"6" = 6;
"7" = 7;
"8" = 8;
"9" = 9;
"a" = 10;
"b" = 11;
"c" = 12;
"d" = 13;
"e" = 14;
"f" = 15;
"A" = 10;
"B" = 11;
"C" = 12;
"D" = 13;
"E" = 14;
"F" = 15;
};
in
foldl' (acc: x: acc * 16 + literalValues.${x}) 0 (stringToCharacters v);
# Generates a number in [2, 2^bits - 1) for each given hostname to be used
# as an ip address offset into an arbirary cidr with at least size=bits.
# This function tries to generate offsets as stable as possible to
# avoid changing existing hosts' ip addresses as best as possible when other
# hosts are added or removed. Although of course no actual guarantee can be
# given that this will be the case. The algorithm uses hashing with linear probing,
# xs will be sorted automatically.
assignIps = subnetLength: hosts: let
nHosts = builtins.length hosts;
nInit = builtins.length (attrNames init);
# Pre-sort all hosts, to ensure ordering invariance
sortedHosts =
warnIf
((nInit + nHosts) > 0.3 * pow 2 subnetLength)
"assignIps: hash stability may be degraded since utilization is >30%"
(builtins.sort (a: b: a < b) hosts);
# The capacity of the subnet
capacity = pow 2 subnetLength;
# Generates a hash (i.e. offset value) for a given hostname
hashElem = x:
builtins.bitAnd (capacity - 1)
(hexToDec (substring 0 16 (builtins.hashString "sha256" x)));
# Do linear probing. Returns the first unused value at or after the given value.
probe = avoid: value:
if elem value avoid
# Poor man's modulo, because nix has no modulo. Luckily we operate on a residue
# class of x modulo 2^n, so we can use bitAnd instead.
then probe avoid (builtins.bitAnd (capacity - 1) (value + 1))
else value;
# Hash a new element and avoid assigning any existing values.
hashElem = accum: x:
accum
// {
${x} = probe (attrValues accum) (hashElem x);
};
# Reserve some values for the network, host and broadcast address.
# The network and broadcast address should never be used, and we
# want to reserve the host address for the host.
init = {
_network = 0;
_host = 1;
_broadcast = capacity - 1;
};
in
assert assertMsg (subnetLength >= 2 && subnetLength <= 62)
"assignIps: subnetLength=${subnetLength} is not in [2, 62].";
assert assertMsg (nHosts <= capacity - nInit)
"assignIps: number of hosts (${toString nHosts}) must be <= capacity (${toString capacity}) - reserved (${toString nInit})";
# Assign an ip (in the subnet) to each element in order
foldl' hashElem init sortedHosts;
disko = { disko = {
gpt = { gpt = {
partEfi = name: start: end: { partEfi = name: start: end: {