#!/usr/bin/env python3
"""NM dispatcher: never let a WAN's DHCP/RA-provided DNS servers get used,
and keep mDNS scoped to the van's own LAN (never a WAN).

This router only resolves via the fixed servers in
/etc/systemd/resolved.conf.d/99-van-router-dns.conf (deploy.conf's
DNS_RESOLVERS) — never whatever a WAN happens to hand out (Wapana, a
campsite AP, Starlink, cellular). NetworkManager's own ipv4/ipv6.ignore-auto-
dns can't be set as a config-file connection default: NM rejects it there
("unknown key") even though it's a real, settable per-connection property —
so this enforces the same outcome directly against systemd-resolved instead,
on every WAN connect/lease event, for every current and future WAN profile.

Also disables mDNS on the WAN link: 99-van-router-dns.conf sets the global
MulticastDNS default to "yes" (a prerequisite for br0's own per-link
MulticastDNS=yes to mean anything — resolved gates per-link "yes" behind the
global default), so every link starts out mDNS-enabled unless told
otherwise; this opts each WAN back out as it comes up.

Also records what DNS was advertised (but never used) to /run/van-wan-dns/,
for reference/debugging.

NM dispatcher calling convention: argv = [iface, action].
"""
import json
import os
import subprocess
import sys
import time

STATE_DIR = "/run/van-wan-dns"


def clear_link_dns(iface):
    subprocess.run(["resolvectl", "dns", iface, ""], check=False)
    subprocess.run(["resolvectl", "domain", iface, ""], check=False)
    subprocess.run(["resolvectl", "mdns", iface, "no"], check=False)


def main():
    iface, action = sys.argv[1], sys.argv[2]
    if action not in ("up", "dhcp4-change", "dhcp6-change"):
        return

    # Strip whatever DNS/search-domain this link just got from DHCP/RA —
    # global DNS=/Domains=~. (99-van-router-dns.conf) then wins for everything.
    # NM commits its own DNS to resolved asynchronously, shortly *after* this
    # dispatcher fires — a single clear here loses that race, so retry over a
    # few seconds (confirmed empirically: one clear immediately gets
    # clobbered, a clear a couple seconds later sticks).
    for _ in range(5):
        clear_link_dns(iface)
        time.sleep(1)

    out = subprocess.run(["nmcli", "-t", "-f", "DHCP4.OPTION", "device", "show", iface],
                          capture_output=True, text=True, check=False).stdout

    servers = domain = None
    for line in out.splitlines():
        if ":" not in line or " = " not in line:
            continue
        _, kv = line.split(":", 1)
        key, _, val = kv.partition(" = ")
        if key == "domain_name_servers":
            servers = val.split()
        elif key == "domain_name":
            domain = val.strip() or None

    if not servers:
        return

    os.makedirs(STATE_DIR, exist_ok=True)
    with open(f"{STATE_DIR}/{iface}.json", "w") as f:
        json.dump({
            "iface": iface,
            "dns_advertised": servers,
            "domain_advertised": domain,
            "updated": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
        }, f, indent=2)


if __name__ == "__main__":
    main()
