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) {