diff --git a/internal/app/harness_test.go b/internal/app/harness_test.go index cec5f02..c8302a5 100644 --- a/internal/app/harness_test.go +++ b/internal/app/harness_test.go @@ -182,16 +182,27 @@ func newHarness(t *testing.T, mutate func(*config.Config), opts ...func(*Options return &harness{t: t, app: a, addr: dnsAddr, api: a.Addr(), key: "test-key", up: up, dir: dir, config: doc} } -// freePort returns a currently-free TCP port on the loopback interface. +// freePort returns a port that is free for both TCP and UDP on the loopback +// interface. The DNS listeners bind both on one port, and a TCP-only probe can +// hand out a port that another test's ephemeral UDP client socket is holding, +// which made the listener fail to bind and the queries see "connection refused". func freePort(t *testing.T) int { t.Helper() - ln, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("free port: %v", err) + for range 50 { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("free port: %v", err) + } + port := ln.Addr().(*net.TCPAddr).Port + pc, perr := net.ListenPacket("udp", fmt.Sprintf("127.0.0.1:%d", port)) + _ = ln.Close() + if perr == nil { + _ = pc.Close() + return port + } } - port := ln.Addr().(*net.TCPAddr).Port - _ = ln.Close() - return port + t.Fatal("no loopback port was free for TCP and UDP at once") + return 0 } func (h *harness) query(name string, qtype uint16) *dns.Msg {