{ config, inputs, host, ... }:
let
  inherit (config.services) unbound;
  inherit (config.users) users;
in
{
  networking.resolvconf.useLocalResolver = true;
  services.resolved.enable = false;
  services.unbound = {
    enable = true;
    # DOC: https://calomel.org/unbound_dns.html
    settings = {
      remote-control = {
        control-enable = true;
        control-interface = "/run/unbound/unbound.socket";
      };
      server = {
        log-queries = false;
        verbosity = 1;
        interface = [
          "127.0.0.1"
          "::1"
        ];
        access-control = [
          "0.0.0.0/0 refuse"
          "::0/0 refuse"
          "127.0.0.0/8 allow"
          "::1 allow"
        ];
        prefer-ip4 = !config.networking.enableIPv6;
        port = 53;

        # The file which contains the listing of primary root DNS servers.
        # To be updated once every six months.
        root-hints = "/var/lib/unbound/named.root";

        # Do no answer id.server and hostname.bind queries.
        hide-identity = true;
        # Do not answer version.server and version.bind queries.
        hide-version = true;

        # Will trust glue only if it is within the servers authority.
        # Harden against out of zone rrsets, to avoid spoofing attempts.
        # Hardening queries multiple name servers for the same data to make
        # spoofing significantly harder and does not mandate dnssec.
        harden-glue = true;

        # Require DNSSEC data for trust-anchored zones, if such data is absent, the
        # zone becomes  bogus.  Harden against receiving dnssec-stripped data. If you
        # turn it off, failing to validate dnskey data for a trustanchor will trigger
        # insecure mode for that zone (like without a trustanchor).  Default on,
        # which insists on dnssec data for trust-anchored zones.
        harden-dnssec-stripped = true;

        # Use 0x20-encoded random bits in the query to foil spoof attempts.
        # http://tools.ietf.org/html/draft-vixie-dnsext-dns0x20-00
        #
        # When Unbound sends a query to a remote server it sends the hostname
        # string in random upper and lower characters. The remote server must
        # resolve the hostname as if all the characters were lower case. The remote
        # server must then send the query back to Unbound in the same random upper
        # and lower characters that Unbound sent. If the characters of the hostname
        # in the response are in the same format as the query then the dns-0x20
        # check is satisfied.
        # Attackers hoping to poison a Unbound DNS cache must therefore guess the
        # mixed-case encoding of the query and the timing of the return dns answer
        # in addition to all other fields required in a DNS poisoning attack.
        # dns-0x20 increases the difficulty of the attack significantly.
        #
        # It may result in maybe 0.4% of domains getting no answers
        # due to no support on the authoritative server side
        use-caps-for-id = true;

        #cache-min-ttl = 3600;
        cache-max-ttl = 86400;

        # Perform prefetching of close to expired message cache entries.  If a client
        # requests the dns lookup and the TTL of the cached hostname is going to
        # expire in less than 10% of its TTL, unbound will (1st) return the IP of the
        # host to the client and (2nd) pre-fetch the DNS request from the remote DNS server.
        # This method has been shown to increase the amount of cached hits by
        # local clients by 10% on average.
        prefetch = true;

        # Number of threads to create. 1 disables threading.
        # This should equal the number of CPU cores in the host.
        num-threads = host.CPUs;

        # The number of slabs to use for cache and must be a power of 2 times the
        # number of num-threads set above. more slabs reduce lock contention,
        # but fragment memory usage.
        msg-cache-slabs = 8;
        rrset-cache-slabs = 8;
        infra-cache-slabs = 8;
        key-cache-slabs = 8;

        # Increase the memory size of the cache. Use roughly twice as much rrset cache
        # memory as you use msg cache memory. Due to malloc overhead, the total memory
        # usage is likely to rise to double (or 2.5x) the total cache memory.
        rrset-cache-size = "32m";
        msg-cache-size = "16m";

        # buffer size for UDP port 53 incoming (SO_RCVBUF socket option). This sets
        # the kernel buffer larger so that no messages are lost in spikes in the traffic.
        so-rcvbuf = "1m";

        # Enforce privacy of these addresses. Strips them away from answers.
        # It may cause DNSSEC validation to additionally mark it as bogus.
        # Protects against 'DNS Rebinding' (uses browser as network proxy).
        #  Only 'private-domain' and 'local-data' names are allowed
        # to have these private addresses. No default.
        private-address = [
          "192.168.0.0/16"
          "172.16.0.0/12"
          "10.0.0.0/8"
        ];

        # Allow the domain (and its subdomains) to contain private addresses.
        # local-data statements are allowed to contain private addresses too.
        #private-domain = "home.lan";

        # If nonzero, unwanted replies are not only reported in statistics, but also
        # a running total is kept per thread. If it reaches the threshold, a warning
        # is printed and a defensive action is taken, the cache is cleared to flush
        # potential poison out of it.  A suggested value is 10000000, the default is
        # 0 (turned off). calomel.org thinks 10K is a good value.
        unwanted-reply-threshold = 10000;

        # IMPORTANT FOR TESTING: If you are testing and setup NSD or BIND on
        # localhost you will want to allow the resolver to send queries to localhost.
        # Make sure to set do-not-query-localhost = true;.
        do-not-query-localhost = true;

        # Should additional section of secure message also be kept clean of unsecure
        # data. Useful to shield the users of this validator from potential bogus
        # data in the additional section. All unsigned data in the additional section
        # is removed from secure messages.
        val-clean-additional = true;
      };
    };
  };
  networking.nftables.ruleset = ''
    table inet filter {
      chain output-net {
        meta l4proto { udp, tcp } \
          th dport domain \
          skuid ${users.unbound.name} \
          counter accept comment "unbound"
      }
    }
  '';
  systemd.services.unbound = {
    serviceConfig = {
      RuntimeDirectory = "unbound";
      RuntimeDirectoryMode = "0700";
      BindReadOnlyPaths = [
        "${inputs.self}/share/networking/named.root:/var/lib/unbound/named.root"
      ];
    };
  };
}