package netstack
import (
"fmt"
"net"
"strings"
"unicode"
)
const maxNameservers = 3
func NormalizeDNS(cfg DNSConfig) (DNSConfig, error) {
var out DNSConfig
for _, raw := range cfg.Nameservers {
ip := net.ParseIP(strings.TrimSpace(raw))
if ip == nil || ip.To4() == nil || ip.IsLoopback() || ip.IsUnspecified() {
return DNSConfig{}, fmt.Errorf("nameserver %q must be a reachable IPv4 address", raw)
}
out.Nameservers = append(out.Nameservers, ip.To4().String())
}
out.Nameservers = uniqueLimited(out.Nameservers, maxNameservers)
if cfg.Domain != "" {
if !validResolverToken(cfg.Domain) {
return DNSConfig{}, fmt.Errorf("invalid domain %q", cfg.Domain)
}
out.Domain = cfg.Domain
}
for _, value := range cfg.Search {
if !validResolverToken(value) {
return DNSConfig{}, fmt.Errorf("invalid search domain %q", value)
}
out.Search = append(out.Search, value)
}
out.Search = uniqueLimited(out.Search, 0)
for _, value := range cfg.Options {
if !validResolverToken(value) {
return DNSConfig{}, fmt.Errorf("invalid resolver option %q", value)
}
out.Options = append(out.Options, value)
}
out.Options = uniqueLimited(out.Options, 0)
if len(out.Search) != 0 {
out.Domain = ""
}
return out, nil
}
func validResolverToken(value string) bool {
if value == "" {
return false
}
for _, r := range value {
if unicode.IsSpace(r) || unicode.IsControl(r) {
return false
}
}
return true
}
func uniqueLimited(values []string, limit int) []string {
seen := make(map[string]struct{}, len(values))
out := make([]string, 0, len(values))
for _, value := range values {
if _, ok := seen[value]; ok {
continue
}
seen[value] = struct{}{}
out = append(out, value)
if limit > 0 && len(out) == limit {
break
}
}
return out
}