{ config, lib, ... }: with lib; let v4PtrHierarchy = address: bits: reverseList (sublist 0 (bits / 8) (splitString "." address)); v6PtrHierarchy = address: bits: let separator = lists.findFirstIndex (hextet: hextet == "") null colonSplit; colonSplit = splitString ":" address; zeroFill = replicate (8 - length colonSplit + 1) "0000"; leftSplit = sublist 0 separator colonSplit; rightSplit = sublist (separator + 1) (length colonSplit - separator - 1) colonSplit; fullSplit = if separator != null then leftSplit ++ zeroFill ++ rightSplit else colonSplit; padded = map (hextet: strings.replicate (4 - stringLength hextet) "0" + hextet) fullSplit; in reverseList (sublist 0 (bits / 4) (flatten (map stringToCharacters padded))); matchPtrRecordName = { splitter, netAddress, netBits, targetAddress, targetBits }: let netSplit = splitter netAddress netBits; targetSplit = splitter targetAddress targetBits; netLength = length netSplit; lengthDelta = length targetSplit - netLength; withinNet = lengthDelta >= 0 && sublist lengthDelta netLength targetSplit == netSplit; throwMessage = "${targetAddress}/${toString targetBits} is not a subset of ${netAddress}/${toString netBits}"; recordHierarchy = sublist 0 lengthDelta targetSplit; recordName = if recordHierarchy != [ ] then concatStringsSep "." recordHierarchy else "@"; in throwIfNot withinNet throwMessage recordName; in { options.local.nets = with lib.types; mkOption { readOnly = true; type = attrsOf (submodule ({ config, ... }: { options = let v4config = config.v4; v6config = config.v6; in { hosts = mkOption { default = { }; type = attrsOf (submodule { options = { v4 = mkOption { default = null; type = nullOr (submodule ({ config, ... }: { options = { suffix = mkOption { type = str; }; address = mkOption { type = str; readOnly = true; }; cidr = mkOption { type = str; readOnly = true; }; single = mkOption { type = str; readOnly = true; }; }; config = { address = if v4config.bits == 0 then config.suffix else if v4config.bits == 32 then v4config.subnet else "${v4config.prefix}.${config.suffix}"; cidr = "${config.address}/${toString v4config.bits}"; single = "${config.address}/32"; }; })); }; v6 = mkOption { default = null; type = nullOr (submodule ({ config, ... }: { options = { suffix = mkOption { type = str; }; address = mkOption { type = str; readOnly = true; }; cidr = mkOption { type = str; readOnly = true; }; single = mkOption { type = str; readOnly = true; }; }; config = { address = let hextets = fragment: length (splitString ":" fragment); separator = if doubleColon then "::" else ":"; doubleColon = hextets v6config.prefix + hextets config.suffix < 8; joined = if v6config.bits == 128 then v6config.prefix else if v6config.bits == 0 then config.suffix else "${v6config.prefix}${separator}${config.suffix}"; in joined; cidr = "${config.address}/${toString v6config.bits}"; single = "${config.address}/128"; }; })); }; }; }); }; v4 = mkOption { default = null; type = nullOr (submodule ({ config, ... }: { options = { bits = mkOption { type = enum [ 0 8 16 24 32 ]; }; prefix = mkOption { type = str; }; subnet = mkOption { type = str; readOnly = true; }; cidr = mkOption { type = str; readOnly = true; }; ptrDomain = mkOption { type = str; readOnly = true; }; ptrRecordName = mkOption { type = functionTo (functionTo str); readOnly = true; }; }; config = { cidr = "${config.subnet}/${toString config.bits}"; subnet = if config.bits != 0 then config.prefix + strings.replicate (4 - config.bits / 8) ".0" else "0.0.0.0"; ptrDomain = concatStrings (map (x: x + ".") (v4PtrHierarchy config.subnet config.bits)) + "in-addr.arpa"; ptrRecordName = address: bits: matchPtrRecordName { splitter = v4PtrHierarchy; netBits = config.bits; netAddress = config.subnet; targetBits = bits; targetAddress = address; }; }; })); }; v6 = mkOption { default = null; type = nullOr (submodule ({ config, ... }: { options = { bits = mkOption { type = addCheck (ints.between 0 128) (b: mod b 4 == 0) // { description = "IPv6 subnet bits at nibble boundary"; }; }; prefix = mkOption { type = str; }; subnet = mkOption { type = str; readOnly = true; }; cidr = mkOption { type = str; readOnly = true; }; ptrDomain = mkOption { type = str; readOnly = true; }; ptrRecordName = mkOption { type = functionTo (functionTo str); readOnly = true; }; }; config = { cidr = "${config.subnet}/${toString config.bits}"; subnet = if config.bits == 128 || length (splitString "::" config.prefix) > 1 then config.prefix else "${config.prefix}::"; ptrDomain = concatStrings (map (x: x + ".") (v6PtrHierarchy config.subnet config.bits)) + "ip6.arpa"; ptrRecordName = address: bits: matchPtrRecordName { splitter = v6PtrHierarchy; netBits = config.bits; netAddress = config.subnet; targetBits = bits; targetAddress = address; }; }; })); }; }; })); }; }