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>
126 lines
3.0 KiB
Go
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)
|
|
}
|
|
}
|