You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
server/nix/lib/network.nix

239 lines
6.8 KiB
Nix

1 month ago
{ lib, self, ... }@flakeArg:
let
inherit (builtins)
concatMap
concatStringsSep
mapAttrs
elemAt
fromTOML
genList
isAttrs
length
match
replaceStrings
;
inherit (lib.asserts) assertMsg;
inherit (lib.lists)
count
imap1
last
singleton
sublist
;
inherit (lib.math) binToInt intToBin;
inherit (lib.strings)
commonPrefixLength
hasInfix
splitString
substring
toIntBase10
toLower
;
inherit (lib.trivial) flip pipe toHexString;
fixedWidthStrSuffix =
width: filler: str:
let
strw = lib.stringLength str;
reqWidth = width - (lib.stringLength filler);
in
assert lib.assertMsg (strw <= width)
"fixedWidthString: requested string length (${toString width}) must not be shorter than actual length (${toString strw})";
if strw == width then str else fixedWidthStrSuffix reqWidth filler str + filler;
fromHexString = str: (fromTOML "v=0x${str}").v; # TODO not (yet) available in nixpkgs.lib
toHex = str: toLower (toHexString str);
toIpClass =
ipArg:
let
statics = {
ipv4 = {
_group_bits = 8;
_group_count = 4;
_group_sep = ".";
_group_toInt = toIntBase10;
compressed = ip.decCompressed;
shorted = ip.decCompressed;
};
ipv6 = {
_group_bits = 16;
_group_count = 8;
_group_sep = ":";
_group_toInt = fromHexString;
compressed = ip.hexShorted; # TODO temporary
shorted = ip.hexShorted;
};
};
ip =
statics.${ipArg.version} # avoid recursion error
// {
type = "ipAddress";
# internal operators
__toString = s: s.cidrCompressed;
# decimal
decGroups = map ip._group_toInt ip._groups;
decCompressed = concatStringsSep ip._group_sep (map toString ip.decGroups);
# binary
binGroups = map (v: intToBin ip._group_bits) ip.decGroups; # shortcut compared to hexGroups
binRaw = concatStringsSep "" ip.binGroups;
# hex
hexGroups = map toHex ip.decGroups;
hexShorted = concatStringsSep ":" ip.hexGroups;
# TODO hexCompressed
# TODO hexExploded
# network
binRawNet = substring 0 ip.cidrInt ip.binRaw;
_cidr_max = ip._group_count * ip._group_bits;
cidrInt = if ip._cidrGroup == null then ip._cidr_max else toIntBase10 ip._cidrGroup;
cidrCompressed = "${ip.compressed}/${ip.cidrStr}";
cidrShorted = "${ip.shorted}/${ip.cidrStr}";
cidrStr = "${toString ip.cidrInt}";
# helpers
isCompatible =
o:
assert self.isParsedIP o;
ip.type == o.type;
__verifyCompat = fun: o: if ip.isCompatible o then fun o else false;
split = map (self.parseBinNet ip.version) [
"${ip.binRawNet}0"
"${ip.binRawNet}1"
];
}
// mapAttrs (_: ip.__verifyCompat) {
contains = o: ip.cidrInt <= commonPrefixLength ip.binRawNet (o.binRawNet);
equals = o: ip.decGroups == o.decGroups && ip.cidrInt == o.cidrInt;
sameNetwork = o: ip.binRawNet == o.binRawNet;
}
// ipArg;
in
assert assertMsg (length ip._groups == ip._group_count)
"invalid IP group count, expected ${toString ip._group_count}, got: ${toString (length ip._groups)}, input: ${ipArg._input} (bug, please report)";
assert ip.cidrInt <= ip._cidr_max;
ip;
in
rec {
formatMAC =
let
badChars = [
"."
":"
"_"
"-"
];
goodChars = map (x: "") badChars;
in
mac:
pipe mac [
(replaceStrings badChars goodChars)
toLower
];
isParsedIP = x: isAttrs x && x.type or null == "ipAddress";
isParsedIPv4 = x: isParsedIP x && x.version == "ipv4";
isParsedIPv6 = x: isParsedIP x && x.version == "ipv6";
parseIP = ip: if hasInfix ":" ip then parseIPv6 ip else parseIPv4 ip;
parseIPv4 =
ipStr:
let
parsed = match ''^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)(/([0-9]+))?$'' ipStr;
ip = toIpClass {
version = "ipv4";
_input = ipStr;
_cidrGroup = last parsed;
_groups = substring 0 4 parsed;
};
in
assert parsed != null; # TODO improve
ip;
parseIPv6 =
# TODO add support for IPv4 mapped addresses
ipStr:
let
parsed = match ''^(([0-9a-f]{0,4}:){1,7}[0-9a-f]{0,4})(/([0-9]+))?$'' (toLower ipStr);
rawGroups = pipe parsed [
(flip elemAt 0)
(splitString ":")
# first & last zeros might be omitted as well, but are not a compression artifact
(imap1 (i: x: if (i == 1 || i == length rawGroups) && x == "" then "0" else x))
];
groups = flip concatMap rawGroups (
x: if x == "" then genList (_: "0") (9 - length rawGroups) else singleton x
);
ip = toIpClass {
version = "ipv6";
_input = ipStr;
_cidrGroup = last parsed;
_groups = groups;
};
in
assert parsed != null;
assert count (g: g == "") rawGroups <= 1;
ip;
parseIPv6IfId =
ipStr:
let
parsed = match ''^(([0-9a-f]{0,4}:){1,3}[0-9a-f]{0,4})$'' (toLower ipStr);
rawGroups = pipe parsed [
(flip elemAt 0)
(splitString ":")
# first & last zeros might be omitted as well, but are not a compression artifact
(imap1 (i: x: if (i == 1 || i == length rawGroups) && x == "" then "0" else x))
];
groups = flip concatMap rawGroups (
x: if x == "" then genList (_: "0") (5 - length rawGroups) else singleton x
);
ip = toIpClass {
type = "ipInterfaceIdentifier";
version = "ipv6";
_group_count = 4;
_input = ipStr;
_cidrGroup = null;
_groups = groups;
};
in
assert parsed != null;
assert count (g: g == "") rawGroups <= 1;
ip;
parseBinNet =
ipV: binStr:
let
ip = toIpClass {
version = ipV;
_input = binStr;
# special overwrites - TODO integrate into toIpClass
cidrInt = length binStr;
binRaw = fixedWidthStrSuffix ip._cidr_max "0" binStr;
binGroups = genList (i: substring (ip._group_bits * i) ip.binRaw) ip._group_count;
decGroups = map binToInt ip.binGroups;
# shortcuts
binRawNet = binStr;
};
in
ip;
mergeIPv6IfId =
prefix: suffix:
let
pref = parseIPv6 prefix;
suff = parseIPv6IfId suffix;
in
assert pref.cidrInt <= 64;
"${concatStringsSep ":" (sublist 0 4 pref.hexGroups ++ suff.hexGroups)}/64";
netMinus =
excl: net:
if net.sameNetwork excl then
[ ]
else if !net.contains excl then
singleton net
else
net.split;
netListMinus = excl: concatMap (netMinus excl);
}