fix(diagnostics): let the in-zone probes read the drop-in that holds the zone

d14354e892 added both probes reading only /etc/dnsmasq.conf, but svc-net-tor writes its onion zone to /etc/dnsmasq.d/tor-onion.conf and leaves the main file with nothing but a conf-dir pointer. Every Tor host of run 33698844864 therefore captured resolve-inzone.txt as '[no output, exit 2]' and all 471 sampler lines as inzone=unconfigured: during the resolver outage that ended Gitea#0, nobody ever asked dnsmasq for an onion name, and two investigations went without the one measurement that would have settled them.

Both probes now follow the conf-dir pointer, main file first so a lab node with its zone in the main config answers as before. Each is exercised by a test that runs it against real files rather than comparing its text, which the rescue constant had no test for at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5 committed 2026-09-03 12:50:58 +02:00
1 parent 11c98825aa
commit 56b8f1716b
4 files changed
+72 -2

No files matched your search

+4 -1
View File
@@ -12,7 +12,10 @@ sample_once() {
}
zone_probe_name() {
zone="$(sed -n 's|^[[:space:]]*address=/\([^/]*\)/.*|\1|p' "${1}" 2>/dev/null | head -n1)"
dirs="$(sed -n 's|^[[:space:]]*conf-dir=\([^,[:space:]]*\).*|\1|p' "${1}" 2>/dev/null)"
# shellcheck disable=SC2086 # a dnsmasq conf-dir path carries no whitespace
zone="$(grep -rhs '^[[:space:]]*address=/' "${1}" ${dirs} |
sed -n 's|^[[:space:]]*address=/\([^/]*\)/.*|\1|p' | head -n1)"
[ -n "${zone}" ] || return 0
printf 'rescue-probe.%s' "${zone}"
}
@@ -11,6 +11,7 @@ from __future__ import annotations
import subprocess
import tempfile
import unittest
from pathlib import Path
from utils.cache.files import PROJECT_ROOT
@@ -113,6 +114,33 @@ class ZoneProbeNameTests(unittest.TestCase):
def test_a_config_without_a_zone_yields_nothing(self):
self.assertEqual(self._zone("no-resolv\nserver=192.0.2.1\n"), "")
def test_a_zone_declared_in_a_drop_in_is_found(self):
"""svc-net-tor writes the onion zone to /etc/dnsmasq.d/tor-onion.conf
and leaves only a conf-dir pointer in the main file, so a probe that
reads the main file alone reports no zone on every Tor host."""
with tempfile.TemporaryDirectory() as tmp:
drop_in_dir = Path(tmp) / "dnsmasq.d"
drop_in_dir.mkdir()
(drop_in_dir / "tor-onion.conf").write_text(
"address=/abc123.onion/127.0.0.1\n", encoding="utf-8"
)
main = Path(tmp) / "dnsmasq.conf"
main.write_text(f"conf-dir={drop_in_dir},*.conf\n", encoding="utf-8")
proc = subprocess.run(
[
"sh",
"-c",
'DNS53_SAMPLER_LIB=1 . "$1" && zone_probe_name "$2"',
"sh",
str(SAMPLER),
str(main),
],
capture_output=True,
text=True,
check=True,
)
self.assertEqual(proc.stdout, "rescue-probe.abc123.onion")
if __name__ == "__main__":
unittest.main()
@@ -44,6 +44,42 @@ def _cp(cmd, rc=0, stdout=b"", stderr=b""):
return subprocess.CompletedProcess(cmd, rc, stdout, stderr)
class InZoneProbeTests(unittest.TestCase):
"""The probe is a shell one-liner, so run it: svc-net-tor keeps the onion
zone in a drop-in and leaves only a conf-dir pointer in the main file."""
def _zone(self, main: Path) -> str:
probe = _load()._INZONE_PROBE.replace("/etc/dnsmasq.conf", str(main))
inner = probe.split("rescue-probe.$(", 1)[1].rsplit(")", 1)[0]
proc = subprocess.run(
["sh", "-c", inner], capture_output=True, text=True, check=False
)
return proc.stdout.strip()
def test_a_zone_in_a_drop_in_is_found(self):
with tempfile.TemporaryDirectory() as tmp:
drop_ins = Path(tmp) / "dnsmasq.d"
drop_ins.mkdir()
(drop_ins / "tor-onion.conf").write_text(
"address=/abc123.onion/127.0.0.1\n", encoding="utf-8"
)
main = Path(tmp) / "dnsmasq.conf"
main.write_text(f"conf-dir={drop_ins},*.conf\n", encoding="utf-8")
self.assertEqual(self._zone(main), "abc123.onion")
def test_a_zone_in_the_main_file_still_wins(self):
with tempfile.TemporaryDirectory() as tmp:
main = Path(tmp) / "dnsmasq.conf"
main.write_text("address=/infinito.test/192.0.2.1\n", encoding="utf-8")
self.assertEqual(self._zone(main), "infinito.test")
def test_a_config_without_a_zone_yields_nothing(self):
with tempfile.TemporaryDirectory() as tmp:
main = Path(tmp) / "dnsmasq.conf"
main.write_text("no-resolv\nserver=192.0.2.1\n", encoding="utf-8")
self.assertEqual(self._zone(main), "")
class HelperTests(unittest.TestCase):
def test_sanitize_replaces_unsafe_chars(self):
mod = _load()
+4 -1
View File
@@ -42,7 +42,10 @@ _SELF_IN_CONTAINER = "/tmp/rescue-self.py" # noqa: S108 - fixed staging path in
_LOCAL_DUMPS_ENV = "INFINITO_RESCUE_LOCAL_DUMPS_DIR"
_APP_LOG_TAIL = "find /var/log /var/www /opt /srv -xdev -type f -name '*.log' -size -20971520c -mmin -360 -exec sh -c 'echo \"===== $1\"; tail -n 400 \"$1\"' _ {} ';'"
_PROBE_HOSTS = ("deb.debian.org", "ghcr.io", "repo.packagist.org")
_INZONE_PROBE = "getent hosts rescue-probe.$(awk -F/ '/^address=/{print $2;exit}' /etc/dnsmasq.conf)"
_DNSMASQ_DIRS = (
"$(sed -n 's|^ *conf-dir=\\([^,[:space:]]*\\).*|\\1|p' /etc/dnsmasq.conf)"
)
_INZONE_PROBE = f"getent hosts rescue-probe.$(grep -rhs '^ *address=/' /etc/dnsmasq.conf {_DNSMASQ_DIRS} | awk -F/ '{{print $2;exit}}')"
def runtime_bin() -> str | None: