summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Keller <tjk@tjkeller.xyz>2025-12-30 23:38:51 -0600
committerTim Keller <tjk@tjkeller.xyz>2025-12-30 23:38:51 -0600
commitd4db2f41db471ee25a03d9cdae37f55301b98f22 (patch)
treef83cef60d485837f490b9ede4fec7e18055b9bd8
parent39180d50fd978a3a2106ce1d060e847e14eae38f (diff)
downloadnixos-d4db2f41db471ee25a03d9cdae37f55301b98f22.tar.xz
nixos-d4db2f41db471ee25a03d9cdae37f55301b98f22.zip
unbound config in router profile is now services/router/dns.nix. unbound + dnsmasq config for local resolution and dhcp
-rw-r--r--archetypes/profiles/router/default.nix28
-rw-r--r--archetypes/profiles/router/unbound.nix70
-rw-r--r--nixos/default.nix3
-rw-r--r--nixos/services/router/dns.nix171
-rw-r--r--nixos/services/router/unbound-blocklist.nix (renamed from nixos/unbound-blocklist.nix)0
5 files changed, 195 insertions, 77 deletions
diff --git a/archetypes/profiles/router/default.nix b/archetypes/profiles/router/default.nix
index 0818a6b..646982b 100644
--- a/archetypes/profiles/router/default.nix
+++ b/archetypes/profiles/router/default.nix
@@ -1,12 +1,28 @@
{ lib, pkgs, ... }: let
mkRouter = lib.mkOverride 800;
- # TODO pass mkRouter
- #imports = [
- # ./unbound.nix
- #];
-
- nixosConfig = {};
+ nixosConfig = {
+ services.unbound = {
+ _blocklists = {
+ enable = true;
+ blocklists = {
+ hageziNSFW = [
+ "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/rpz/nsfw.txt"
+ "https://gitlab.com/hagezi/mirror/-/raw/main/dns-blocklists/rpz/nsfw.txt"
+ "https://codeberg.org/hagezi/mirror2/raw/branch/main/dns-blocklists/rpz/nsfw.txt"
+ ];
+ hageziPro = [
+ "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/rpz/pro.txt"
+ "https://gitlab.com/hagezi/mirror/-/raw/main/dns-blocklists/rpz/pro.txt"
+ "https://codeberg.org/hagezi/mirror2/raw/branch/main/dns-blocklists/rpz/pro.txt"
+ ];
+ };
+ };
+ };
+ services._router.dnsDhcpConfig = {
+ enable = mkRouter true;
+ };
+ };
homeConfig = {};
in {
diff --git a/archetypes/profiles/router/unbound.nix b/archetypes/profiles/router/unbound.nix
deleted file mode 100644
index 1322193..0000000
--- a/archetypes/profiles/router/unbound.nix
+++ /dev/null
@@ -1,70 +0,0 @@
-{
- services.unbound = {
- enable = true;
- _blocklists = {
- enable = true;
- blocklists = {
- hageziNSFW = [
- "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/rpz/nsfw.txt"
- "https://gitlab.com/hagezi/mirror/-/raw/main/dns-blocklists/rpz/nsfw.txt"
- "https://codeberg.org/hagezi/mirror2/raw/branch/main/dns-blocklists/rpz/nsfw.txt"
- ];
- hageziPro = [
- "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/rpz/pro.txt"
- "https://gitlab.com/hagezi/mirror/-/raw/main/dns-blocklists/rpz/pro.txt"
- "https://codeberg.org/hagezi/mirror2/raw/branch/main/dns-blocklists/rpz/pro.txt"
- ];
- };
- };
- settings = {
- server = {
- # Listen on all interfaces (or specify specific IPs)
- interface = [ "0.0.0.0" "::0" ];
-
- # Allow queries from local networks
- access-control = [
- "127.0.0.0/8 allow"
- "192.168.0.0/16 allow"
- "10.0.0.0/8 allow"
- "172.16.0.0/12 allow"
- ];
-
- ## Enable DNSSEC validation
- #auto-trust-anchor-file: "/var/unbound/root.key"
-
- # Harden against out-of-zone data
- harden-referral-path = true;
- harden-dnssec-stripped = true;
-
- # Privacy options
- qname-minimisation = true;
-
- # Cache settings
- cache-min-ttl = 300;
- cache-max-ttl = 86400;
-
- # Hide version
- hide-identity = true;
- hide-version = true;
-
- # Based on recommended settings in https://docs.pi-hole.net/guides/dns/unbound/#configure-unbound
- harden-glue = true;
- use-caps-for-id = false;
- prefetch = true;
- edns-buffer-size = 1232;
- };
- # Forward unknown to public resolver via DoT
- forward-zone = [
- {
- name = ".";
- forward-addr = [
- "9.9.9.9#dns.quad9.net"
- "149.112.112.112#dns.quad9.net"
- ];
- forward-tls-upstream = true; # Encrypted DNS
- }
- ];
- remote-control.control-enable = true;
- };
- };
-}
diff --git a/nixos/default.nix b/nixos/default.nix
index 4b87741..76b5d6f 100644
--- a/nixos/default.nix
+++ b/nixos/default.nix
@@ -7,6 +7,8 @@
./services/cgit.nix
./services/gitea.nix
./services/searxng.nix
+ ./services/router/dns.nix
+ ./services/router/unbound-blocklist.nix
./bootloader.nix
./doas.nix
@@ -23,7 +25,6 @@
./ssh.nix
./sudo.nix
./suspend.nix
- ./unbound-blocklist.nix
./zshenv.nix
];
}
diff --git a/nixos/services/router/dns.nix b/nixos/services/router/dns.nix
new file mode 100644
index 0000000..2772a27
--- /dev/null
+++ b/nixos/services/router/dns.nix
@@ -0,0 +1,171 @@
+{ config, lib, ... }: let
+ #cfg = config.services._routing;
+ cfg = {
+ localDomain = "home.lan";
+ defaultGateway = "127.0.0.1";
+ dhcp = {
+ rangeStart = "192.168.1.50";
+ rangeEnd = "192.168.1.150";
+ subnetMask = "255.255.255.0";
+ leaseTime = "12h";
+ };
+ log = {
+ dhcp = false;
+ dns = false;
+ };
+ };
+in {
+ options.services._router.dnsDhcpConfig = {
+ enable = lib.mkEnableOption "enable pre-configured unbound(outbound) + dnsmasq(local) dns(+dhcp) server";
+ # TODO
+ defaultGateway = lib.mkOption {
+ };
+ localIPSubnet = lib.mkOption {
+ };
+ dhcp = {
+ rangeStart = lib.mkOption {
+ };
+ rangeEnd = lib.mkOption {
+ };
+ rangeSubnetMask = lib.mkOption {
+ };
+ leaseTime = lib.mkOption {
+ };
+ staticLeases = lib.mkOption {
+ };
+ };
+ localDomain = lib.mkOption {
+ type = lib.types.str;
+ default = "home.lan";
+ description = "";
+ };
+ #ipv6 = lib.mkEnableOption "enable ipv6"; # TODO
+ };
+
+ config = lib.mkIf cfg.enable {
+ # Unbound as a recursive DNS resolver.
+ # Preferred for its security/privacy features + performance
+ services.unbound = {
+ enable = true;
+ settings = {
+ server = {
+ # Listen on all interfaces (or specify specific IPs)
+ interface = [ "0.0.0.0" ]; # ++ lib.optionals cfg.ipv6 [ "::0" ];
+
+ # Allow queries from local networks
+ access-control = [
+ "127.0.0.0/8 allow"
+ "192.168.0.0/16 allow"
+ "10.0.0.0/8 allow"
+ "172.16.0.0/12 allow"
+ ];
+
+ # Cache settings
+ cache-min-ttl = 300; # 5 min
+ cache-max-ttl = 60 * 60 * 24; # 1 day
+
+ # Prefetch DNS records for better performance
+ prefetch-key = true;
+ prefetch = true;
+
+ # Enable DNSSEC validation (signed DNS, prevents man-in-the-middle attacks)
+ # `auto-trust-anchor-file` is set by default to "/var/unbound/root.key"
+ trust-anchor-file = ''""'';
+ domain-insecure = cfg.localDomain; # Local domain is not DNSSEC-signed
+ harden-below-nxdomain = true; # Protect against non-existent domain response attacks
+ harden-glue = true; # Protect against incorrect DNS glue record attacks
+ harden-dnssec-stripped = true; # Ensures that DNSSEC signatures are not stripped from DNS responses
+
+ # Allow queries to localhost for dnsmasq local domain resolution
+ do-not-query-localhost = false;
+
+ # Privacy / Security
+ harden-referral-path = true; # Prevents receiving data from servers that are outside the zone in the DNS referral chain
+ qname-minimisation = true; # Minimizes the amount of information exposed in DNS queries by only sending the essential parts of the query
+ hide-identity = true; # Hide software identity in response
+ hide-version = true; # Hide unbound version in response
+ use-caps-for-id = false; # Disables using uppercase characters in the DNS transaction ID, for compatibility
+ edns-buffer-size = 1232; # Sets the EDNS (Extension mechanisms for DNS) buffer size to 1232 bytes, which is the default for many DNS resolvers
+
+ # Logging
+ #verbosity = 3;
+ #log-queries = true;
+ #log-replies = true;
+ #log-local-actions = true;
+ };
+ # Forward unknown to public resolver via DoT
+ forward-zone = [
+ # Local DNS: forward to dnsmasq
+ {
+ name = ''"${cfg.localDomain}."''; # TODO mk config
+ forward-addr = "127.0.0.1@5353"; # TODO mk config
+ }
+ # Local reverse DNS: forward reverse lookups (PTR records)
+ {
+ name = ''"1.168.192.in-addr.arpa"''; # NOTE: 192.168.1.0 -> 1.168.192
+ forward-addr = "127.0.0.1@5353";
+ }
+ # Upstream DNS
+ {
+ name = ''"."'';
+ forward-addr = [
+ "9.9.9.9#dns.quad9.net"
+ "149.112.112.112#dns.quad9.net"
+ ];
+ forward-tls-upstream = true; # Encrypted DNS
+ }
+ ];
+ remote-control.control-enable = true;
+ };
+ };
+
+ # Configure dnsmasq for dhcp and local hostname resolution
+ services.dnsmasq = {
+ enable = true;
+ settings = let
+ mkDHCPRange = ipRangeStart: ipRangeEnd: subnetMask: leaseTime: "${ipRangeStart},${ipRangeEnd},${subnetMask},${leaseTime}";
+ mkDHCPOption = option: value: "option:${option},${value}";
+ mkDHCPStaticLease = macAddress: hostname: staticIp: "${macAddress},${hostname},${staticIp},infinite";
+ #dhcpStaticLeases = builtins.map ();
+ in {
+ # General
+ no-resolv = true; # Do not read /etc/resolv.conf, resolve only the LAN
+ no-poll = true; # Do not poll /etc/resolv.conf for changes
+ # TODO config local domain
+ local = "/${cfg.localDomain}/"; # Use local-only for the defined domain (prevents upstream leaks)
+ domain = cfg.localDomain; # Define the local domain name
+ expand-hosts = true; # Create both fully-qualified and short-name entries from DHCP hostnames
+
+ # DNS Server
+ port = 5353; # Use port 5353 for DNS server since unbound is the main DNS resolver
+
+ # DHCP Server
+ # TODO config
+ #dhcp-range = mkDHCPRange "192.168.1.50" "192.168.1.150" "255.255.255.0" "12h"; # Enable DHCP on the LAN interface
+ dhcp-range = with cfg.dhcp; mkDHCPRange rangeStart rangeEnd subnetMask leaseTime; # Enable DHCP on the LAN interface
+
+ # TODO config
+ #dhcp-host = [ mkDHCPStaticLease ... ]; # Setup static leases
+ #dhcp-host = dhcpStaticLeases; # Setup static leases
+
+ dhcp-option = [
+ (mkDHCPOption "router" cfg.defaultGateway) # Set default gateway for clients
+ #(mkDHCPOption "ntp-server" cfg.defaultGateway) # Set ntp server for clients
+ (mkDHCPOption "dns-server" cfg.defaultGateway) # Set dns server for clients
+ (mkDHCPOption "domain-search" cfg.localDomain) # Add search rule to clients so they can resolve hostnames w/o the local domain suffix
+ ];
+
+ # Logging
+ #log-dhcp = true; # Log DHCP events
+ #log-queries = true; # Log DNS queries
+
+ # Cache
+ cache-size = 1000; # Small cache limit is fine since Unbound does the heavy caching
+ };
+ };
+
+ # Search localDomain so host can resolve short names
+ # This is eq. to dnsmasq's dhcp-option "domain-search" for clients, it just adds a search rule to resolv.conf
+ networking.search = [ cfg.localDomain ];
+ };
+}
diff --git a/nixos/unbound-blocklist.nix b/nixos/services/router/unbound-blocklist.nix
index 153f2c0..153f2c0 100644
--- a/nixos/unbound-blocklist.nix
+++ b/nixos/services/router/unbound-blocklist.nix