diff --git a/axfr.go b/axfr.go index dd037f2..d6107cd 100644 --- a/axfr.go +++ b/axfr.go @@ -71,6 +71,7 @@ func (c *Client) AXFR(ctx context.Context) (*TransferResult, error) { }, 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, ".") @@ -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 { deadline, ok := ctx.Deadline() if !ok { diff --git a/client.go b/client.go index 4af226e..cca1729 100644 --- a/client.go +++ b/client.go @@ -8,9 +8,9 @@ import ( // TSIG is caller-supplied key material. go-dns does not generate keys. type TSIG struct { - Name string - Algorithm string - Secret string + Name string // key name presented to BIND + Algorithm string // BIND algorithm name, e.g. hmac-sha256 + Secret string // base64 secret from the Key File } // 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 } +// validateTSIG requires name, algorithm, and secret, and an ASCII key name. func validateTSIG(key TSIG) error { if strings.TrimSpace(key.Name) == "" { return fmt.Errorf("TSIG name is required") @@ -55,6 +56,7 @@ func validateTSIG(key TSIG) error { return nil } +// withDNSPort appends :53 when server has no port. func withDNSPort(server string) (string, error) { host := strings.TrimSpace(server) if _, _, err := net.SplitHostPort(host); err == nil { @@ -67,6 +69,7 @@ func withDNSPort(server string) (string, error) { return net.JoinHostPort(host, "53"), nil } +// asciiName rejects empty or non-ASCII DNS names. func asciiName(label, name string) error { trimmed := strings.TrimSpace(name) if trimmed == "" { diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..7f88c3d --- /dev/null +++ b/doc.go @@ -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 diff --git a/record.go b/record.go index 9d6ac80..c8cae68 100644 --- a/record.go +++ b/record.go @@ -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}) } +// rrHeader builds an INET header. Owner ASCII is checked in fromRR. func rrHeader(owner string, typ uint16, ttl uint32) dns.RR_Header { name, err := fqdnASCII(owner) 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} } +// fromRR maps a miekg/dns RR onto the public Record type. func fromRR(rr dns.RR) (Record, error) { hdr := rr.Header() if err := asciiName("owner", strings.TrimSuffix(hdr.Name, ".")); err != nil { @@ -151,6 +153,7 @@ func fromRR(rr dns.RR) (Record, error) { }, nil } +// recordToRR encodes a Record as a miekg/dns RR for UPDATE. func recordToRR(rec Record) (dns.RR, error) { if err := asciiName("owner", rec.Owner); err != nil { return nil, err @@ -162,6 +165,7 @@ func recordToRR(rec Record) (dns.RR, error) { return rr, nil } +// fqdnASCII validates ASCII and returns a trailing-dot FQDN. func fqdnASCII(name string) (string, error) { if err := asciiName("name", name); err != nil { return "", err @@ -169,6 +173,7 @@ func fqdnASCII(name string) (string, error) { return dns.Fqdn(strings.TrimSpace(name)), nil } +// isASCII reports whether s contains only bytes <= 127. func isASCII(s string) bool { for i := 0; i < len(s); i++ { if s[i] > 127 { diff --git a/update.go b/update.go index 72405dd..046ad07 100644 --- a/update.go +++ b/update.go @@ -12,7 +12,9 @@ import ( type Action int const ( + // ActionAdd inserts a record. ActionAdd Action = iota + // ActionDelete removes a matching RR (name, type, and rdata). ActionDelete ) @@ -38,7 +40,7 @@ type UpdateResult struct { } // 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) { if err := ctx.Err(); err != nil { 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 } +// updateMessage builds a TSIG-signed RFC 2136 UPDATE message. func (c *Client) updateMessage(changes []Change) (*dns.Msg, error) { zone := dns.Fqdn(c.zone) msg := new(dns.Msg)