WIP: sane-vpn: fix "sane-vpn up none" to correctly delegate all DNS to the DHCP-provided servers when using BIND

previously this only worked when using hickory-dns as the local resolver
This commit is contained in:
2025-01-31 02:01:37 +00:00
parent b536a30919
commit 8c660e3c07
2 changed files with 81 additions and 2 deletions

View File

@@ -1,11 +1,14 @@
# debugging:
# - `man named`
# - `man named.conf`
# - `systemctl stop bind`
# - `sudo /nix/store/0zpdy93sd3fgbxgvf8dsxhn8fbbya8d2-bind-9.18.28/sbin/named -g -u named -4 -c /nix/store/f1mp0myzmfms71h9vinwxpn2i9362a9a-named.conf`
# - `-g` = don't fork
# - `-u named` = start as superuser (to claim port 53), then drop to user `named`
{ config, lib, ... }:
{ config, lib, pkgs, ... }:
let
hostCfg = config.sane.hosts.by-name."${config.networking.hostName}";
bindCfg = config.services.bind;
in
{
config = lib.mkIf (!config.sane.services.hickory-dns.asSystemResolver) {
@@ -53,5 +56,52 @@ in
networking.resolvconf.useLocalResolver = false; #< we manage resolvconf explicitly, above
# TODO: how to exempt `pool.ntp.org` from DNSSEC checks, as i did when using unbound?
# allow runtime insertion of zones or other config changes:
# add your supplemental config as a toplevel file in /run/named/dhcp-configs/, then `systemctl restart bind`
services.bind.extraConfig = ''
include "/run/named/dhcp-configs.conf";
'';
services.bind.extraOptions = ''
// we can't guarantee that all forwarders support DNSSEC,
// and as of 2025-01-30 BIND9 gives no way to disable DNSSEC per-forwarder/zone,
// so just disable it globally
dnssec-validation no;
'';
# re-implement the nixos default bind config, but without `options { forwarders { }; };`,
# as having an empty `forwarders` at the top-level prevents me from forwarding the `.` zone in a separate statement
# (which i want to do to allow sane-vpn to forward all DNS).
services.bind.configFile = pkgs.writeText "named.conf" ''
include "/etc/bind/rndc.key";
controls {
inet 127.0.0.1 allow {localhost;} keys {"rndc-key";};
};
acl cachenetworks { ${lib.concatMapStrings (entry: " ${entry}; ") bindCfg.cacheNetworks} };
acl badnetworks { ${lib.concatMapStrings (entry: " ${entry}; ") bindCfg.blockedNetworks} };
options {
listen-on { ${lib.concatMapStrings (entry: " ${entry}; ") bindCfg.listenOn} };
listen-on-v6 { ${lib.concatMapStrings (entry: " ${entry}; ") bindCfg.listenOnIpv6} };
allow-query-cache { cachenetworks; };
blackhole { badnetworks; };
//v disable top-level forwards, so that i can do forwarding more generically in `zone FOO { ... }` directives.
// forward ${bindCfg.forward};
// forwarders { ${lib.concatMapStrings (entry: " ${entry}; ") bindCfg.forwarders} };
directory "${bindCfg.directory}";
pid-file "/run/named/named.pid";
${bindCfg.extraOptions}
};
${bindCfg.extraConfig}
'';
systemd.services.bind.serviceConfig.ExecStartPre = pkgs.writeShellScript "named-generate-config" ''
mkdir -p /run/named/dhcp-configs
echo "// FILE GENERATED BY bind.service's ExecStartPre: CHANGES TO THIS FILE WILL BE OVERWRITTEN" > /run/named/dhcp-configs.conf
for c in $(ls /run/named/dhcp-configs/); do
cat "/run/named/dhcp-configs/$c" >> /run/named/dhcp-configs.conf
done
'';
};
}

View File

@@ -279,7 +279,32 @@ def vpn_toggle(config: VpnConfig, dir_: ToggleDir) -> None:
print("new IP address ...")
subprocess.check_call(["sane-ip-check", "--no-upnp"])
def dns_toggle(dns: list[str], dir_: ToggleDir) -> None:
def dns_toggle_bind(dns: list[str], dir_: ToggleDir) -> None:
if dir_ == ToggleDir.Up:
formatted_nameservers = "\n".join(f"{ns};" for ns in dns)
text = f'''
zone . {{
//v XXX(2025-01-30): BIND9 doesn't allow dnssec-validation per-zone. put this in toplevel `options` instead.
// dnssec-validation no; // compatibility: many network-specific DNS servers fail DNSSEC, by design
type forward;
forward first;
forwarders {{
{formatted_nameservers}
}};
}};
'''
elif dir == ToggleDir.Down:
text = ""
if not os.path.isdir("/run/named/dhcp-configs"):
logger.info("not restarting bind because it appears to not be in use")
return
with open("/run/named/dhcp-configs/210-sane-vpn.conf", "w") as f:
f.write(text)
subprocess.check_call([ "systemctl", "restart", "bind" ])
def dns_toggle_hickory(dns: list[str], dir_: ToggleDir) -> None:
if dir_ == ToggleDir.Up:
formatted_nameservers = ",\n".join(
'{ socket_addr = "{ns}:53", protocol = "udp", trust_nx_responses = false }'.replace("{ns}", ns)
@@ -304,6 +329,10 @@ stores = {{ type = "forward", name_servers = [
f.write(text)
subprocess.check_call([ "systemctl", "restart", "hickory-dns-localhost" ])
def dns_toggle(dns: list[str], dir_: ToggleDir) -> None:
dns_toggle_bind(dns, dir_)
dns_toggle_hickory(dns, dir_)
def main():
logging.basicConfig()
logging.getLogger().setLevel(logging.INFO)