139 lines
3.9 KiB
Nix
139 lines
3.9 KiB
Nix
# 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: isValidIp4 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"
|
|
]);
|
|
nth = n:
|
|
let
|
|
result = nthInRange4 range n;
|
|
try =
|
|
if check result
|
|
then id
|
|
else
|
|
warn (concatStringsSep " " [
|
|
"nth call with n = ${toString n}"
|
|
"is out of range for subnet ${str}"
|
|
]);
|
|
in
|
|
try result;
|
|
in
|
|
try {
|
|
inherit baseIp check cidr mask nth 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}";
|
|
|
|
# Get the nth address from an IPv4 range
|
|
nthInRange4 = { from, to }: n:
|
|
let
|
|
carry = lhs: { carry, acc }:
|
|
let
|
|
totVal = lhs + carry;
|
|
in
|
|
{
|
|
carry = totVal / 256;
|
|
acc = [ (mod totVal 256) ] ++ acc;
|
|
};
|
|
carried = foldr carry { carry = n; acc = [ ]; } from;
|
|
checkInRange =
|
|
if (to - from) < n
|
|
then
|
|
warn ''
|
|
nthInRange4: '${n}'-th address outside of range (${prettyIp4 from}, ${prettyIp4 to})
|
|
''
|
|
else id;
|
|
in
|
|
checkInRange carried.acc;
|
|
|
|
# Convert an IPv4 range into a list of all its constituent addresses
|
|
rangeIp4 =
|
|
{ from, to } @ arg:
|
|
let
|
|
numAddresses =
|
|
builtins.foldl' (acc: rhs: acc * 256 + rhs)
|
|
0
|
|
(zipListsWith (lhs: rhs: lhs - rhs) to from);
|
|
in
|
|
map (nthInRange4 arg) (range 0 numAddresses);
|
|
|
|
isValidIp4 = ip:
|
|
(builtins.all (n: n >= 0 && n < 256) ip) && (builtins.length ip == 4);
|
|
}
|