DNS & resolver recovery
Symptoms: /etc/resolv.conf keeps flipping away from nameserver 127.0.0.1
after DHCP lease renewals; intermittent lookup failures on a box that resolves
fine right after a BOA run. Root cause is almost always a fight between the
DHCP client and BOA over /etc/resolv.conf. Since 5.10.3 BOA settles the fight
permanently: BOA owns /etc/resolv.conf, the DHCP client is told to keep
its hands off, and the content self-heals on every agent run. This page covers
the ownership model, the two self-healing tiers (they restore different
things — do not conflate them), the operator toggles, and the dhcpfix
recovery tool.
The canonical file
_fix_dns_settings writes one canonical /etc/resolv.conf:
### BOA-DNS-Config ###
nameserver 127.0.0.1
nameserver 1.1.1.1
nameserver 8.8.8.8
nameserver 9.9.9.9
The 127.0.0.1 line (the local unbound resolver) is included only when
unbound is installed and running — the test is /usr/sbin/unbound
executable plus /run/unbound/unbound.pid present. Then the three public
fallbacks in fixed order: 1.1.1.1, 8.8.8.8, 9.9.9.9. After every rewrite
the function reloads unbound via unbound-control reload (when installed) and
keeps a reference copy at /var/backups/resolv.conf.vanilla.
The BOA.sh.txt bootstrap copy additionally chmods the file 0644 and
locks it with chattr +i, so nothing short of chattr -i can modify it. The
copies in the fetched agents (boa, barracuda, octopus,
lib/functions/dns.sh.inc) write the same content but leave the file 0644
and mutable — see the two-tier split below.
Trade-off carried from the source: the public fallbacks let lookups survive unbound being down, but they also let queries bypass unbound (cache, RPZ, split view) whenever unbound is unreachable or times out. That is an intentional availability-over-purity choice.
DHCP overwrite protection: the nodnsupdate hook
Whenever the BOA.sh.txt copy rewrites resolv.conf, it also installs an
executable hook at /etc/dhcp/dhclient-enter-hooks.d/nodnsupdate:
make_resolv_conf() { :; }
That one no-op is sufficient on its own: dhclient-script sources the
enter-hooks directory after defining its own make_resolv_conf, so the
no-op wins and resolv.conf is never written by dhclient regardless of which
DNS options the lease carries. No dhclient.conf request-line surgery is
needed, and the hook is designed to take effect at the next natural lease
RENEW — no lease churn at install time.
Self-healing: two tiers
Both tiers run _check_dns_settings, which forces a rewrite of
resolv.conf on the same content triggers — but they restore different
things:
| Trigger forcing a rewrite | BOA.sh.txt bootstrap |
Fetched agents (boa/barracuda/octopus) |
|---|---|---|
/etc/resolv.conf is a symlink |
yes | yes |
unbound running but nameserver 127.0.0.1 missing |
yes | yes |
### BOA-DNS-Config ### header missing |
yes | yes |
Remote lookup test fails (host files.boa.io 1.1.1.1) |
yes | yes |
unbound running but nodnsupdate hook missing |
yes | not checked |
| What a rewrite restores | BOA.sh.txt bootstrap |
Fetched agents |
|---|---|---|
Canonical content, 0644 |
yes | yes |
chattr +i lock |
yes | no — file left mutable |
nodnsupdate hook (re-)installed |
yes | no |
In practice: every boa, barracuda or octopus run self-heals the resolv.conf
content (the check runs early, before mirror selection), so a box that lost
its 127.0.0.1 line recovers on the next agent run. But a lost
nodnsupdate hook — and the immutable lock — are restored only by the next
BOA.sh.txt-driven install/upgrade run, or immediately by dhcpfix (below).
Operator toggles
| Control file | Effect |
|---|---|
/root/.use.default.nameservers.cnf |
Forces BOA's default public-nameserver handling. When both toggles exist, this one wins and .use.local.nameservers.cnf is deleted. |
/root/.use.local.nameservers.cnf |
Sets _USE_PROVIDER_DNS=YES — the remote lookup test is skipped, and the unbound install path skips its forced public-set rewrites, designed to leave a provider-managed resolv.conf in place. The ### BOA-DNS-Config ### header check in _fix_dns_settings is not gated by this toggle, so a provider file lacking the header can still be rewritten to the canonical set. |
Quick diagnosis
cat /etc/resolv.conf # header + 127.0.0.1 first (when unbound runs)?
lsattr /etc/resolv.conf # 'i' flag = bootstrap-locked; absent = agent/dhcpfix wrote it last
ls -l /etc/dhcp/dhclient-enter-hooks.d/nodnsupdate # hook present + executable?
host files.boa.io 127.0.0.1 # is unbound answering?
Missing hook + file flipping after lease renewals = run dhcpfix.
dhcpfix — recovery on a live box
dhcpfix (rewritten in 5.10.3) is safe to run on a production server. It
detects the active DHCP client (dhclient, dhcpcd, NetworkManager — the
target stack is Devuan/SysVinit + ISC dhclient; the others are handled for
completeness) and then does exactly two things:
- Stops the client from writing
resolv.conf. For dhclient it installs only the samenodnsupdateno-op hook (0755) — no awk surgery on/etc/dhclient.conf's request line, no lease release/renew, and it never touches/etc/network/interfaces. The hook is designed to take effect at the next natural RENEW. For dhcpcd it ensuresnohook resolv.confin/etc/dhcpcd.conf(applied on the next dhcpcd cycle, no forced restart); for NetworkManager it setsdns=noneunder[main]and reloads (not restarts) the service. - Writes the canonical nameserver set once —
127.0.0.1plus the three public fallbacks —0644and deliberately mutable (nochattr +i), so BOA stays the owner. The file carries no### BOA-DNS-Config ###header, so the next agent run re-stamps it into full canonical form — expected, not drift. The file is correct immediately, even before the hook bites.
The configs it edits (/etc/dhcpcd.conf, NetworkManager.conf) and the
replaced resolv.conf are first backed up as <file>.bak.<epoch>. Set
_DEBUG_MODE=YES in the environment for verbose [dhcpfix] output. The tool
is fleet-deployed to /opt/local/bin/dhcpfix via serial-gated
_fetch_versioned with a --guard pgrep check that never clobbers a running copy.
Related
- Auto-healing — the
unbound.shwatchdog independently restarts unbound and rewrites a BOA-taggedresolv.confwhen its liveness lookup fails. - OS lifecycle — the install/upgrade runs that carry
the full bootstrap tier (hook +
chattr +i). - IP-blocked recovery — the other "box unreachable / half-working" recovery procedure in this chapter.