daemon: doctor passes vm dns when banger itself owns the port
The previous check tried to bind 127.0.0.1:42069 and warned on 'address already in use' — which is exactly the state when the banger daemon is running, the case the user ran 'doctor' to confirm. The warning was actively misleading. Now, on 'address already in use', probe the listener with a *.vm DNS query that only banger's vmdns server answers authoritatively (NXDOMAIN with Authoritative=true). If the shape matches we pass; if the port is held by something else we still warn. Tests cover both branches: a real vmdns server is accepted, and a silent UDP listener on the same port is rejected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
35bfac3f13
commit
3ec357090a
2 changed files with 56 additions and 1 deletions
|
|
@ -7,6 +7,9 @@ import (
|
|||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"banger/internal/firecracker"
|
||||
"banger/internal/guestconfig"
|
||||
|
|
@ -282,7 +285,15 @@ func (dnsCapability) AddDoctorChecks(_ context.Context, report *system.Report) {
|
|||
conn, err := net.ListenPacket("udp", vmdns.DefaultListenAddr)
|
||||
if err != nil {
|
||||
if strings.Contains(strings.ToLower(err.Error()), "address already in use") {
|
||||
report.AddWarn("feature vm dns", "listener address "+vmdns.DefaultListenAddr+" is already in use")
|
||||
// "Already in use" is the expected state when banger's own
|
||||
// daemon is running. Probe the listener with a *.vm query
|
||||
// the banger DNS server is the only thing on the host
|
||||
// authoritative for, and pass if the response shape matches.
|
||||
if probeBangerDNS(vmdns.DefaultListenAddr) {
|
||||
report.AddPass("feature vm dns", "banger DNS server is already serving "+vmdns.DefaultListenAddr)
|
||||
return
|
||||
}
|
||||
report.AddWarn("feature vm dns", "listener address "+vmdns.DefaultListenAddr+" is held by another process")
|
||||
return
|
||||
}
|
||||
report.AddFail("feature vm dns", "cannot bind "+vmdns.DefaultListenAddr+": "+err.Error())
|
||||
|
|
@ -292,6 +303,22 @@ func (dnsCapability) AddDoctorChecks(_ context.Context, report *system.Report) {
|
|||
report.AddPass("feature vm dns", "listener can bind "+vmdns.DefaultListenAddr)
|
||||
}
|
||||
|
||||
// probeBangerDNS returns true iff a UDP DNS query to addr is answered
|
||||
// by something that behaves like banger's vmdns server: a *.vm name
|
||||
// produces an authoritative NXDOMAIN. Any other listener (a stub
|
||||
// resolver, a different DNS server) either refuses, recurses, or
|
||||
// returns non-authoritative — all distinguishable from this probe.
|
||||
func probeBangerDNS(addr string) bool {
|
||||
client := &dns.Client{Net: "udp", Timeout: 500 * time.Millisecond}
|
||||
req := new(dns.Msg)
|
||||
req.SetQuestion("doctor-probe-not-a-real-vm.vm.", dns.TypeA)
|
||||
resp, _, err := client.Exchange(req, addr)
|
||||
if err != nil || resp == nil {
|
||||
return false
|
||||
}
|
||||
return resp.Authoritative && resp.Rcode == dns.RcodeNameError
|
||||
}
|
||||
|
||||
// natCapability sets up host-side NAT so guest traffic can reach the
|
||||
// outside world. Needs VMService (tap lookup + aliveness) and
|
||||
// HostNetwork (NAT rules), plus the daemon logger for the cleanup
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package daemon
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
|
|
@ -10,6 +11,7 @@ import (
|
|||
"banger/internal/guestconfig"
|
||||
"banger/internal/model"
|
||||
"banger/internal/system"
|
||||
"banger/internal/vmdns"
|
||||
)
|
||||
|
||||
type testCapability struct {
|
||||
|
|
@ -146,6 +148,32 @@ func TestContributeHooksPopulateGuestAndMachineConfig(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestProbeBangerDNSAcceptsRealServer(t *testing.T) {
|
||||
server, err := vmdns.New("127.0.0.1:0", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("vmdns.New: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = server.Close() })
|
||||
|
||||
if !probeBangerDNS(server.Addr()) {
|
||||
t.Fatal("probeBangerDNS rejected the real banger DNS server")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProbeBangerDNSRejectsSilentListener(t *testing.T) {
|
||||
// A UDP listener that drops every datagram. The probe should
|
||||
// time out and return false — i.e. "this is not banger".
|
||||
conn, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("ListenPacket: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = conn.Close() })
|
||||
|
||||
if probeBangerDNS(conn.LocalAddr().String()) {
|
||||
t.Fatal("probeBangerDNS accepted a silent non-DNS listener")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultCapabilitiesInOrder(t *testing.T) {
|
||||
d := &Daemon{}
|
||||
wireServices(d)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue