package godns_test import ( "context" "net" "strings" "sync/atomic" "testing" "time" godns "gitea.neitzel.de/konrad/go-dns" "github.com/miekg/dns" ) func TestUpdateFallsBackToTCPWhenUDPTruncated(t *testing.T) { t.Parallel() var udpHits, tcpHits atomic.Int32 mux := dns.NewServeMux() mux.HandleFunc("example.test.", func(w dns.ResponseWriter, r *dns.Msg) { if r.IsTsig() == nil { t.Error("UPDATE request missing TSIG") } msg := new(dns.Msg) msg.SetReply(r) if _, ok := w.RemoteAddr().(*net.UDPAddr); ok { udpHits.Add(1) msg.Truncated = true _ = w.WriteMsg(msg) return } tcpHits.Add(1) _ = w.WriteMsg(msg) }) udp, tcp := startDNSServers(t, mux) client, err := godns.NewClient(udp, "example.test", fixtureTSIG()) if err != nil { t.Fatal(err) } _ = tcp rec, err := godns.A("new.example.test", 300, net.ParseIP("192.0.2.99")) if err != nil { t.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() result, err := client.Update(ctx, godns.Add(rec)) if err != nil { t.Fatal(err) } if result.Rcode != "NOERROR" { t.Fatalf("rcode: %s", result.Rcode) } if udpHits.Load() < 1 { t.Fatal("expected a UDP attempt") } if tcpHits.Load() < 1 { t.Fatal("expected TCP fallback") } } func startDNSServers(t *testing.T, handler dns.Handler) (udpAddr, tcpAddr string) { t.Helper() secrets := map[string]string{"go-dns-test.": fixtureSecret} acceptUpdate := func(dh dns.Header) dns.MsgAcceptAction { return dns.MsgAccept } udpSrv := &dns.Server{Addr: "127.0.0.1:0", Net: "udp", Handler: handler, TsigSecret: secrets, MsgAcceptFunc: acceptUpdate} udpReady := make(chan struct{}) udpSrv.NotifyStartedFunc = func() { close(udpReady) } go func() { _ = udpSrv.ListenAndServe() }() <-udpReady t.Cleanup(func() { _ = udpSrv.Shutdown() }) udpAddr = udpSrv.PacketConn.LocalAddr().String() _, port, err := net.SplitHostPort(udpAddr) if err != nil { t.Fatal(err) } tcpSrv := &dns.Server{Addr: "127.0.0.1:" + port, Net: "tcp", Handler: handler, TsigSecret: secrets, MsgAcceptFunc: acceptUpdate} tcpReady := make(chan struct{}) tcpSrv.NotifyStartedFunc = func() { close(tcpReady) } go func() { _ = tcpSrv.ListenAndServe() }() <-tcpReady t.Cleanup(func() { _ = tcpSrv.Shutdown() }) return udpAddr, tcpSrv.Listener.Addr().String() } func TestAddChangeDescribesInsert(t *testing.T) { t.Parallel() rec, err := godns.A("www.example.test", 300, net.ParseIP("192.0.2.10")) if err != nil { t.Fatal(err) } change := godns.Add(rec) if change.Action != godns.ActionAdd { t.Fatalf("action: %v", change.Action) } if change.Record.Type != "A" { t.Fatalf("record: %+v", change.Record) } } func TestDeleteChangeDescribesRemove(t *testing.T) { t.Parallel() rec, err := godns.TXT("bad.example.test", 300, "stale") if err != nil { t.Fatal(err) } change := godns.Delete(rec) if change.Action != godns.ActionDelete { t.Fatalf("action: %v", change.Action) } if !strings.Contains(change.Record.RData, "stale") { t.Fatalf("rdata: %q", change.Record.RData) } }