Compare commits
4 Commits
master
...
2025-01-30
Author | SHA1 | Date | |
---|---|---|---|
de070bd5d8 | |||
ac58b884db | |||
d52844ddca | |||
8c660e3c07 |
@@ -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,53 @@ 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
|
||||
chmod g+w /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
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@
|
||||
"feedbackd" # moby, so `fbcli` can control vibrator and LEDs
|
||||
"input" # for /dev/input/<xyz>... TODO:is this still necessary?
|
||||
"media" # servo
|
||||
"named" # for `sane-vpn {up,down}`
|
||||
"networkmanager"
|
||||
"nixbuild"
|
||||
"plugdev" # desko, for ZSA/QMK/udev
|
||||
|
@@ -151,11 +151,6 @@ def get_dns_resolvers_for_dev(dev: str) -> list[str]:
|
||||
"""
|
||||
passt/pasta can't proxy to e.g. 127.0.0.53, but it can to 127.0.0.1 and anything routable by the device
|
||||
"""
|
||||
nameservers = nameservers_from_resolvconf(RESOLVCONF)
|
||||
if nameservers == []:
|
||||
nameservers = [ "127.0.0.1 "] #< libc default if unspecified
|
||||
if "127.0.0.1" in nameservers:
|
||||
return [ "127.0.0.1" ]
|
||||
|
||||
# output looks like IP4.DNS[1]:192.168.0.1
|
||||
nmcli_stdout = subprocess.check_output([
|
||||
@@ -169,8 +164,37 @@ def get_dns_resolvers_for_dev(dev: str) -> list[str]:
|
||||
_fname, ns = line.split(":")
|
||||
nameservers.append(ns)
|
||||
|
||||
if nameservers == []:
|
||||
nameservers = nameservers_from_resolvconf(RESOLVCONF)
|
||||
if nameservers == []:
|
||||
nameservers = [ "127.0.0.1 "] #< libc default if unspecified
|
||||
|
||||
# old logic from when i was using systemd-resolved (127.0.0.53, which pasta couldn't reach). safe to remove?
|
||||
# if "127.0.0.1" in nameservers:
|
||||
# return [ "127.0.0.1" ]
|
||||
|
||||
return nameservers
|
||||
|
||||
def get_addr_v4_for_dev(dev: str) -> str | None:
|
||||
# output looks like IP4.ADDRESS[1]:192.168.1.72
|
||||
nmcli_stdout = subprocess.check_output([
|
||||
"nmcli", "-t", "-f", "IP4.ADDRESS", "device", "show", dev
|
||||
]).decode("utf-8")
|
||||
addrs = []
|
||||
for line in nmcli_stdout.splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
_fname, addr = line.split(":")
|
||||
if not addr:
|
||||
continue
|
||||
if "/" in addr:
|
||||
addr, _subnet = addr.split("/")
|
||||
if not addr:
|
||||
continue
|
||||
addrs.append(addr)
|
||||
|
||||
return addrs[0] if len(addrs) else None
|
||||
|
||||
def get_connected_networks() -> list[str]:
|
||||
# output looks like:
|
||||
@@ -211,8 +235,9 @@ def get_vpn(vpn_name: str) -> VpnConfig:
|
||||
return load_vpns()[vpn_name]
|
||||
elif vpn_name == "none":
|
||||
dev = get_default_dev()
|
||||
addr_v4 = get_addr_v4_for_dev(dev)
|
||||
dns = get_dns_resolvers_for_dev(dev)
|
||||
return VpnConfig(dev=dev or "all", dns=dns)
|
||||
return VpnConfig(dev=dev or "all", addr_v4=addr_v4, dns=dns)
|
||||
elif vpn_name == "unmetered":
|
||||
for dev in get_connected_networks():
|
||||
dns = get_dns_resolvers_for_dev(dev)
|
||||
@@ -245,7 +270,8 @@ def vpn_do(config: VpnConfig, cmd: list[str]) -> None:
|
||||
# exec instead of subprocess so that (hopefully) the process inherits stdin/stdout.
|
||||
os.execvp("bunpen", wrapped_cmd)
|
||||
|
||||
def vpn_toggle(config: VpnConfig, dir_: ToggleDir) -> None:
|
||||
def vpn_toggle(config: VpnConfig, dir_: ToggleDir, do_ip_check: bool = True) -> None:
|
||||
if do_ip_check:
|
||||
print("old IP address ...")
|
||||
_ = subprocess.run(["sane-ip-check", "--no-upnp", "--retry-duration", "2"])
|
||||
|
||||
@@ -276,10 +302,36 @@ def vpn_toggle(config: VpnConfig, dir_: ToggleDir) -> None:
|
||||
|
||||
dns_toggle(config.dns, dir_)
|
||||
|
||||
if do_ip_check:
|
||||
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 +356,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)
|
||||
@@ -336,18 +392,18 @@ def main():
|
||||
|
||||
logger.debug(f"sane-vpn {subcommand} region={region} ...")
|
||||
|
||||
do_ip_check = region != "none"
|
||||
vpn_config = get_vpn(region)
|
||||
|
||||
if args.no_proxy_dns:
|
||||
vpn_config.dns = []
|
||||
|
||||
|
||||
if subcommand == "do":
|
||||
vpn_do(vpn_config, args.command)
|
||||
elif subcommand == "down":
|
||||
vpn_toggle(vpn_config, ToggleDir.Down)
|
||||
vpn_toggle(vpn_config, ToggleDir.Down, do_ip_check=do_ip_check)
|
||||
elif subcommand == "up":
|
||||
vpn_toggle(vpn_config, ToggleDir.Up)
|
||||
vpn_toggle(vpn_config, ToggleDir.Up, do_ip_check=do_ip_check)
|
||||
else:
|
||||
print(parser.usage)
|
||||
sys.exit(1)
|
||||
|
Reference in New Issue
Block a user