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>
This commit is contained in:
Konrad Neitzel
2026-08-25 20:44:44 +02:00
co-authored by Cursor
parent afbe268cb5
commit 7055eac0ae
14 changed files with 1229 additions and 2 deletions
+177
View File
@@ -0,0 +1,177 @@
package godns_test
import (
"net"
"strings"
"testing"
godns "gitea.neitzel.de/konrad/go-dns"
)
func TestABuilder(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)
}
if rec.Owner != "www.example.test." {
t.Fatalf("owner: %q", rec.Owner)
}
if rec.Type != "A" {
t.Fatalf("type: %q", rec.Type)
}
if rec.TTL != 300 {
t.Fatalf("ttl: %d", rec.TTL)
}
if rec.RData != "192.0.2.10" {
t.Fatalf("rdata: %q", rec.RData)
}
if rec.Class != "IN" {
t.Fatalf("class: %q", rec.Class)
}
}
func TestAAAABuilder(t *testing.T) {
t.Parallel()
rec, err := godns.AAAA("www.example.test", 300, net.ParseIP("2001:db8::10"))
if err != nil {
t.Fatal(err)
}
if rec.Type != "AAAA" {
t.Fatalf("type: %q", rec.Type)
}
if rec.RData != "2001:db8::10" {
t.Fatalf("rdata: %q", rec.RData)
}
}
func TestMXBuilder(t *testing.T) {
t.Parallel()
rec, err := godns.MX("example.test", 3600, 10, "mail.example.test")
if err != nil {
t.Fatal(err)
}
if rec.Type != "MX" {
t.Fatalf("type: %q", rec.Type)
}
if rec.RData != "10 mail.example.test." {
t.Fatalf("rdata: %q", rec.RData)
}
}
func TestTXTBuilder(t *testing.T) {
t.Parallel()
rec, err := godns.TXT("example.test", 3600, "v=spf1 -all")
if err != nil {
t.Fatal(err)
}
if rec.Type != "TXT" {
t.Fatalf("type: %q", rec.Type)
}
if rec.RData != `"v=spf1 -all"` {
t.Fatalf("rdata: %q", rec.RData)
}
}
func TestCNAMEBuilder(t *testing.T) {
t.Parallel()
rec, err := godns.CNAME("www.example.test", 3600, "example.test")
if err != nil {
t.Fatal(err)
}
if rec.Type != "CNAME" {
t.Fatalf("type: %q", rec.Type)
}
if rec.RData != "example.test." {
t.Fatalf("rdata: %q", rec.RData)
}
}
func TestNSBuilder(t *testing.T) {
t.Parallel()
rec, err := godns.NS("example.test", 3600, "ns.example.test")
if err != nil {
t.Fatal(err)
}
if rec.Type != "NS" {
t.Fatalf("type: %q", rec.Type)
}
if rec.RData != "ns.example.test." {
t.Fatalf("rdata: %q", rec.RData)
}
}
func TestSRVBuilder(t *testing.T) {
t.Parallel()
rec, err := godns.SRV("_sip._tcp.example.test", 3600, 10, 20, 5060, "sip.example.test")
if err != nil {
t.Fatal(err)
}
if rec.Type != "SRV" {
t.Fatalf("type: %q", rec.Type)
}
if rec.RData != "10 20 5060 sip.example.test." {
t.Fatalf("rdata: %q", rec.RData)
}
}
func TestPTRBuilder(t *testing.T) {
t.Parallel()
rec, err := godns.PTR("1.2.0.192.in-addr.arpa", 3600, "www.example.test")
if err != nil {
t.Fatal(err)
}
if rec.Type != "PTR" {
t.Fatalf("type: %q", rec.Type)
}
if rec.RData != "www.example.test." {
t.Fatalf("rdata: %q", rec.RData)
}
}
func TestBuilderRejectsNonASCIIOwner(t *testing.T) {
t.Parallel()
_, err := godns.A("wüw.example.test", 300, net.ParseIP("192.0.2.10"))
if err == nil {
t.Fatal("expected error")
}
if !strings.Contains(strings.ToLower(err.Error()), "ascii") {
t.Fatalf("error should mention ASCII, got %v", err)
}
}
func TestZoneGroupsRRSets(t *testing.T) {
t.Parallel()
a1, err := godns.A("www.example.test", 300, net.ParseIP("192.0.2.10"))
if err != nil {
t.Fatal(err)
}
a2, err := godns.A("www.example.test", 300, net.ParseIP("192.0.2.11"))
if err != nil {
t.Fatal(err)
}
mx, err := godns.MX("example.test", 3600, 10, "mail.example.test")
if err != nil {
t.Fatal(err)
}
zone := godns.Zone{
Origin: "example.test.",
Serial: 1,
Records: []godns.Record{a1, a2, mx},
}
sets := zone.RRSets()
if len(sets) != 2 {
t.Fatalf("expected 2 RRsets, got %d", len(sets))
}
}