add lib.network
parent
5b0b20b3a9
commit
9de5d64d7e
@ -0,0 +1,238 @@
|
||||
{ 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);
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue