# Small IPv4 helpers (addresses are plain 32-bit integers inside Nix). # Parsing returns null on malformed input so callers can turn that into an # assertion message instead of an evaluation crash. { lib }: let pow2 = n: if n == 0 then 1 else 2 * pow2 (n - 1); in rec { ipv4 = rec { parse = s: let m = builtins.match "([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})" s; octets = map lib.toInt m; in if m == null || lib.any (o: o > 255) octets then null else lib.foldl' (acc: o: acc * 256 + o) 0 octets; # "10.0.0.5" or "10.0.0.0/24" -> { address (int); prefixLength }, or null. parseNet = s: let m = builtins.match "([0-9.]+)(/([0-9]{1,2}))?" s; address = if m == null then null else parse (builtins.elemAt m 0); prefixLength = if m == null || builtins.elemAt m 2 == null then 32 else lib.toInt (builtins.elemAt m 2); in if m == null || address == null || prefixLength > 32 then null else { inherit address prefixLength; }; format = n: lib.concatMapStringsSep "." (shift: toString (lib.mod (n / pow2 shift) 256)) [ 24 16 8 0 ]; netMask = prefixLength: 4294967295 - (pow2 (32 - prefixLength) - 1); # Network address (integer) of `address` under `prefixLength`. networkOf = address: prefixLength: builtins.bitAnd address (netMask prefixLength); # "192.168.10.1" /24 -> "192.168.10.0/24" cidr = address: prefixLength: "${format (networkOf (parse address) prefixLength)}/${toString prefixLength}"; inSubnet = { address, prefixLength }: ip: networkOf ip prefixLength == networkOf address prefixLength; # Whether two subnets share any address (one contains the other's network). overlaps = a: b: let p = lib.min a.prefixLength b.prefixLength; in networkOf a.address p == networkOf b.address p; }; }