From 9400bab6fd41a5dfdead5e93583cf64ca0da1540 Mon Sep 17 00:00:00 2001 From: Thales Maciel Date: Fri, 1 May 2026 15:42:11 -0300 Subject: [PATCH] fix: accept host:port in validateResolverAddr; release v0.1.8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The root helper's resolver-address validator only accepted bare IPs, so `resolvectl dns 127.0.0.1:42069` — banger's own auto-wire call to point systemd-resolved at the in-process DNS server — was rejected before it ever reached resolvectl. The auto-wire is best-effort and only logs a warning on failure, so .vm resolution silently broke on the NSS path: dig @127.0.0.1 worked, curl .vm didn't. Validator now allows both bare IPs and IP:port (matching what `resolvectl dns` itself accepts), with new test coverage for the port'd form. Existing installs need a one-time `sudo banger system restart` after updating to v0.1.8 so the daemon re-runs the auto-wire with the fixed validator. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 27 +++++++++++++++++++++++++- internal/roothelper/roothelper.go | 18 +++++++++++------ internal/roothelper/roothelper_test.go | 3 +++ 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 431b24b..23bdabc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,30 @@ changed between versions. ## [Unreleased] +## [v0.1.8] - 2026-05-01 + +### Fixed + +- `.vm` resolution from the host (NSS path: curl, ssh hostname, + etc.) now works on systemd-resolved hosts. The root helper's + `validateResolverAddr` was rejecting the `host:port` form + (`127.0.0.1:42069`) that banger constructs to point resolved at the + in-process DNS server, so the auto-wire silently failed at every + daemon startup. `dig @127.0.0.1` worked because that bypasses NSS; + any tool going through glibc's resolver chain didn't. +- Validator now accepts both bare IPs and `IP:port` (matching what + `resolvectl dns` itself accepts) with new test coverage for the + port'd form. + +### Notes + +- Existing v0.1.x installs that already booted with the broken + validator have stale per-link resolved state. After updating to + v0.1.8, run `sudo banger system restart` once to re-trigger the + auto-wire, or restart the host. systemd-resolved restarts also + wipe per-link state — banger restores it on its own daemon + startup but won't re-run for an already-running daemon. + ## [v0.1.7] - 2026-05-01 ### Added @@ -250,7 +274,8 @@ root filesystem and network, and exits on demand. the swap rather than starting up against an incompatible store. - Linux only. amd64 only. KVM required. -[Unreleased]: https://git.thaloco.com/thaloco/banger/compare/v0.1.7...HEAD +[Unreleased]: https://git.thaloco.com/thaloco/banger/compare/v0.1.8...HEAD +[v0.1.8]: https://git.thaloco.com/thaloco/banger/releases/tag/v0.1.8 [v0.1.7]: https://git.thaloco.com/thaloco/banger/releases/tag/v0.1.7 [v0.1.6]: https://git.thaloco.com/thaloco/banger/releases/tag/v0.1.6 [v0.1.5]: https://git.thaloco.com/thaloco/banger/releases/tag/v0.1.5 diff --git a/internal/roothelper/roothelper.go b/internal/roothelper/roothelper.go index f164b5d..3aec14e 100644 --- a/internal/roothelper/roothelper.go +++ b/internal/roothelper/roothelper.go @@ -1296,18 +1296,24 @@ func validateIPv4(ip string) error { return nil } -// validateResolverAddr confirms s parses as an IP address (v4 or v6). -// resolvectl accepts either; reject anything that doesn't parse so a -// compromised daemon can't wedge resolved with garbage input. +// validateResolverAddr confirms s parses as an IP address, optionally +// with a ":port" suffix. resolvectl accepts both bare IPs and the +// "IP:port" form (used to point at a non-default DNS port — banger's +// in-process server binds to 127.0.0.1:42069). Reject anything that +// doesn't parse so a compromised daemon can't wedge resolved with +// garbage input. func validateResolverAddr(s string) error { s = strings.TrimSpace(s) if s == "" { return errors.New("resolver address is required") } - if net.ParseIP(s) == nil { - return fmt.Errorf("invalid resolver address %q", s) + if net.ParseIP(s) != nil { + return nil } - return nil + if host, _, err := net.SplitHostPort(s); err == nil && net.ParseIP(host) != nil { + return nil + } + return fmt.Errorf("invalid resolver address %q", s) } func validateTapName(tapName string) error { diff --git a/internal/roothelper/roothelper_test.go b/internal/roothelper/roothelper_test.go index ac698c3..441a1e4 100644 --- a/internal/roothelper/roothelper_test.go +++ b/internal/roothelper/roothelper_test.go @@ -566,8 +566,11 @@ func TestValidateResolverAddr(t *testing.T) { }{ {name: "ipv4", arg: "192.168.1.1", ok: true}, {name: "ipv6", arg: "fe80::1", ok: true}, + {name: "ipv4_with_port", arg: "127.0.0.1:42069", ok: true}, + {name: "ipv6_with_port", arg: "[fe80::1]:42069", ok: true}, {name: "empty", arg: "", ok: false}, {name: "garbage", arg: "resolver.example", ok: false}, + {name: "garbage_with_port", arg: "resolver.example:53", ok: false}, } { tc := tc t.Run(tc.name, func(t *testing.T) {