253 lines
7.2 KiB
Nix
253 lines
7.2 KiB
Nix
{ lib, config, pkgs, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
cfg = config.fudo.local-network;
|
|
|
|
ip = import ../../lib/ip.nix {};
|
|
dns = import ../../lib/dns.nix {};
|
|
|
|
join-lines = concatStringsSep "\n";
|
|
|
|
hostOpts = { hostname, ... }: {
|
|
options = {
|
|
ip-address = mkOption {
|
|
type = types.str;
|
|
description = ''
|
|
The V4 IP of a given host, if any.
|
|
'';
|
|
};
|
|
|
|
mac-address = mkOption {
|
|
type = with types; nullOr types.str;
|
|
description = ''
|
|
The MAC address of a given host, if desired for IP reservation.
|
|
'';
|
|
default = null;
|
|
};
|
|
|
|
ssh-fingerprints = mkOption {
|
|
type = with types; listOf str;
|
|
description = "A list of DNS SSHFP records for this host.";
|
|
default = [];
|
|
};
|
|
};
|
|
};
|
|
|
|
traceout = out: builtins.trace out out;
|
|
|
|
in {
|
|
|
|
options.fudo.local-network = {
|
|
|
|
enable = mkEnableOption "Enable local network configuration (DHCP & DNS).";
|
|
|
|
hosts = mkOption {
|
|
type = with types; attrsOf (submodule hostOpts);
|
|
default = {};
|
|
description = "A map of hostname => { host_attributes }.";
|
|
};
|
|
|
|
domain = mkOption {
|
|
type = types.str;
|
|
description = "The domain to use for the local network.";
|
|
};
|
|
|
|
dns-servers = mkOption {
|
|
type = with types; listOf str;
|
|
description = "A list of domain name server to use for the local network.";
|
|
};
|
|
|
|
dhcp-interfaces = mkOption {
|
|
type = with types; listOf str;
|
|
description = "A list of interfaces on which to serve DHCP.";
|
|
};
|
|
|
|
dns-serve-ips = mkOption {
|
|
type = with types; listOf str;
|
|
description = "A list of IPs on which to server DNS queries.";
|
|
};
|
|
|
|
gateway = mkOption {
|
|
type = types.str;
|
|
description = "The gateway to use for the local network.";
|
|
};
|
|
|
|
aliases = mkOption {
|
|
type = with types; attrsOf str;
|
|
default = {};
|
|
description = "A mapping of host-alias => hostname to use on the local network.";
|
|
};
|
|
|
|
network = mkOption {
|
|
type = types.str;
|
|
description = "Network to treat as local.";
|
|
};
|
|
|
|
enable-reverse-mappings = mkOption {
|
|
type = types.bool;
|
|
description = "Genereate PTR reverse lookup records.";
|
|
default = false;
|
|
};
|
|
|
|
dhcp-dynamic-network = mkOption {
|
|
type = types.str;
|
|
description = ''
|
|
The network from which to dynamically allocate IPs via DHCP.
|
|
|
|
Must be a subnet of <network>.
|
|
'';
|
|
};
|
|
|
|
recursive-resolver = mkOption {
|
|
type = types.str;
|
|
description = "DNS nameserver to use for recursive resolution.";
|
|
};
|
|
|
|
server-ip = mkOption {
|
|
type = types.str;
|
|
description = "IP of the DNS server.";
|
|
};
|
|
|
|
extra-dns-records = mkOption {
|
|
type = with types; listOf str;
|
|
description = "Records to be inserted verbatim into the DNS zone.";
|
|
example = ["some-host IN CNAME other-host"];
|
|
default = [];
|
|
};
|
|
|
|
srv-records = mkOption {
|
|
type = dns.srvRecords;
|
|
description = "Map of traffic type to srv records.";
|
|
default = {};
|
|
example = {
|
|
tcp = {
|
|
kerberos = {
|
|
port = 88;
|
|
host = "auth-host.my-domain.com";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
search-domains = mkOption {
|
|
type = with types; listOf str;
|
|
description = "A list of domains to search for DNS names.";
|
|
example = ["my-domain.com" "other-domain.com"];
|
|
default = [];
|
|
};
|
|
|
|
# TODO: srv records
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
services.dhcpd4 = {
|
|
enable = true;
|
|
|
|
machines = mapAttrsToList (hostname: hostOpts: {
|
|
ethernetAddress = hostOpts.mac-address;
|
|
hostName = hostname;
|
|
ipAddress = hostOpts.ip-address;
|
|
}) (filterAttrs (host: hostOpts: hostOpts.mac-address != null) cfg.hosts);
|
|
|
|
interfaces = cfg.dhcp-interfaces;
|
|
|
|
extraConfig = ''
|
|
subnet ${ip.getNetworkBase cfg.network} netmask ${ip.maskFromV32Network cfg.network} {
|
|
authoritative;
|
|
option subnet-mask ${ip.maskFromV32Network cfg.network};
|
|
option broadcast-address ${ip.networkMaxIp cfg.network};
|
|
option routers ${cfg.gateway};
|
|
option domain-name-servers ${concatStringsSep " " cfg.dns-servers};
|
|
option domain-name "${cfg.domain}";
|
|
option domain-search ${join-lines (map (dom: "\"${dom}\"") ([cfg.domain] ++ cfg.search-domains))};
|
|
range ${ip.networkMinIp cfg.dhcp-dynamic-network} ${ip.networkMaxButOneIp cfg.dhcp-dynamic-network};
|
|
}
|
|
'';
|
|
};
|
|
|
|
services.bind = let
|
|
blockHostsToZone = block: hosts-data: {
|
|
master = true;
|
|
name = "${block}.in-addr.arpa";
|
|
file = let
|
|
# We should add these...but need a domain to assign them to.
|
|
# ip-last-el = ip: toInt (last (splitString "." ip));
|
|
# used-els = map (host-data: ip-last-el host-data.ip-address) hosts-data;
|
|
# unused-els = subtractLists used-els (map toString (range 1 255));
|
|
|
|
in pkgs.writeText "db.${block}-zone" ''
|
|
$ORIGIN ${block}.in-addr.arpa.
|
|
$TTL 1h
|
|
|
|
@ IN SOA ns1.${cfg.domain}. hostmaster.${cfg.domain}. (
|
|
${toString builtins.currentTime}
|
|
1800
|
|
900
|
|
604800
|
|
1800)
|
|
|
|
@ IN NS ns1.${cfg.domain}.
|
|
|
|
${join-lines (map hostPtrRecord hosts-data)}
|
|
'';
|
|
};
|
|
|
|
ipToBlock = ip: concatStringsSep "." (reverseList (take 3 (splitString "." ip)));
|
|
compactHosts = mapAttrsToList (host: data: data // { host = host; }) cfg.hosts;
|
|
hostsByBlock = groupBy (host-data: ipToBlock host-data.ip-address) compactHosts;
|
|
hostPtrRecord = host-data:
|
|
"${last (splitString "." host-data.ip-address)} IN PTR ${host-data.host}.${cfg.domain}.";
|
|
|
|
blockZones = mapAttrsToList blockHostsToZone hostsByBlock;
|
|
|
|
hostARecord = host: data: "${host} IN A ${data.ip-address}";
|
|
hostSshFpRecords = host: data: join-lines (map (sshfp: "${host} IN SSHFP ${sshfp}") data.ssh-fingerprints);
|
|
cnameRecord = alias: host: "${alias} IN CNAME ${host}";
|
|
|
|
in {
|
|
enable = true;
|
|
cacheNetworks = [ cfg.network "localhost" "localnets" ];
|
|
forwarders = [ cfg.recursive-resolver ];
|
|
listenOn = cfg.dns-serve-ips;
|
|
extraOptions = concatStringsSep "\n" [
|
|
"dnssec-enable yes;"
|
|
"dnssec-validation yes;"
|
|
"auth-nxdomain no;"
|
|
"recursion yes;"
|
|
"allow-recursion { any; };"
|
|
];
|
|
zones = [
|
|
{
|
|
master = true;
|
|
name = cfg.domain;
|
|
file = pkgs.writeText "${cfg.domain}-zone" ''
|
|
@ IN SOA ns1.${cfg.domain}. hostmaster.${cfg.domain}. (
|
|
${toString builtins.currentTime}
|
|
5m
|
|
2m
|
|
6w
|
|
5m)
|
|
|
|
$TTL 1h
|
|
|
|
@ IN NS ns1.${cfg.domain}.
|
|
|
|
$ORIGIN ${cfg.domain}.
|
|
|
|
$TTL 30m
|
|
|
|
ns1 IN A ${cfg.server-ip}
|
|
${join-lines (mapAttrsToList hostARecord cfg.hosts)}
|
|
${join-lines (mapAttrsToList hostSshFpRecords cfg.hosts)}
|
|
${join-lines (mapAttrsToList cnameRecord cfg.aliases)}
|
|
${join-lines cfg.extra-dns-records}
|
|
${dns.srvRecordsToBindZone cfg.srv-records}
|
|
'';
|
|
}
|
|
] ++ blockZones;
|
|
};
|
|
};
|
|
}
|