diff --git a/lib/misc.nix b/lib/misc.nix index 70d667b..dd0fbcc 100644 --- a/lib/misc.nix +++ b/lib/misc.nix @@ -1,43 +1,40 @@ -_inputs: _final: prev: let - inherit - (prev.lib) +_inputs: final: prev: +let + inherit (prev.lib) filter foldl' genAttrs - genList mergeAttrs mkMerge stringToCharacters substring unique ; + inherit (final.lib) + bit + ; # Counts how often each element occurrs in xs. # Elements must be strings. - countOccurrences = - foldl' - (acc: x: acc // {${x} = (acc.${x} or 0) + 1;}) - {}; + countOccurrences = foldl' (acc: x: acc // { ${x} = (acc.${x} or 0) + 1; }) { }; # Returns all elements in xs that occur at least twice - duplicates = xs: let - occurrences = countOccurrences xs; - in + duplicates = + xs: + let + occurrences = countOccurrences xs; + in unique (filter (x: occurrences.${x} > 1) xs); # Concatenates all given attrsets as if calling a // b in order. - concatAttrs = foldl' mergeAttrs {}; + concatAttrs = foldl' mergeAttrs { }; # True if the path or string starts with / isAbsolutePath = x: substring 0 1 x == "/"; # Merges all given attributes from the given attrsets using mkMerge. # Useful to merge several top-level configs in a module. - mergeToplevelConfigs = keys: attrs: - genAttrs keys (attr: mkMerge (map (x: x.${attr} or {}) attrs)); - - # Calculates base^exp, but careful, this overflows for results > 2^62 - pow = base: exp: foldl' (a: x: x * a) 1 (genList (_: base) exp); + mergeToplevelConfigs = keys: attrs: genAttrs keys (attr: mkMerge (map (x: x.${attr} or { }) attrs)); hexLiteralValues = { "0" = 0; @@ -66,19 +63,17 @@ _inputs: _final: prev: let # Converts the given hex string to an integer. Only reliable for inputs in [0, 2^63), # after that the sign bit will overflow. - hexToDec = v: foldl' (acc: x: acc * 16 + hexLiteralValues.${x}) 0 (stringToCharacters v); -in { - lib = - prev.lib - // { - inherit - concatAttrs - countOccurrences - duplicates - hexToDec - isAbsolutePath - mergeToplevelConfigs - pow - ; - }; + hexToDec = v: foldl' (acc: x: (bit.left acc 4) + hexLiteralValues.${x}) 0 (stringToCharacters v); +in +{ + lib = prev.lib // { + inherit + hexToDec + concatAttrs + countOccurrences + duplicates + isAbsolutePath + mergeToplevelConfigs + ; + }; } diff --git a/lib/net.nix b/lib/net.nix index 3713f09..9c274c3 100644 --- a/lib/net.nix +++ b/lib/net.nix @@ -1,6 +1,6 @@ -inputs: _final: prev: let - inherit - (prev.lib) +inputs: _final: prev: +let + inherit (prev.lib) all any assertMsg @@ -22,24 +22,29 @@ inputs: _final: prev: let ; # From misc.nix - inherit - (prev.lib) + inherit (prev.lib) hexToDec - pow ; # IP address math library # https://gist.github.com/duairc/5c9bb3c922e5d501a1edb9e7b3b845ba # Plus some extensions by us libNet = - (import "${inputs.lib-net}/net.nix" { + (import ./netu.nix { inherit (inputs.nixpkgs) lib; - }) - .lib - .net; -in { + }).lib.net; +in +{ lib = recursiveUpdate prev.lib { - net = recursiveUpdate (removeAttrs libNet ["types"]) { + inherit (libNet) + arithmetic + typechecks + bit + implementations + parsers + ; + + net = recursiveUpdate (removeAttrs libNet [ "types" ]) { cidr = rec { # host :: (ip | mac | integer) -> cidr -> ip # @@ -58,11 +63,13 @@ in { # "192.168.1.0" # > net.cidr.host (-257) "192.168.1.0/24" # - host = i: n: let - cap = libNet.cidr.capacity n; - in + host = + i: n: + let + cap = libNet.cidr.capacity n; + in assert assertMsg (i >= (-cap) && i < cap) "The host ${toString i} lies outside of ${n}"; - libNet.cidr.host i n; + libNet.cidr.host i n; # hostCidr :: (ip | mac | integer) -> cidr -> cidr # # Returns the nth host in the given cidr range (like cidr.host) @@ -103,37 +110,30 @@ in { # # > net.cidr.mergev4 ["192.168.1.1/24" "192.168.6.1/32"] # "192.168.0.0/21" - mergev4 = addrs_: let - # Append /32 if necessary - addrs = map (x: - if hasInfix "/" x - then x - else "${x}/32") - addrs_; - # The smallest occurring length is the first we need to start checking, since - # any greater cidr length represents a smaller address range which - # wouldn't contain all of the original addresses. - startLength = foldl' min 32 (map libNet.cidr.length addrs); - possibleLengths = reverseList (range 0 startLength); - # The first ip address will be "expanded" in cidr length until it covers all other - # used addresses. - firstIp = ip (head addrs); - # Return the first (i.e. greatest length -> smallest prefix) cidr length - # in the list that covers all used addresses - bestLength = head (filter - # All given addresses must be contained by the generated address. - (len: - all (x: - libNet.cidr.contains - (ip x) - (libNet.cidr.make len firstIp)) - addrs) - possibleLengths); - in + mergev4 = + addrs_: + let + # Append /32 if necessary + addrs = map (x: if hasInfix "/" x then x else "${x}/32") addrs_; + # The smallest occurring length is the first we need to start checking, since + # any greater cidr length represents a smaller address range which + # wouldn't contain all of the original addresses. + startLength = foldl' min 32 (map libNet.cidr.length addrs); + possibleLengths = reverseList (range 0 startLength); + # The first ip address will be "expanded" in cidr length until it covers all other + # used addresses. + firstIp = ip (head addrs); + # Return the first (i.e. greatest length -> smallest prefix) cidr length + # in the list that covers all used addresses + bestLength = head ( + filter + # All given addresses must be contained by the generated address. + (len: all (x: libNet.cidr.contains (ip x) (libNet.cidr.make len firstIp)) addrs) + possibleLengths + ); + in assert assertMsg (!any (hasInfix ":") addrs) "mergev4 cannot operate on ipv6 addresses"; - if addrs == [] - then null - else libNet.cidr.make bestLength firstIp; + if addrs == [ ] then null else libNet.cidr.make bestLength firstIp; # mergev6 :: [cidrv6 | ipv6] -> (cidrv6 | null) # # Returns the smallest cidr network that includes all given networks. @@ -143,48 +143,44 @@ in { # # > net.cidr.mergev6 ["fd00:dead:cafe::/64" "fd00:fd12:3456:7890::/56"] # "fd00:c000::/18" - mergev6 = addrs_: let - # Append /128 if necessary - addrs = map (x: - if hasInfix "/" x - then x - else "${x}/128") - addrs_; - # The smallest occurring length is the first we need to start checking, since - # any greater cidr length represents a smaller address range which - # wouldn't contain all of the original addresses. - startLength = foldl' min 128 (map libNet.cidr.length addrs); - possibleLengths = reverseList (range 0 startLength); - # The first ip address will be "expanded" in cidr length until it covers all other - # used addresses. - firstIp = ip (head addrs); - # Return the first (i.e. greatest length -> smallest prefix) cidr length - # in the list that covers all used addresses - bestLength = head (filter - # All given addresses must be contained by the generated address. - (len: - all (x: - libNet.cidr.contains - (ip x) - (libNet.cidr.make len firstIp)) - addrs) - possibleLengths); - in + mergev6 = + addrs_: + let + # Append /128 if necessary + addrs = map (x: if hasInfix "/" x then x else "${x}/128") addrs_; + # The smallest occurring length is the first we need to start checking, since + # any greater cidr length represents a smaller address range which + # wouldn't contain all of the original addresses. + startLength = foldl' min 128 (map libNet.cidr.length addrs); + possibleLengths = reverseList (range 0 startLength); + # The first ip address will be "expanded" in cidr length until it covers all other + # used addresses. + firstIp = ip (head addrs); + # Return the first (i.e. greatest length -> smallest prefix) cidr length + # in the list that covers all used addresses + bestLength = head ( + filter + # All given addresses must be contained by the generated address. + (len: all (x: libNet.cidr.contains (ip x) (libNet.cidr.make len firstIp)) addrs) + possibleLengths + ); + in assert assertMsg (all (hasInfix ":") addrs) "mergev6 cannot operate on ipv4 addresses"; - if addrs == [] - then null - else libNet.cidr.make bestLength firstIp; + if addrs == [ ] then null else libNet.cidr.make bestLength firstIp; # 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. # Equivalent to calling mergev4 and mergev6 on a partition individually. - merge = addrs: let - v4_and_v6 = partition (hasInfix ":") addrs; - in { - cidrv4 = mergev4 v4_and_v6.wrong; - cidrv6 = mergev6 v4_and_v6.right; - }; + merge = + addrs: + let + v4_and_v6 = partition (hasInfix ":") addrs; + in + { + 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. @@ -203,68 +199,76 @@ in { # # > 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 = libNet.cidr.size net; - capacity = libNet.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 = unique ( - [0 (capacity - 1)] - ++ flip map reserved (x: - if builtins.typeOf x == "int" - then x - else -(libNet.ip.diff baseAddr x)) - ); - nHosts = builtins.length hosts; - nInit = builtins.length init; - # Pre-sort all hosts, to ensure ordering invariance - sortedHosts = - warnIf - ((nInit + nHosts) > 0.3 * capacity) - "assignIps: hash stability may be degraded since utilization is >30%" - (builtins.sort builtins.lessThan hosts); - # Generates a hash (i.e. offset value) for a given hostname - hashElem = x: - builtins.bitAnd (capacity - 1) - (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 elem value avoid - # TODO lib.mod - # 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; + assignIps = + net: reserved: hosts: + let + cidrSize = libNet.cidr.size net; + capacity = libNet.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 = unique ( + [ + 0 + (capacity - 1) + ] + ++ flip map reserved (x: if builtins.typeOf x == "int" then x else -(libNet.ip.diff baseAddr x)) + ); + nHosts = builtins.length hosts; + nInit = builtins.length init; + # Pre-sort all hosts, to ensure ordering invariance + sortedHosts = + warnIf ((nInit + nHosts) > 0.3 * capacity) + "assignIps: hash stability may be degraded since utilization is >30%" + (builtins.sort builtins.lessThan hosts); + # Generates a hash (i.e. offset value) for a given hostname + hashElem = + x: + builtins.bitAnd (capacity - 1) ( + 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 + elem value avoid + # TODO lib.mod + # 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; }; - used = [value] ++ used; - }; - in - assert assertMsg (cidrSize >= 2 && cidrSize <= 62) - "assignIps: cidrSize=${toString cidrSize} is not in [2, 62]."; + in + assert assertMsg ( + cidrSize >= 2 && cidrSize <= 62 + ) "assignIps: cidrSize=${toString cidrSize} is not in [2, 62]."; assert assertMsg (nHosts <= capacity - nInit) - "assignIps: number of hosts (${toString nHosts}) must be <= capacity (${toString capacity}) - reserved (${toString 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' assignOne { - assigned = {}; - used = init; - } - sortedHosts) - .assigned; + (foldl' assignOne { + assigned = { }; + used = init; + } sortedHosts).assigned; }; ip = rec { # Checks whether the given address (with or without cidr notation) is an ipv4 address. @@ -275,11 +279,14 @@ in { mac = { # Adds offset to the given base address and ensures the result is in # a locally administered range by replacing the second nibble with a 2. - addPrivate = base: offset: let - added = libNet.mac.add base offset; - pre = substring 0 1 added; - suf = substring 2 (-1) added; - in "${pre}2${suf}"; + addPrivate = + base: offset: + let + added = libNet.mac.add base offset; + pre = substring 0 1 added; + suf = substring 2 (-1) added; + in + "${pre}2${suf}"; # assignMacs :: mac (base) -> int (size) -> [int | mac] (reserved) -> [string] (hosts) -> [mac] # # Assigns a semi-stable MAC address starting in [base, base + 2^size) to each hostname. @@ -297,63 +304,64 @@ in { # # > net.mac.assignMacs "11:22:33:00:00:00" 24 ["11:22:33:1b:bd:ca"] ["a" "b" "c"] # { a = "11:22:33:1b:bd:cb"; b = "11:22:33:39:59:4a"; c = "11:22:33:50:7a:e2"; } - assignMacs = base: size: reserved: hosts: let - capacity = pow 2 size; - baseAsInt = libNet.mac.diff base "00:00:00:00:00:00"; - init = unique ( - flip map reserved (x: - if builtins.typeOf x == "int" - then x - else libNet.mac.diff x base) - ); - nHosts = builtins.length hosts; - nInit = builtins.length init; - # Pre-sort all hosts, to ensure ordering invariance - sortedHosts = - warnIf - ((nInit + nHosts) > 0.3 * capacity) - "assignMacs: hash stability may be degraded since utilization is >30%" - (builtins.sort builtins.lessThan hosts); - # 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 - # TODO lib.mod - # 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} = libNet.mac.add value base; + assignMacs = + base: size: reserved: hosts: + let + capacity = libNet.bit.left 1 size; + baseAsInt = libNet.net.mac.diff base "00:00:00:00:00:00"; + init = unique ( + flip map reserved (x: if builtins.typeOf x == "int" then x else libNet.net.mac.diff x base) + ); + nHosts = builtins.length hosts; + nInit = builtins.length init; + # Pre-sort all hosts, to ensure ordering invariance + sortedHosts = + warnIf ((nInit + nHosts) > 0.3 * capacity) + "assignMacs: hash stability may be degraded since utilization is >30%" + (builtins.sort builtins.lessThan hosts); + # 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 + # TODO lib.mod + # 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} = libNet.net.mac.add value base; + }; + used = [ value ] ++ used; }; - used = [value] ++ used; - }; - in - assert assertMsg (size >= 2 && size <= 62) - "assignMacs: size=${toString size} is not in [2, 62]."; - assert assertMsg (builtins.bitAnd (capacity - 1) baseAsInt == 0) - "assignMacs: the size=${toString size} least significant bits of the base mac address must be 0."; + in + assert assertMsg (size >= 2 && size <= 62) "assignMacs: size=${toString size} is not in [2, 62]."; + assert assertMsg ( + builtins.bitAnd (capacity - 1) baseAsInt == 0 + ) "assignMacs: the size=${toString size} least significant bits of the base mac address must be 0."; assert assertMsg (nHosts <= capacity - nInit) - "assignMacs: number of hosts (${toString nHosts}) must be <= capacity (${toString capacity}) - reserved (${toString nInit})"; + "assignMacs: 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' assignOne { - assigned = {}; - used = init; - } - sortedHosts) - .assigned; + (foldl' assignOne { + assigned = { }; + used = init; + } sortedHosts).assigned; }; }; types.net = libNet.types; diff --git a/lib/netu.nix b/lib/netu.nix index a6df65e..5cf6995 100644 --- a/lib/netu.nix +++ b/lib/netu.nix @@ -460,20 +460,111 @@ let bit = let - shift = - n: x: - if n < 0 then - x * math.pow 2 (-n) + lut = { + "0" = 1; + "1" = 2; + "2" = 4; + "3" = 8; + "4" = 16; + "5" = 32; + "6" = 64; + "7" = 128; + "8" = 256; + "9" = 512; + "10" = 1024; + "11" = 2048; + "12" = 4096; + "13" = 8192; + "14" = 16384; + "15" = 32768; + "16" = 65536; + "17" = 131072; + "18" = 262144; + "19" = 524288; + "20" = 1048576; + "21" = 2097152; + "22" = 4194304; + "23" = 8388608; + "24" = 16777216; + "25" = 33554432; + "26" = 67108864; + "27" = 134217728; + "28" = 268435456; + "29" = 536870912; + "30" = 1073741824; + "31" = 2147483648; + "32" = 4294967296; + "33" = 8589934592; + "34" = 17179869184; + "35" = 34359738368; + "36" = 68719476736; + "37" = 137438953472; + "38" = 274877906944; + "39" = 549755813888; + "40" = 1099511627776; + "41" = 2199023255552; + "42" = 4398046511104; + "43" = 8796093022208; + "44" = 17592186044416; + "45" = 35184372088832; + "46" = 70368744177664; + "47" = 140737488355328; + "48" = 281474976710656; + "49" = 562949953421312; + "50" = 1125899906842624; + "51" = 2251799813685248; + "52" = 4503599627370496; + "53" = 9007199254740992; + "54" = 18014398509481984; + "55" = 36028797018963968; + "56" = 72057594037927936; + "57" = 144115188075855872; + "58" = 288230376151711744; + "59" = 576460752303423488; + "60" = 1152921504606846976; + "61" = 2305843009213693952; + "62" = 4611686018427387904; + }; + intmin = (-9223372036854775807) - 1; + intmax = 9223372036854775807; + left = + a: b: + if a >= 64 then + # It's allowed to shift out all bits + 0 + else if a == 0 then + b + else if a < 0 then + right (-a) b else let - safeDiv = n: d: if d == 0 then 0 else n / d; - d = math.pow 2 n; + inv = 63 - a; + mask = if inv == 63 then intmax else lut.${toString inv} - 1; + masked = and b mask; + checker = if inv == 63 then intmin else lut.${toString inv}; + negate = (and b checker) != 0; + mult = if a == 63 then intmin else lut.${toString a}; + result = masked * mult; in - if x < 0 then not (safeDiv (not x) d) else safeDiv x d; + if !negate then result else intmin + result; - left = n: shift (-n); - - right = shift; + right = + a: b: + if a >= 64 then + 0 + else if a == 0 then + b + else if a < 0 then + left (-a) b + else + let + masked = and b intmax; + negate = b < 0; + result = masked / lut.${toString a}; + inv = 63 - a; + highest_bit = lut.${toString inv}; + in + if !negate then result else result + highest_bit; and = builtins.bitAnd; @@ -505,15 +596,6 @@ let clamp = a: b: c: max a (min b c); - - pow = - x: n: - if n == 0 then - 1 - else if bit.and n 1 != 0 then - x * pow (x * x) ((n - 1) / 2) - else - pow (x * x) (n / 2); }; parsers = @@ -951,8 +1033,6 @@ let lower = bit.mask 4 n; in "${builtins.substring upper 1 digits}${builtins.substring lower 1 digits}"; - in - let a = bit.mask 8 (bit.right 40 address.mac); b = bit.mask 8 (bit.right 32 address.mac); c = bit.mask 8 (bit.right 24 address.mac); @@ -1390,6 +1470,13 @@ in { lib = { - inherit net; + inherit + arithmetic + net + typechecks + parsers + implementations + bit + ; }; }