Files
go-dns/update_test.go
T
Konrad NeitzelandCursor 7055eac0ae Implement AXFR over TCP and RFC 2136 UPDATE with caller TSIG.
Callers get a session API, typed RR builders, and BIND testcontainer proof so CheckDNS can stay off the DNS wire.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-25 20:44:44 +02:00

126 lines
3.0 KiB
Go

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