forked from mirrors_public/oddlama_nix-config
chore: refactor assignIps as cidr library function that returns ips
This commit is contained in:
parent
1a0225336f
commit
05813fafb4
2 changed files with 83 additions and 54 deletions
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
extraLib,
|
||||
inputs,
|
||||
lib,
|
||||
nodeName,
|
||||
|
@ -68,7 +69,7 @@
|
|||
# > net.cidr.canonicalize "192.168.1.100/24"
|
||||
# "192.168.1.0/24"
|
||||
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.
|
||||
# If no cidr mask is given, /32 is assumed.
|
||||
|
@ -109,7 +110,7 @@
|
|||
if addrs == []
|
||||
then null
|
||||
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.
|
||||
# If no cidr mask is given, /128 is assumed.
|
||||
|
@ -150,7 +151,7 @@
|
|||
if addrs == []
|
||||
then null
|
||||
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,
|
||||
# but yields two separate result for all given ipv4 and ipv6 addresses.
|
||||
|
@ -161,6 +162,85 @@
|
|||
cidrv4 = mergev4 v4_and_v6.wrong;
|
||||
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 = {
|
||||
# Checks whether the given address (with or without cidr notation) is an ipv6 address.
|
||||
|
|
51
nix/lib.nix
51
nix/lib.nix
|
@ -93,57 +93,6 @@ in rec {
|
|||
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 = {
|
||||
gpt = {
|
||||
partEfi = name: start: end: {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue