diff --git a/lib/ip.nix b/lib/ip.nix new file mode 100644 index 0000000..7009727 --- /dev/null +++ b/lib/ip.nix @@ -0,0 +1,88 @@ +# Taken from [1]. +# +# [1]: https://gist.github.com/Infinisil/c68a2f385c6d7c52c324d529b7f1d07c +{ lib, ... }: +let + inherit (lib) + bitAnd + concatMapStringsSep + concatStringsSep + elemAt + foldr + genList + id + mod + range + toInt + warn + zipListsWith + ; +in +rec { + # Translate CIDR mask into list of masks to bitwise-and with parsed IPv4 + # address in order to get base address of the subnet + cidrToMask4 = + let + # Generate a partial mask for an integer from 0 to 7 + part = n: + if n == 0 then 0 + else part (n - 1) / 2 + 128; + in + cidr: + let + # How many initial parts of the mask are full (=255) + fullParts = cidr / 8; + partGen = i: + # Fill up initial full parts + if i < fullParts then 255 + # If we're above the first non-full part, fill with 0 + else if fullParts < i then 0 + # First non-full part generation + else part (mod cidr 8); + in + genList partGen 4; + + # Parse an IPv4 address into a list of 4 integers + parseIp4 = str: + map toInt (builtins.match "([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)" str); + + # Parse a given IPv4 subnet into an attribute set containing: + # * baseIp: the base IP for this subnet + # * check: a function to check if a parsed IPv4 address is part of this subnet + # * cidr: the value of the CIDR subnet + # * mask: the mask corresponding to the CIDR to bitwise-and with the address + # and get the base address + # * range: an attribute set containing `from` and `to`, the lowest and + # highest address in the range, in the form of a parsed IPv4 address. + parseSubnet4 = str: + let + splitParts = builtins.split "/" str; + givenIp = parseIp4 (elemAt splitParts 0); + cidr = toInt (elemAt splitParts 2); + mask = cidrToMask4 cidr; + baseIp = zipListsWith bitAnd givenIp mask; + range = { + from = baseIp; + to = zipListsWith (b: m: 255 - m + b) baseIp mask; + }; + check = ip: baseIp == zipListsWith (b: m: bitAnd b m) ip mask; + try = + if baseIp == givenIp + then id + else + warn (concatStringsSep " " [ + "subnet ${str} has a too specific base address ${prettyIp4 givenIp}," + "which will get masked to ${prettyIp4 baseIp}," + "which should be used instead" + ]); + in + try { + inherit baseIp check cidr mask range; + }; + + # Pretty print a parsed IPv4 address into a human readable form + prettyIp4 = concatMapStringsSep "." toString; + + # Pretty print a parsed subnet into a human readable form + prettySubnet4 = { baseIp, cidr, ... }: "${prettyIp4 baseIp}/${toString cidr}"; +}