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>
This commit is contained in:
@@ -71,6 +71,7 @@ func (c *Client) AXFR(ctx context.Context) (*TransferResult, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tsigAlgorithm maps a BIND algorithm name to miekg/dns constants.
|
||||||
func tsigAlgorithm(name string) string {
|
func tsigAlgorithm(name string) string {
|
||||||
trimmed := strings.ToLower(strings.TrimSpace(name))
|
trimmed := strings.ToLower(strings.TrimSpace(name))
|
||||||
trimmed = strings.TrimSuffix(trimmed, ".")
|
trimmed = strings.TrimSuffix(trimmed, ".")
|
||||||
@@ -92,6 +93,7 @@ func tsigAlgorithm(name string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// timeoutFrom uses the context deadline, or 30s, with a 1s floor.
|
||||||
func timeoutFrom(ctx context.Context) time.Duration {
|
func timeoutFrom(ctx context.Context) time.Duration {
|
||||||
deadline, ok := ctx.Deadline()
|
deadline, ok := ctx.Deadline()
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import (
|
|||||||
|
|
||||||
// TSIG is caller-supplied key material. go-dns does not generate keys.
|
// TSIG is caller-supplied key material. go-dns does not generate keys.
|
||||||
type TSIG struct {
|
type TSIG struct {
|
||||||
Name string
|
Name string // key name presented to BIND
|
||||||
Algorithm string
|
Algorithm string // BIND algorithm name, e.g. hmac-sha256
|
||||||
Secret string
|
Secret string // base64 secret from the Key File
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client is a session against one nameserver and zone, authenticated with TSIG.
|
// Client is a session against one nameserver and zone, authenticated with TSIG.
|
||||||
@@ -39,6 +39,7 @@ func NewClient(server, zone string, key TSIG) (*Client, error) {
|
|||||||
return &Client{server: addr, zone: strings.TrimSpace(zone), tsig: key}, nil
|
return &Client{server: addr, zone: strings.TrimSpace(zone), tsig: key}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateTSIG requires name, algorithm, and secret, and an ASCII key name.
|
||||||
func validateTSIG(key TSIG) error {
|
func validateTSIG(key TSIG) error {
|
||||||
if strings.TrimSpace(key.Name) == "" {
|
if strings.TrimSpace(key.Name) == "" {
|
||||||
return fmt.Errorf("TSIG name is required")
|
return fmt.Errorf("TSIG name is required")
|
||||||
@@ -55,6 +56,7 @@ func validateTSIG(key TSIG) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// withDNSPort appends :53 when server has no port.
|
||||||
func withDNSPort(server string) (string, error) {
|
func withDNSPort(server string) (string, error) {
|
||||||
host := strings.TrimSpace(server)
|
host := strings.TrimSpace(server)
|
||||||
if _, _, err := net.SplitHostPort(host); err == nil {
|
if _, _, err := net.SplitHostPort(host); err == nil {
|
||||||
@@ -67,6 +69,7 @@ func withDNSPort(server string) (string, error) {
|
|||||||
return net.JoinHostPort(host, "53"), nil
|
return net.JoinHostPort(host, "53"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// asciiName rejects empty or non-ASCII DNS names.
|
||||||
func asciiName(label, name string) error {
|
func asciiName(label, name string) error {
|
||||||
trimmed := strings.TrimSpace(name)
|
trimmed := strings.TrimSpace(name)
|
||||||
if trimmed == "" {
|
if trimmed == "" {
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// Package godns is a library for authoritative DNS against BIND-compatible
|
||||||
|
// nameservers: full zone transfer (AXFR) over TCP and RFC 2136 dynamic UPDATE,
|
||||||
|
// authenticated with caller-supplied TSIG. Domain names are ASCII only in v1.
|
||||||
|
package godns
|
||||||
@@ -124,6 +124,7 @@ func PTR(owner string, ttl uint32, target string) (Record, error) {
|
|||||||
return fromRR(&dns.PTR{Hdr: rrHeader(owner, dns.TypePTR, ttl), Ptr: name})
|
return fromRR(&dns.PTR{Hdr: rrHeader(owner, dns.TypePTR, ttl), Ptr: name})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rrHeader builds an INET header. Owner ASCII is checked in fromRR.
|
||||||
func rrHeader(owner string, typ uint16, ttl uint32) dns.RR_Header {
|
func rrHeader(owner string, typ uint16, ttl uint32) dns.RR_Header {
|
||||||
name, err := fqdnASCII(owner)
|
name, err := fqdnASCII(owner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -134,6 +135,7 @@ func rrHeader(owner string, typ uint16, ttl uint32) dns.RR_Header {
|
|||||||
return dns.RR_Header{Name: name, Rrtype: typ, Class: dns.ClassINET, Ttl: ttl}
|
return dns.RR_Header{Name: name, Rrtype: typ, Class: dns.ClassINET, Ttl: ttl}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fromRR maps a miekg/dns RR onto the public Record type.
|
||||||
func fromRR(rr dns.RR) (Record, error) {
|
func fromRR(rr dns.RR) (Record, error) {
|
||||||
hdr := rr.Header()
|
hdr := rr.Header()
|
||||||
if err := asciiName("owner", strings.TrimSuffix(hdr.Name, ".")); err != nil {
|
if err := asciiName("owner", strings.TrimSuffix(hdr.Name, ".")); err != nil {
|
||||||
@@ -151,6 +153,7 @@ func fromRR(rr dns.RR) (Record, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// recordToRR encodes a Record as a miekg/dns RR for UPDATE.
|
||||||
func recordToRR(rec Record) (dns.RR, error) {
|
func recordToRR(rec Record) (dns.RR, error) {
|
||||||
if err := asciiName("owner", rec.Owner); err != nil {
|
if err := asciiName("owner", rec.Owner); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -162,6 +165,7 @@ func recordToRR(rec Record) (dns.RR, error) {
|
|||||||
return rr, nil
|
return rr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fqdnASCII validates ASCII and returns a trailing-dot FQDN.
|
||||||
func fqdnASCII(name string) (string, error) {
|
func fqdnASCII(name string) (string, error) {
|
||||||
if err := asciiName("name", name); err != nil {
|
if err := asciiName("name", name); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -169,6 +173,7 @@ func fqdnASCII(name string) (string, error) {
|
|||||||
return dns.Fqdn(strings.TrimSpace(name)), nil
|
return dns.Fqdn(strings.TrimSpace(name)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isASCII reports whether s contains only bytes <= 127.
|
||||||
func isASCII(s string) bool {
|
func isASCII(s string) bool {
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
if s[i] > 127 {
|
if s[i] > 127 {
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import (
|
|||||||
type Action int
|
type Action int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// ActionAdd inserts a record.
|
||||||
ActionAdd Action = iota
|
ActionAdd Action = iota
|
||||||
|
// ActionDelete removes a matching RR (name, type, and rdata).
|
||||||
ActionDelete
|
ActionDelete
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,7 +40,7 @@ type UpdateResult struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update applies RFC 2136 changes. It tries UDP first and retries on TCP when
|
// Update applies RFC 2136 changes. It tries UDP first and retries on TCP when
|
||||||
// the response is truncated.
|
// the UDP exchange fails or the response is truncated.
|
||||||
func (c *Client) Update(ctx context.Context, changes ...Change) (*UpdateResult, error) {
|
func (c *Client) Update(ctx context.Context, changes ...Change) (*UpdateResult, error) {
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -72,6 +74,7 @@ func (c *Client) Update(ctx context.Context, changes ...Change) (*UpdateResult,
|
|||||||
return &UpdateResult{Rcode: dns.RcodeToString[resp.Rcode]}, nil
|
return &UpdateResult{Rcode: dns.RcodeToString[resp.Rcode]}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// updateMessage builds a TSIG-signed RFC 2136 UPDATE message.
|
||||||
func (c *Client) updateMessage(changes []Change) (*dns.Msg, error) {
|
func (c *Client) updateMessage(changes []Change) (*dns.Msg, error) {
|
||||||
zone := dns.Fqdn(c.zone)
|
zone := dns.Fqdn(c.zone)
|
||||||
msg := new(dns.Msg)
|
msg := new(dns.Msg)
|
||||||
|
|||||||
Reference in New Issue
Block a user