testsAndMisc/linux_configuration/features/lib/dns_units.sh
Krzysztof kuhy Rudnicki 1ea94e142c lan blocker: gateway mode, WARP drop with a Mon/Fri 09:00-15:30 window
Gateway mode routes LAN devices through the PC: forced DNS, DoH/DoT
dropped. The work laptop still bypassed it through Cloudflare WARP, so
the WARP ingress is dropped too. warp_window.sh opens it for the LAN
only Mon/Fri 09:00-15:30 (nft set with a timeout to 15:30), asked for
over a forced-command key by warp_home.sh, which keeps WARP off at home
otherwise because WARP fails closed. Also: dnsmasq upstreams, AAAA
filtering, DoH canary, and extracted_repo_dir resolving ~/src first
(the nightly blocklist refresh had failed since 2026-09-11).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019C8pdLR4MePsLocE1pWz7F
2026-10-25 09:52:40 +01:00

139 lines
4.9 KiB
Bash
Executable File

#!/bin/bash
# dnsmasq config, drop-in, refresh timer and watchdog units.
#
# Sourced by setup_dns_blocker.sh; split out to keep it under the 250-line cap.
# Sourced rather than run, so it inherits the caller's strict mode.
# Upstream resolvers for every name not in the blocklist.
readonly UPSTREAMS=(1.1.1.1 9.9.9.9)
# Hostnames browsers use to reach DoH providers; answered 0.0.0.0 so a browser
# set to "secure DNS" falls back to this resolver instead of bypassing it.
readonly DOH_HOSTNAMES=(
dns.google cloudflare-dns.com one.one.one.one dns.quad9.net
dns.nextdns.io doh.opendns.com dns.adguard-dns.com doh.cleanbrowsing.org
dns.mullvad.net dns.controld.com freedns.controld.com doh.libredns.gr
)
write_dnsmasq_conf() {
ensure_dir "$(dirname "$DNSMASQ_CONF")" # /etc/dnsmasq.d may not exist yet
local server_lines doh_lines
server_lines="$(printf 'server=%s\n' "${UPSTREAMS[@]}")"
doh_lines="$(printf 'address=/%s/0.0.0.0\n' "${DOH_HOSTNAMES[@]}")"
log_info "Writing ${DNSMASQ_CONF} (listen on ${LAN_IFACE}/${LAN_IP}, upstream ${UPSTREAMS[*]})."
cat >"$DNSMASQ_CONF" <<EOF
# Managed by setup_dns_blocker.sh -- LAN DNS blocker.
# Serves the identical blocklist as /etc/hosts to LAN clients via addn-hosts.
# Do not edit by hand; re-run 'setup_dns_blocker.sh setup' instead.
# Listen only on the LAN interface (leaves 127.0.0.53 free for the
# systemd-resolved stub that the /etc/hosts installer enables).
# bind-dynamic, not bind-interfaces: at boot dnsmasq can start before
# ${LAN_IFACE} exists, and bind-interfaces then dies with "unknown interface".
# Its 5 s restart recovered, but stunnel-dot's start job had already failed on
# the dependency and was never retried, so DoT stayed down every boot.
# bind-dynamic binds the address whenever the interface appears.
interface=${LAN_IFACE}
bind-dynamic
# Do NOT read /etc/hosts (huge + immutable); the blocklist comes from the feed.
no-hosts
addn-hosts=${FEED}
# Forward everything not in the blocklist to the upstream resolvers ONLY.
# no-resolv: ignore /etc/resolv.conf so we never chain through the
# systemd-resolved stub (127.0.0.53) that the /etc/hosts installer enables.
# Not the Funbox: it drops ~20% of queries (measured 2026-09-18), which is
# every device's problem once gateway mode forces all LAN DNS through here.
no-resolv
${server_lines}
domain-needed
bogus-priv
cache-size=10000
# The feed only carries 0.0.0.0, so every AAAA query used to be forwarded and a
# "blocked" name resolved to its real IPv6 address. Strip AAAA everywhere: the
# LAN has no IPv6 from the Funbox anyway, and IPv6 would bypass gateway mode.
filter-AAAA
# DoH bypasses this resolver. Firefox turns its default DoH off only when the
# canary domain is NXDOMAIN; the bootstrap names stop browsers resolving the
# common DoH providers (gateway mode also drops their IPs).
local=/use-application-dns.net/
${doh_lines}
# Log dnsmasq's own messages (startup, upstream failures) -- "log failures".
log-facility=${LOG_FILE}
# log-queries # opt-in: full per-query log (verbose + privacy). Off by default.
EOF
}
install_restart_dropin() {
ensure_dir "$DNSMASQ_DROPIN_DIR"
cat >"$DNSMASQ_DROPIN" <<'EOF'
# Managed by setup_dns_blocker.sh -- keep the resolver up.
# No network-online ordering: the stock unit is Before=network-online.target,
# so After= here was an ordering cycle systemd broke by deleting a job at every
# boot. bind-dynamic (dnsmasq.conf) makes waiting for the NIC unnecessary.
[Service]
Restart=always
RestartSec=5
EOF
}
install_refresh_timer() {
cat >"$REFRESH_SERVICE" <<EOF
[Unit]
Description=Rebuild the LAN DNS blocklist and reload dnsmasq
After=network-online.target
[Service]
Type=oneshot
ExecStart=${SELF} refresh
EOF
cat >"$REFRESH_TIMER" <<'EOF'
[Unit]
Description=Daily rebuild of the LAN DNS blocklist
[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=15m
[Install]
WantedBy=timers.target
EOF
}
# The whole LAN's DHCP/DNS depends on this one process. Restart=always in the
# drop-in only catches an actual crash; an incident on 2026-07-23 found
# dnsmasq dead with ZERO error logged (no crash, no "Stopping"/"Stopped" line
# either -- consistent with something issuing a plain stop, not a failure) and
# it stayed down for hours, silently, until noticed by a phone unable to join
# WiFi. Mirrors the existing shutdown-timer-monitor-watchdog pattern used
# elsewhere in this repo: same 5-minute cadence, same is-active-or-start shape,
# plus a logger call so a future incident leaves a trace instead of none.
install_watchdog() {
cat >"$WATCHDOG_SERVICE" <<'EOF'
[Unit]
Description=Watchdog for dnsmasq (LAN DHCP/DNS)
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'systemctl is-active --quiet dnsmasq || { logger -t dnsmasq-watchdog "dnsmasq was down -- restarting"; systemctl restart dnsmasq; }'
EOF
cat >"$WATCHDOG_TIMER" <<'EOF'
[Unit]
Description=Watchdog Timer for dnsmasq
[Timer]
OnBootSec=60
OnUnitActiveSec=300
Persistent=true
[Install]
WantedBy=timers.target
EOF
}