package godns import ( "fmt" "net" "strings" ) // TSIG is caller-supplied key material. go-dns does not generate keys. type TSIG struct { 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. type Client struct { server string zone string tsig TSIG } // NewClient validates ASCII zone names and required TSIG fields. // server is host or host:port; port 53 is assumed when omitted. func NewClient(server, zone string, key TSIG) (*Client, error) { if strings.TrimSpace(server) == "" { return nil, fmt.Errorf("server is required") } if err := asciiName("zone", zone); err != nil { return nil, err } if err := validateTSIG(key); err != nil { return nil, err } addr, err := withDNSPort(server) if err != nil { return nil, err } 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") } if strings.TrimSpace(key.Algorithm) == "" { return fmt.Errorf("TSIG algorithm is required") } if strings.TrimSpace(key.Secret) == "" { return fmt.Errorf("TSIG secret is required") } if err := asciiName("TSIG name", key.Name); err != nil { return err } 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 { return host, nil } // net.SplitHostPort fails for bare IPv6; require brackets or a hostname/IPv4. if strings.Count(host, ":") > 1 && !strings.HasPrefix(host, "[") { return "", fmt.Errorf("IPv6 server must be [host]:port or wrapped in brackets") } 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 == "" { return fmt.Errorf("%s is required", label) } for i := 0; i < len(trimmed); i++ { if trimmed[i] > 127 { return fmt.Errorf("%s must be ASCII, got %q", label, name) } } return nil }