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

chore: refactor assignIps as cidr library function that returns ips

This commit is contained in:
oddlama 2023-05-28 00:17:55 +02:00
parent 1a0225336f
commit 05813fafb4
No known key found for this signature in database
GPG key ID: 14EFE510775FE39A
2 changed files with 83 additions and 54 deletions

View file

@ -1,4 +1,5 @@
{ {
extraLib,
inputs, inputs,
lib, lib,
nodeName, nodeName,
@ -68,7 +69,7 @@
# > net.cidr.canonicalize "192.168.1.100/24" # > net.cidr.canonicalize "192.168.1.100/24"
# "192.168.1.0/24" # "192.168.1.0/24"
canonicalize = x: libWithNet.net.cidr.make (libWithNet.net.cidr.length x) (ip x); canonicalize = x: libWithNet.net.cidr.make (libWithNet.net.cidr.length x) (ip x);
# mergev4 :: [cidr4 | ipv4] -> (cidr4 | null) # mergev4 :: [cidrv4 | ipv4] -> (cidrv4 | null)
# #
# Returns the smallest cidr network that includes all given networks. # Returns the smallest cidr network that includes all given networks.
# If no cidr mask is given, /32 is assumed. # If no cidr mask is given, /32 is assumed.
@ -109,7 +110,7 @@
if addrs == [] if addrs == []
then null then null
else libWithNet.net.cidr.make bestLength firstIp; else libWithNet.net.cidr.make bestLength firstIp;
# mergev6 :: [cidr6 | ipv6] -> (cidr6 | null) # mergev6 :: [cidrv6 | ipv6] -> (cidrv6 | null)
# #
# Returns the smallest cidr network that includes all given networks. # Returns the smallest cidr network that includes all given networks.
# If no cidr mask is given, /128 is assumed. # If no cidr mask is given, /128 is assumed.
@ -150,7 +151,7 @@
if addrs == [] if addrs == []
then null then null
else libWithNet.net.cidr.make bestLength firstIp; else libWithNet.net.cidr.make bestLength firstIp;
# merge :: [cidr] -> { cidrv4 = (cidr4 | null); cidrv6 = (cidr4 | null); } # merge :: [cidr] -> { cidrv4 = (cidrv4 | null); cidrv6 = (cidrv4 | null); }
# #
# Returns the smallest cidr network that includes all given networks, # Returns the smallest cidr network that includes all given networks,
# but yields two separate result for all given ipv4 and ipv6 addresses. # but yields two separate result for all given ipv4 and ipv6 addresses.
@ -161,6 +162,85 @@
cidrv4 = mergev4 v4_and_v6.wrong; cidrv4 = mergev4 v4_and_v6.wrong;
cidrv6 = mergev6 v4_and_v6.right; cidrv6 = mergev6 v4_and_v6.right;
}; };
# assignIps :: cidr -> [int | ip] -> [string] -> [ip]
#
# Assigns a semi-stable ip address from the given cidr network to each hostname.
# The algorithm is based on hashing (abusing sha256) with linear probing.
# The order of hosts doesn't matter. No ip (or offset) from the reserved list
# will be assigned. The network address and broadcast address will always be reserved
# automatically.
#
# Examples:
#
# > net.cidr.assignIps "192.168.100.1/24" [] ["a" "b" "c"]
# { a = "192.168.100.202"; b = "192.168.100.74"; c = "192.168.100.226"; }
#
# > net.cidr.assignIps "192.168.100.1/24" [] ["a" "b" "c" "a-new-elem"]
# { a = "192.168.100.202"; a-new-elem = "192.168.100.88"; b = "192.168.100.74"; c = "192.168.100.226"; }
#
# > net.cidr.assignIps "192.168.100.1/24" [202 "192.168.100.74"] ["a" "b" "c"]
# { a = "192.168.100.203"; b = "192.168.100.75"; c = "192.168.100.226"; }
assignIps = net: reserved: hosts: let
cidrSize = libWithNet.net.cidr.size net;
capacity = libWithNet.net.cidr.capacity net;
# The base address of the network. Used to convert ip-based reservations to offsets
baseAddr = host 0 net;
# 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. We also convert
# any ips to offsets here.
init = lib.unique (
[0 (capacity - 1)]
++ lib.flip map reserved (x:
if builtins.typeOf x == "int"
then x
else -(libWithNet.net.ip.diff baseAddr x))
);
nHosts = builtins.length hosts;
nInit = builtins.length init;
# Pre-sort all hosts, to ensure ordering invariance
sortedHosts =
lib.warnIf
((nInit + nHosts) > 0.3 * capacity)
"assignIps: hash stability may be degraded since utilization is >30%"
(builtins.sort (a: b: a < b) hosts);
# Generates a hash (i.e. offset value) for a given hostname
hashElem = x:
builtins.bitAnd (capacity - 1)
(extraLib.hexToDec (builtins.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 lib.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.
assignOne = {
assigned,
used,
}: x: let
value = probe used (hashElem x);
in {
assigned =
assigned
// {
${x} = host value net;
};
used = [value] ++ used;
};
in
assert lib.assertMsg (cidrSize >= 2 && cidrSize <= 62)
"assignIps: cidrSize=${cidrSize} is not in [2, 62].";
assert lib.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
(lib.foldl' assignOne {
assigned = {};
used = init;
}
sortedHosts)
.assigned;
}; };
ip = { ip = {
# Checks whether the given address (with or without cidr notation) is an ipv6 address. # Checks whether the given address (with or without cidr notation) is an ipv6 address.

View file

@ -93,57 +93,6 @@ in rec {
in in
foldl' (acc: x: acc * 16 + literalValues.${x}) 0 (stringToCharacters v); 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: {