add: a blank fallback DNS field means single-server #171

Merged
mysticalsoap merged 1 commit from single-server-dns into trunk 2026-08-27 11:36:51 -04:00
Owner

Problem

The single-server path already existed internally — dns_2=None is handled by set_dns, repin_dns_primary, dnsmasq, and the watchdog gate — but a cleared GUI field saved "" rather than None, and the empty string passed every is not None check. It reached systemd-resolve --set-dns= as an empty argument, the whole command failed, and the tunnel came up with no DNS on the link at all: no servers, no ~. routing domain. The dnsmasq (--server=) and resolv.conf (nameserver ) paths broke the same way.

Fix

  • New config.alt_dns_pair normalizes the two fields at the boundary: blanks and whitespace drop out, order is kept, a cleared primary promotes the fallback, nothing configured comes back as (None, None).
  • All four read sites go through it: tunnel roles on connect and reset, the service's options apply, and the bypass resolver's fallback pair.
  • set_dns(None, ...) now refuses with a warning instead of breaking the link (both-fields-blank user error stays loud but safe).
  • The GUI strips whitespace on save; --set-alt-dns accepts a single server and clears the fallback (at most two, as before).

One server on the tunnel link means resolved's sticky failover has nowhere to strand a split-horizon primary, so the #95 watchdog never starts there — the Proton app's custom-DNS behavior, opt-in via a cleared field instead of the originally proposed address-class heuristic (#167 was re-scoped accordingly). #170's damping covers everyone who keeps a pair.

Verification

  • New tests/test_config.py (TestAltDnsPair): pass-through, blank fallback, whitespace-only, promoted fallback, nothing configured, missing/null keys.
  • TestSetDns: set_dns(None) touches neither resolved nor resolv.conf and warns.
  • TestManagementEvents.test_single_server_sets_dns_without_watchdog: CONNECTED with a lone server applies it and starts no watchdog.
  • Full suite: 386 passed (+ tests/test_mgmt.py runs separately); ruff E9,F63,F7,F82 and compileall clean.

Closes #167

🤖 Generated with Claude Code

**Problem** The single-server path already existed internally — `dns_2=None` is handled by `set_dns`, `repin_dns_primary`, `dnsmasq`, and the watchdog gate — but a cleared GUI field saved `""` rather than `None`, and the empty string passed every `is not None` check. It reached `systemd-resolve --set-dns=` as an empty argument, the whole command failed, and the tunnel came up with no DNS on the link at all: no servers, no `~.` routing domain. The dnsmasq (`--server=`) and resolv.conf (`nameserver `) paths broke the same way. **Fix** - New `config.alt_dns_pair` normalizes the two fields at the boundary: blanks and whitespace drop out, order is kept, a cleared primary promotes the fallback, nothing configured comes back as `(None, None)`. - All four read sites go through it: tunnel roles on connect and reset, the service's options apply, and the bypass resolver's fallback pair. - `set_dns(None, ...)` now refuses with a warning instead of breaking the link (both-fields-blank user error stays loud but safe). - The GUI strips whitespace on save; `--set-alt-dns` accepts a single server and clears the fallback (at most two, as before). One server on the tunnel link means resolved's sticky failover has nowhere to strand a split-horizon primary, so the #95 watchdog never starts there — the Proton app's custom-DNS behavior, opt-in via a cleared field instead of the originally proposed address-class heuristic (#167 was re-scoped accordingly). #170's damping covers everyone who keeps a pair. **Verification** - New `tests/test_config.py` (`TestAltDnsPair`): pass-through, blank fallback, whitespace-only, promoted fallback, nothing configured, missing/null keys. - `TestSetDns`: `set_dns(None)` touches neither resolved nor resolv.conf and warns. - `TestManagementEvents.test_single_server_sets_dns_without_watchdog`: CONNECTED with a lone server applies it and starts no watchdog. - Full suite: 386 passed (+ `tests/test_mgmt.py` runs separately); ruff E9,F63,F7,F82 and `compileall` clean. Closes #167 🤖 Generated with [Claude Code](https://claude.com/claude-code)
add: a blank fallback DNS field means single-server
All checks were successful
ci / test (pull_request) Successful in 25s
ci / test (push) Successful in 46s
12a79fa316
The single-server path existed internally - dns_2=None is handled by
set_dns, repin_dns_primary, dnsmasq, and the watchdog gate - but a
cleared GUI field saved "" rather than None, and the empty string
passed every 'is not None' check: it reached systemd-resolve
--set-dns= as an empty argument, the whole command failed, and the
tunnel came up with no DNS on the link at all.

config.alt_dns_pair now normalizes the two fields at the boundary:
blanks drop out, order is kept, a cleared primary promotes the
fallback, and nothing configured comes back as (None, None), which
set_dns refuses with a warning instead of breaking the link. All four
read sites (tunnel roles on connect and reset, the service's options
apply, the bypass resolver's fallback pair) go through it. The CLI's
--set-alt-dns accepts a single server and clears the fallback.

One server on the tunnel link means resolved's sticky failover has
nowhere to strand a split-horizon primary, so the #95 watchdog never
starts there - the same behavior as the Proton app's custom DNS, as an
opt-in instead of an address-class heuristic.

Closes #167

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mysticalsoap deleted branch single-server-dns 2026-08-27 11:36:51 -04:00
Sign in to join this conversation.
No description provided.