321 lines
11 KiB
Nix
321 lines
11 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
let
|
|
hickory-dns-nmhook = pkgs.static-nix-shell.mkPython3 {
|
|
pname = "hickory-dns-nmhook";
|
|
srcRoot = ./.;
|
|
pkgs = [
|
|
"systemd"
|
|
];
|
|
};
|
|
cfg = config.sane.services.hickory-dns;
|
|
dns = config.sane.dns;
|
|
toml = pkgs.formats.toml { };
|
|
instanceModule = with lib; types.submodule ({ config, name, ...}: {
|
|
options = {
|
|
service = mkOption {
|
|
type = types.str;
|
|
default = "hickory-dns-${name}";
|
|
description = ''
|
|
systemd service name corresponding to this instance (used internally and automatically set).
|
|
'';
|
|
};
|
|
port = mkOption {
|
|
type = types.port;
|
|
default = 53;
|
|
};
|
|
listenAddrsIpv4 = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [ "127.0.0.1" ];
|
|
description = ''
|
|
IPv4 addresses to serve requests from.
|
|
'';
|
|
};
|
|
listenAddrsIpv6 = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [ ];
|
|
description = ''
|
|
IPv6 addresses to serve requests from.
|
|
'';
|
|
};
|
|
substitutions = mkOption {
|
|
type = types.attrsOf types.str;
|
|
default = {};
|
|
description = ''
|
|
text substitutions to make on the config and zone file before starting hickory-dns.
|
|
'';
|
|
example = {
|
|
"%CNAMESELF%" = "lappy";
|
|
"%AWAN%" = ''"$(cat /var/uninsane/wan.txt)"'';
|
|
};
|
|
};
|
|
includes = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [];
|
|
description = ''
|
|
list of paths to cat into the final config.
|
|
non-existent paths are skipped.
|
|
supports shell-style globbing.
|
|
'';
|
|
};
|
|
enableRecursiveResolver = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
act as a recursive resolver
|
|
'';
|
|
};
|
|
extraConfig = mkOption {
|
|
type = types.attrs;
|
|
default = {};
|
|
};
|
|
};
|
|
|
|
config = {
|
|
extraConfig = lib.mkIf config.enableRecursiveResolver {
|
|
zones = [
|
|
{
|
|
zone = ".";
|
|
zone_type = "Hint";
|
|
stores = {
|
|
type = "recursor";
|
|
# contains the list of toplevel DNS servers, from which to recursively resolve entries.
|
|
roots = "${pkgs.dns-root-data}/root.hints";
|
|
|
|
# dnssec, see: <https://github.com/hickory-dns/hickory-dns/issues/2193>
|
|
# probably not needed: the default seems to be that dnssec is disabled
|
|
# enable_dnssec = false;
|
|
#
|
|
# defaults, untuned
|
|
# ns_cache_size = 1024;
|
|
# record_cache_size = 1048576;
|
|
};
|
|
}
|
|
];
|
|
};
|
|
};
|
|
});
|
|
|
|
mkSystemdService = flavor: { includes, listenAddrsIpv4, listenAddrsIpv6, port, substitutions, extraConfig, ... }: let
|
|
sed = lib.getExe pkgs.gnused;
|
|
baseConfig = (
|
|
lib.filterAttrsRecursive (_: v: v != null) config.services.hickory-dns.settings
|
|
) // {
|
|
listen_addrs_ipv4 = listenAddrsIpv4;
|
|
listen_addrs_ipv6 = listenAddrsIpv6;
|
|
};
|
|
configTemplate = toml.generate "hickory-dns-${flavor}.toml" (baseConfig //
|
|
(lib.mapAttrs (k: v:
|
|
if k == "zones" then
|
|
# append to the baseConfig instead of overriding it
|
|
(baseConfig."${k}" or []) ++ v
|
|
else
|
|
v
|
|
)
|
|
extraConfig
|
|
));
|
|
configPath = "/var/lib/hickory-dns/${flavor}-config.toml";
|
|
sedArgs = builtins.map (key: ''-e "s/${key}/${substitutions."${key}"}/g"'') (
|
|
# HACK: %ANATIVE% often expands to one of the other subtitutions (e.g. %AWAN%)
|
|
# so we must expand it *first*.
|
|
lib.sortOn
|
|
(k: if k == "%ANATIVE%" then 0 else 1)
|
|
(builtins.attrNames substitutions)
|
|
);
|
|
subs = lib.concatStringsSep " " sedArgs;
|
|
in {
|
|
description = "hickory-dns Domain Name Server (serving ${flavor})";
|
|
unitConfig.Documentation = "https://hickory-dns.org/";
|
|
after = [ "network.target" ];
|
|
before = [ "network-online.target" ]; # most things assume they'll have DNS services alongside routability
|
|
wantedBy = [ "network.target" ];
|
|
|
|
preStart = lib.concatStringsSep "\n" (
|
|
[''
|
|
mkdir -p "/var/lib/hickory-dns/${flavor}"
|
|
${sed} ${subs} -e "" "${configTemplate}" \
|
|
| cat - \
|
|
${lib.concatStringsSep " " includes} \
|
|
> "${configPath}" || true
|
|
''] ++ lib.mapAttrsToList (zone: { rendered, ... }: ''
|
|
${sed} ${subs} -e "" ${pkgs.writeText "${zone}.zone.in" rendered} \
|
|
> "/var/lib/hickory-dns/${flavor}/${zone}.zone"
|
|
'') dns.zones
|
|
);
|
|
|
|
serviceConfig = config.systemd.services.hickory-dns.serviceConfig // {
|
|
ExecStart = lib.escapeShellArgs ([
|
|
"${lib.getExe config.services.hickory-dns.package}"
|
|
"--port" (builtins.toString port)
|
|
"--zonedir" "/var/lib/hickory-dns/${flavor}"
|
|
"--config" "${configPath}"
|
|
] ++ lib.optionals config.services.hickory-dns.debug [
|
|
"--debug"
|
|
] ++ lib.optionals config.services.hickory-dns.quiet [
|
|
"--quiet"
|
|
]);
|
|
# servo/dyn-dns needs /var/lib/uninsane/wan.txt.
|
|
# this might not exist on other systems,
|
|
# so just bind the deepest path which is guaranteed to exist.
|
|
ReadOnlyPaths = [ "/var/lib" ]; #< TODO: scope this down!
|
|
} // lib.optionalAttrs cfg.asSystemResolver {
|
|
# allow the group to write hickory-dns state (needed by NetworkManager hook)
|
|
StateDirectoryMode = "775";
|
|
};
|
|
};
|
|
in
|
|
{
|
|
options = with lib; {
|
|
sane.services.hickory-dns = {
|
|
enable = mkEnableOption "hickory DNS server";
|
|
asSystemResolver = mkOption {
|
|
default = false;
|
|
type = types.bool;
|
|
description = ''
|
|
host a recursive DNS resolver on localhost for use by local programs.
|
|
plugs into /etc/resolv.conf so that any glibc application knows to use the resolver.
|
|
N.B.: hickory-dns fails to resolve several domain names as of 2024-10-04, including:
|
|
- abs.twimg.com
|
|
- social.kernel.org
|
|
- pe.usps.com
|
|
- social.seattle.wa.us
|
|
- support.mozilla.org
|
|
- shows.acast.com
|
|
'';
|
|
};
|
|
instances = mkOption {
|
|
default = {};
|
|
type = types.attrsOf instanceModule;
|
|
};
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
# enable nixpkgs' hickory-dns so that i get its config generation
|
|
# but don't actually enable the systemd service... i'll instantiate *multiple* instances per interface further below
|
|
services.hickory-dns.enable = true;
|
|
services.hickory-dns.settings.zones = []; #< TODO: remove once upstreamed (bad default)
|
|
|
|
# don't bind to IPv6 until i explicitly test that stack
|
|
services.hickory-dns.settings.listen_addrs_ipv6 = [];
|
|
services.hickory-dns.quiet = true;
|
|
# FIXME(2023/11/26): services.hickory-dns.debug doesn't log requests: use RUST_LOG=debug env for that.
|
|
# - see: <https://github.com/hickory-dns/hickory-dns/issues/2082>
|
|
# services.hickory-dns.debug = true;
|
|
|
|
services.hickory-dns.package = pkgs.hickory-dns.override {
|
|
rustPlatform.buildRustPackage = args: pkgs.rustPlatform.buildRustPackage (args // {
|
|
buildFeatures = [
|
|
# to find available features: `rg 'feature ='`
|
|
"dnssec" #< else the recursor doesn't compile
|
|
# "dnssec-openssl" #< else dnssec doesn't compile
|
|
"dnssec-ring" #< else dnssec doesn't compile
|
|
"recursor"
|
|
# "backtrace"
|
|
# "dns-over-h3"
|
|
# "dns-over-https"
|
|
# "dns-over-https-rustls"
|
|
# "dns-over-native-tls"
|
|
# "dns-over-quic"
|
|
# "dns-over-rustls"
|
|
# "dns-over-tls"
|
|
# "dnssec-openssl"
|
|
# "mdns"
|
|
# "native-certs"
|
|
# "serde"
|
|
# "system-config"
|
|
# "tokio-runtime"
|
|
# "webpki-roots"
|
|
];
|
|
|
|
# XXX(2024-08-18): upstream hickory-dns has a recursive resolver *almost* as capable as my own.
|
|
# it fails against a few sites mine works on:
|
|
# - `en.wikipedia.org.` (doesn't follow the CNAME)
|
|
# it fails against sites mine fails on:
|
|
# - `social.kernel.org.`
|
|
# - `support.mozilla.org.`
|
|
# version = "0.25.0-alpha.2";
|
|
# src = pkgs.fetchFromGitHub {
|
|
# owner = "hickory-dns";
|
|
# repo = "hickory-dns";
|
|
# rev = "v0.25.0-alpha.2";
|
|
# hash = "sha256-bEVApMM6/I3nF1lyRhd+7YtZuSAwiozRkMorRLhLOBY=";
|
|
# };
|
|
# cargoHash = "sha256-KFPwVFixLaL9cdXTAIVJUqmtW1V5GTmvFaK5N5SZKyU=";
|
|
|
|
# fix enough bugs inside the recursive resolver that it's compatible with my infra.
|
|
# TODO: upstream these patches!
|
|
version = "0.24.1-unstable-2024-08-19";
|
|
src = pkgs.fetchFromGitea {
|
|
domain = "git.uninsane.org";
|
|
owner = "colin";
|
|
repo = "hickory-dns";
|
|
rev = "4fd7a8305e333117278e216fa9f81984f1e256b6"; # Recursor: handle NS responses with a different type and no SOA (fix: api.mangadex.org., m.wikipedia.org.)
|
|
hash = "sha256-pNCuark/jvyRABR9Hdd60vndppaE3suvTP3UfCfsimI=";
|
|
};
|
|
cargoHash = "sha256-6yV/qa1CVndHDs/7AK5wVTYIV8NmNqkHL3JPZUN31eM=";
|
|
});
|
|
};
|
|
services.hickory-dns.settings.directory = "/var/lib/hickory-dns";
|
|
|
|
users.groups.hickory-dns = {};
|
|
users.users.hickory-dns = {
|
|
group = "hickory-dns";
|
|
isSystemUser = true;
|
|
};
|
|
|
|
systemd.services = lib.mkMerge [
|
|
{
|
|
hickory-dns.enable = false;
|
|
hickory-dns.serviceConfig = {
|
|
DynamicUser = lib.mkForce false;
|
|
User = "hickory-dns";
|
|
Group = "hickory-dns";
|
|
wantedBy = lib.mkForce [];
|
|
# there can be a lot of restarts as interfaces toggle,
|
|
# particularly around the DHCP/NetworkManager stuff.
|
|
StartLimitBurst = 60;
|
|
StateDirectory = lib.mkForce "hickory-dns";
|
|
};
|
|
# hickory-dns.unitConfig.StartLimitIntervalSec = 60;
|
|
}
|
|
(lib.mapAttrs'
|
|
(flavor: instanceConfig: {
|
|
name = instanceConfig.service;
|
|
value = mkSystemdService flavor instanceConfig;
|
|
})
|
|
cfg.instances
|
|
)
|
|
];
|
|
|
|
# run a hook whenever networking details change, so the DNS zone can be updated to reflect this
|
|
environment.etc."NetworkManager/dispatcher.d/60-hickory-dns-nmhook" = lib.mkIf cfg.asSystemResolver {
|
|
source = lib.getExe hickory-dns-nmhook;
|
|
};
|
|
|
|
# allow NetworkManager (via hickory-dns-nmhook) to restart hickory-dns when necessary
|
|
# - source: <https://stackoverflow.com/questions/61480914/using-policykit-to-allow-non-root-users-to-start-and-stop-a-service>
|
|
security.polkit.extraConfig = lib.mkIf cfg.asSystemResolver ''
|
|
polkit.addRule(function(action, subject) {
|
|
if (subject.isInGroup("hickory-dns") &&
|
|
action.id == "org.freedesktop.systemd1.manage-units" &&
|
|
action.lookup("unit") == "hickory-dns-localhost.service") {
|
|
return polkit.Result.YES;
|
|
}
|
|
});
|
|
'';
|
|
|
|
sane.services.hickory-dns.instances.localhost = lib.mkIf cfg.asSystemResolver {
|
|
listenAddrsIpv4 = [ "127.0.0.1" ];
|
|
listenAddrsIpv6 = [ "::1" ];
|
|
enableRecursiveResolver = true;
|
|
# append zones discovered via DHCP to the resolver config.
|
|
includes = [ "/var/lib/hickory-dns/dhcp-configs/*" ];
|
|
};
|
|
networking.nameservers = lib.mkIf cfg.asSystemResolver [
|
|
"127.0.0.1"
|
|
"::1"
|
|
];
|
|
services.resolved.enable = lib.mkIf cfg.asSystemResolver (lib.mkForce false);
|
|
};
|
|
}
|