summaryrefslogtreecommitdiff
path: root/sys/ns/rr.nix
diff options
context:
space:
mode:
authorAlejandro Soto <alejandro@34project.org>2025-04-19 10:48:15 -0600
committerAlejandro Soto <alejandro@34project.org>2025-04-24 14:27:38 -0600
commit1039d1d47a53be0c814a03608e94a9d0e8f4405b (patch)
treea6cf802896f0ad41742354499df8063d9065eb02 /sys/ns/rr.nix
parentf4ed93ff7d01e659960b9cd3dc5bc3d6d6e27d01 (diff)
sys/ns: implement automatic PTR zones
Diffstat (limited to 'sys/ns/rr.nix')
-rw-r--r--sys/ns/rr.nix180
1 files changed, 156 insertions, 24 deletions
diff --git a/sys/ns/rr.nix b/sys/ns/rr.nix
index a80eaf4..e4fbe12 100644
--- a/sys/ns/rr.nix
+++ b/sys/ns/rr.nix
@@ -1,5 +1,7 @@
{ config, lib, options, pkgs, ... }:
with lib; let
+ inherit (config.local) nets;
+
cfg = config.local.ns;
globalConfig = config;
@@ -25,6 +27,7 @@ with lib; let
"CNAME"
"MX"
"NS"
+ "PTR"
"SOA"
"SRV"
"TXT"
@@ -37,21 +40,63 @@ in
readOnly = true;
};
+ ptr = mkOption {
+ default = { };
+
+ type = with lib.types; attrsOf (submodule {
+ options = {
+ v4 = {
+ zone = mkOption {
+ type = nullOr str;
+ default = null;
+ };
+
+ targets = mkOption {
+ type = attrsOf str;
+ default = { };
+ };
+ };
+
+ v6 = {
+ zone = mkOption {
+ type = nullOr str;
+ default = null;
+ };
+
+ targets = mkOption {
+ type = attrsOf str;
+ default = { };
+ };
+ };
+ };
+ });
+ };
+
zones = mkOption {
default = { };
type = with lib.types; attrsOf (submodule ({ config, name, ... }:
let
- nameOption = extra: mkOption (extra // {
- type = domainRefType;
-
- apply = value:
- if value == "@"
- then "${name}."
- else if ! hasSuffix "." value
- then "${value}.${name}."
- else value;
- });
+ nameOption = args@{ defaultZone ? "${name}.", permitRelative ? true, ... }:
+ mkOption (removeAttrs args [ "defaultZone" "permitRelative" ] // {
+ type = domainRefType;
+
+ apply = value:
+ let
+ zone =
+ throwIfNot
+ (hasSuffix "." defaultZone)
+ "zone expression '${defaultZone}' must be absolute, not relative"
+ defaultZone;
+ in
+ if value == "@"
+ then zone
+ else if hasSuffix "." value
+ then value
+ else if permitRelative
+ then "${value}.${zone}"
+ else throw "zone expression '${value}' in zone '${zone}' must be absolute, not relative";
+ });
rrType = options: mkOption {
default = [ ];
@@ -90,6 +135,23 @@ in
default = 3600;
};
+ ptrName = mkOption {
+ type = nullOr str;
+ default = null;
+ };
+
+ defaultPtr = {
+ v4 = mkOption {
+ type = nullOr str;
+ default = null;
+ };
+
+ v6 = mkOption {
+ type = nullOr str;
+ default = null;
+ };
+ };
+
nsdConfig = mkOption {
type = attrsOf unspecified;
default = { };
@@ -134,12 +196,17 @@ in
};
soa = {
+ authorityZone = nameOption { default = "@"; permitRelative = false; };
+
ttl = mkOption {
type = int;
default = config.defaultTTL;
};
- primary = nameOption { default = "ns1"; };
+ primary = nameOption {
+ default = "ns1";
+ defaultZone = config.soa.authorityZone;
+ };
hostmaster = mkOption {
type = emailType;
@@ -150,7 +217,7 @@ in
split = splitString "@" address;
user = head split;
- domain = if length split == 2 then head (tail split) else name;
+ domain = if length split == 2 then head (tail split) else removeSuffix "." config.soa.authorityZone;
in
if hasSuffix "." address
then address
@@ -187,12 +254,22 @@ in
ipv4 = mkOption {
type = str;
};
+
+ ptr = mkOption {
+ type = nullOr str;
+ default = config.defaultPtr.v4;
+ };
};
aaaa = rrType {
ipv6 = mkOption {
type = str;
};
+
+ ptr = mkOption {
+ type = nullOr str;
+ default = config.defaultPtr.v6;
+ };
};
cname = rrType {
@@ -211,6 +288,10 @@ in
host = nameOption { };
};
+ ptr = rrType {
+ target = nameOption { };
+ };
+
srv = rrType {
host = nameOption { };
@@ -304,6 +385,12 @@ in
})
(rrConfig {
+ rrs = config.ptr;
+ type = "PTR";
+ format = rr: [ rr.target ];
+ })
+
+ (rrConfig {
rrs = config.srv;
type = "SRV";
@@ -344,24 +431,69 @@ in
message = "Update serials for these zones (null-serial hash mismatch): ${concatStringsSep ", " badZones}";
}
)
- ];
+ ] ++ flatten (mapAttrsToList
+ (name: ptr: [
+ {
+ assertion = ptr.v4.targets != { } -> ptr.v4.zone != null;
+ message = "undefined v4 PTR net '${name}': ${concatStringsSep ", " (attrValues ptr.v4.targets)}";
+ }
+ {
+ assertion = ptr.v6.targets != { } -> ptr.v6.zone != null;
+ message = "undefined v6 PTR net '${name}': ${concatStringsSep ", " (attrValues ptr.v6.targets)}";
+ }
+ ])
+ cfg.ptr);
lib.local.zoneSerialUpdates =
let
- zoneChecks = mapAttrs zoneHashCheck cfg.zones;
- updateInfo = check: {
+ ptrChecks = filterAttrs (_: check: check.zone.ptrName != null) allZoneChecks;
+ zoneChecks = filterAttrs (_: check: check.zone.ptrName == null) allZoneChecks;
+ allZoneChecks = filterAttrs (_: check: check.needsUpdate) (mapAttrs zoneHashCheck cfg.zones);
+
+ updateInfo = name: check: {
+ inherit name;
inherit (check) expected;
inherit (check.zone.soa) serial;
};
in
- mapAttrs (_: updateInfo) (filterAttrs (_: check: check.needsUpdate) zoneChecks);
-
- local.ns.nullSerialZones =
- mapAttrs
- (_: zone: mkMerge [
- (filterAttrs (name: _: elem name ([ "defaultTTL" ] ++ map toLower rrTypes)) zone)
- { soa.serial = mkOverride 0 0; }
- ])
- cfg.zones;
+ {
+ ptr = mapAttrs (_: check: updateInfo check.zone.ptrName check) ptrChecks;
+ zones = mapAttrs updateInfo zoneChecks;
+ };
+
+ local.ns = {
+ nullSerialZones =
+ let
+ defaultAttrs = [ "defaultTTL" "defaultPtr" "ptrName" ];
+ filteredAttrs = defaultAttrs ++ map toLower rrTypes;
+ in
+ mapAttrs
+ (_: zone: mkMerge [
+ (filterAttrs (name: _: elem name filteredAttrs) zone)
+ { soa.serial = mkOverride 0 0; }
+ ])
+ cfg.zones;
+
+ ptr =
+ let
+ zonePtrs = zone:
+ let
+ v4Ptrs = map
+ (a: {
+ ${a.ptr}.v4.targets.${nets.${a.ptr}.v4.ptrRecordName a.ipv4 32} = a.name;
+ })
+ (filter (a: a.ptr != null) zone.a);
+
+ v6Ptrs = map
+ (aaaa: {
+ ${aaaa.ptr}.v6.targets.${nets.${aaaa.ptr}.v6.ptrRecordName aaaa.ipv6 128} = aaaa.name;
+ })
+ (filter (aaaa: aaaa.ptr != null) zone.aaaa);
+ in
+ v4Ptrs ++ v6Ptrs;
+
+ in
+ mkMerge (flatten (mapAttrsToList (_: zonePtrs) cfg.zones));
+ };
};
}