{ config, lib, pkgs, ... }: with lib; let hostname = config.instance.hostname; domain-name = config.fudo.hosts.${hostname}.domain; site-name = config.fudo.hosts.${hostname}.site; zone-name = config.fudo.domains.${domain-name}.zone; site = config.fudo.sites.${site-name}; cfg = config.fudo.services.local-network; resolverOpts = { options = { ip = mkOption { type = str; description = "IP address of the upstream recursive resolver."; default = "1.1.1.1"; }; port = mkOption { type = port; description = "Port of DNS server on the recursive resolver."; default = 53; }; }; }; in { options.fudo.services.local-network = with types; { enable = mkEnableOption "Enable local network server."; internal-interfaces = mkOption { type = listOf str; description = '' Interfaces on which to: * Accept NAT traffic * Serve DNS * Serve DHCP ''; }; external-interface = mkOption { type = str; description = "Interface facing the larger internet."; example = "extif0"; }; resolvers = mkOption { type = listOf (submodule resolverOpts); description = "List of upstream DNS servers."; default = [ { ip = "1.1.1.1"; } { ip = "1.0.0.1"; } { ip = "9.9.9.9"; } { ip = "149.112.112.112"; } ]; }; dns-filter-proxy = { enable = mkEnableOption "Enable DNS filter."; http-listen-port = mkOption { type = port; description = "Port on localhost on which to listen for HTTP requests."; default = 4080; }; http-host-alias = mkOption { type = str; description = "Host alias for the DNS filter server."; default = "dns-filter"; }; dns-listen-port = mkOption { type = port; description = "Port on localhost on which to listen for DNS requests."; default = 4053; }; upstream-dns = mkOption { type = listOf str; description = "List of upstream DNS-over-HTTPS endpoints."; default = [ "https://1.1.1.1/dns-query" "https://1.0.0.1/dns-query" # These 11 addrs send the network, so the response can prefer closer answers "https://9.9.9.11/dns-query" "https://149.112.112.11/dns-query" "https://2620:fe::11/dns-query" "https://2620:fe::fe:11/dns-query" ]; }; }; }; config = mkIf (site.local-gateway != null) (let host-ipv4 = pkgs.lib.network.host-ipv4 config; gateway-host = site.local-gateway; nameserver-host = gateway-host; gateway-ip = host-ipv4 gateway-host; nameserver-ip = host-ipv4 gateway-host; is-gateway = hostname == gateway-host; agp = cfg.dns-filter-proxy; fqdn = hostname: "${hostname}.${domain-name}."; in { networking = { nat = mkIf is-gateway { enable = true; externalInterface = cfg.external-interface; internalInterfaces = cfg.internal-interfaces; }; nameservers = [ nameserver-ip ]; firewall = if is-gateway then { enable = true; trustedInterfaces = cfg.internal-interfaces; } else { enable = false; }; }; fudo = { adguard-dns-proxy = mkIf agp.enable { enable = true; http = { listen-ip = "127.0.0.1"; listen-port = agp.http-listen-port; }; dns = { listen-ips = [ "127.0.0.1" ]; listen-port = agp.dns-listen-port; }; local-domain-name = domain-name; }; zones.${zone-name} = { aliases = { ${agp.http-host-alias} = optionalAttrs (agp.enable) (fqdn gateway-host); ns = (fqdn nameserver-host); gw = (fqdn gateway-host); }; hosts = { gateway.ipv4-address = gateway-ip; nameserver.ipv4-address = nameserver-ip; }; nameservers = [ "nameserver" ]; srv-records = { tcp.domain = [{ host = "nameserver.${domain-name}"; port = 53; }]; udp.domain = [{ host = "nameserver.${domain-name}"; port = 53; }]; }; }; local-network = { enable = is-gateway; domain = domain-name; dns-servers = [ nameserver-ip ]; gateway = gateway-ip; dhcp-interfaces = cfg.internal-interfaces; dns-listen-ips = optionals is-gateway [ nameserver-ip "127.0.0.1" "127.0.1.1" ]; dns-listen-ipv6s = optionals (is-gateway && config.networking.enableIPv6) [ "::1" ]; recursive-resolver = if agp.enable then { host = "127.0.0.1"; port = agp.dns-listen-port; } else { host = cfg.resolver.ip; port = cfg.resolver.port; }; network = site.network; dhcp-dynamic-network = site.dynamic-network; search-domains = [ domain-name "fudo.org" ]; enable-reverse-mappings = true; zone-definition = config.fudo.zones.${zone-name}; }; }; services.nginx = mkIf agp.enable { enable = true; recommendedGzipSettings = true; recommendedOptimisation = true; recommendedProxySettings = true; virtualHosts = { "${agp.http-host-alias}.${domain-name}" = { locations."/".proxyPass = "http://127.0.0.1:${toString agp.http-listen-port}"; }; }; }; }); }