Files
go-dns/axfr.go
T
Konrad NeitzelandCursor ad0bb4533e Document the package, exported constants, and remaining helpers.
godoc now covers ActionAdd/ActionDelete, internal helpers, and UDP-then-TCP UPDATE behaviour.

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

108 lines
2.3 KiB
Go

package godns
import (
"context"
"fmt"
"strings"
"time"
"github.com/miekg/dns"
)
// TransferResult is a completed AXFR plus SOA serial metadata.
type TransferResult struct {
Zone Zone
Serial uint32
}
// AXFR fetches the full zone over TCP, signed with the session TSIG key.
func (c *Client) AXFR(ctx context.Context) (*TransferResult, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
zone := dns.Fqdn(c.zone)
msg := new(dns.Msg)
msg.SetAxfr(zone)
msg.SetTsig(dns.Fqdn(c.tsig.Name), tsigAlgorithm(c.tsig.Algorithm), 300, time.Now().Unix())
tr := &dns.Transfer{
TsigSecret: map[string]string{dns.Fqdn(c.tsig.Name): c.tsig.Secret},
DialTimeout: timeoutFrom(ctx),
ReadTimeout: timeoutFrom(ctx),
}
envCh, err := tr.In(msg, c.server)
if err != nil {
return nil, fmt.Errorf("AXFR to %s: %w", c.server, err)
}
var records []Record
var serial uint32
sawSOA := false
for env := range envCh {
if env.Error != nil {
return nil, fmt.Errorf("AXFR stream: %w", env.Error)
}
for _, rr := range env.RR {
if soa, ok := rr.(*dns.SOA); ok {
if sawSOA {
continue // trailing SOA ends the transfer
}
sawSOA = true
serial = soa.Serial
}
rec, err := fromRR(rr)
if err != nil {
return nil, err
}
records = append(records, rec)
}
}
if !sawSOA {
return nil, fmt.Errorf("AXFR returned no SOA for %s", zone)
}
return &TransferResult{
Zone: Zone{
Origin: zone,
Serial: serial,
Records: records,
},
Serial: serial,
}, nil
}
// tsigAlgorithm maps a BIND algorithm name to miekg/dns constants.
func tsigAlgorithm(name string) string {
trimmed := strings.ToLower(strings.TrimSpace(name))
trimmed = strings.TrimSuffix(trimmed, ".")
switch trimmed {
case "hmac-md5":
return dns.HmacMD5
case "hmac-sha1":
return dns.HmacSHA1
case "hmac-sha224":
return dns.HmacSHA224
case "hmac-sha256":
return dns.HmacSHA256
case "hmac-sha384":
return dns.HmacSHA384
case "hmac-sha512":
return dns.HmacSHA512
default:
return dns.Fqdn(trimmed)
}
}
// timeoutFrom uses the context deadline, or 30s, with a 1s floor.
func timeoutFrom(ctx context.Context) time.Duration {
deadline, ok := ctx.Deadline()
if !ok {
return 30 * time.Second
}
d := time.Until(deadline)
if d < time.Second {
return time.Second
}
return d
}