| // Copyright 2012 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package net |
| |
| import ( |
| "context" |
| "internal/nettrace" |
| "internal/singleflight" |
| "net/netip" |
| "sync" |
| ) |
| |
| // protocols contains minimal mappings between internet protocol |
| // names and numbers for platforms that don't have a complete list of |
| // protocol numbers. |
| // |
| // See https://www.iana.org/assignments/protocol-numbers |
| // |
| // On Unix, this map is augmented by readProtocols via lookupProtocol. |
| var protocols = map[string]int{ |
| "icmp": 1, |
| "igmp": 2, |
| "tcp": 6, |
| "udp": 17, |
| "ipv6-icmp": 58, |
| } |
| |
| // services contains minimal mappings between services names and port |
| // numbers for platforms that don't have a complete list of port numbers. |
| // |
| // See https://www.iana.org/assignments/service-names-port-numbers |
| // |
| // On Unix, this map is augmented by readServices via goLookupPort. |
| var services = map[string]map[string]int{ |
| "udp": { |
| "domain": 53, |
| }, |
| "tcp": { |
| "ftp": 21, |
| "ftps": 990, |
| "gopher": 70, // ʕ◔ϖ◔ʔ |
| "http": 80, |
| "https": 443, |
| "imap2": 143, |
| "imap3": 220, |
| "imaps": 993, |
| "pop3": 110, |
| "pop3s": 995, |
| "smtp": 25, |
| "ssh": 22, |
| "telnet": 23, |
| }, |
| } |
| |
| // dnsWaitGroup can be used by tests to wait for all DNS goroutines to |
| // complete. This avoids races on the test hooks. |
| var dnsWaitGroup sync.WaitGroup |
| |
| const maxProtoLength = len("RSVP-E2E-IGNORE") + 10 // with room to grow |
| |
| func lookupProtocolMap(name string) (int, error) { |
| var lowerProtocol [maxProtoLength]byte |
| n := copy(lowerProtocol[:], name) |
| lowerASCIIBytes(lowerProtocol[:n]) |
| proto, found := protocols[string(lowerProtocol[:n])] |
| if !found || n != len(name) { |
| return 0, &AddrError{Err: "unknown IP protocol specified", Addr: name} |
| } |
| return proto, nil |
| } |
| |
| // maxPortBufSize is the longest reasonable name of a service |
| // (non-numeric port). |
| // Currently the longest known IANA-unregistered name is |
| // "mobility-header", so we use that length, plus some slop in case |
| // something longer is added in the future. |
| const maxPortBufSize = len("mobility-header") + 10 |
| |
| func lookupPortMap(network, service string) (port int, error error) { |
| switch network { |
| case "tcp4", "tcp6": |
| network = "tcp" |
| case "udp4", "udp6": |
| network = "udp" |
| } |
| |
| if m, ok := services[network]; ok { |
| var lowerService [maxPortBufSize]byte |
| n := copy(lowerService[:], service) |
| lowerASCIIBytes(lowerService[:n]) |
| if port, ok := m[string(lowerService[:n])]; ok && n == len(service) { |
| return port, nil |
| } |
| } |
| return 0, &AddrError{Err: "unknown port", Addr: network + "/" + service} |
| } |
| |
| // ipVersion returns the provided network's IP version: '4', '6' or 0 |
| // if network does not end in a '4' or '6' byte. |
| func ipVersion(network string) byte { |
| if network == "" { |
| return 0 |
| } |
| n := network[len(network)-1] |
| if n != '4' && n != '6' { |
| n = 0 |
| } |
| return n |
| } |
| |
| // DefaultResolver is the resolver used by the package-level Lookup |
| // functions and by Dialers without a specified Resolver. |
| var DefaultResolver = &Resolver{} |
| |
| // A Resolver looks up names and numbers. |
| // |
| // A nil *Resolver is equivalent to a zero Resolver. |
| type Resolver struct { |
| // PreferGo controls whether Go's built-in DNS resolver is preferred |
| // on platforms where it's available. It is equivalent to setting |
| // GODEBUG=netdns=go, but scoped to just this resolver. |
| PreferGo bool |
| |
| // StrictErrors controls the behavior of temporary errors |
| // (including timeout, socket errors, and SERVFAIL) when using |
| // Go's built-in resolver. For a query composed of multiple |
| // sub-queries (such as an A+AAAA address lookup, or walking the |
| // DNS search list), this option causes such errors to abort the |
| // whole query instead of returning a partial result. This is |
| // not enabled by default because it may affect compatibility |
| // with resolvers that process AAAA queries incorrectly. |
| StrictErrors bool |
| |
| // Dial optionally specifies an alternate dialer for use by |
| // Go's built-in DNS resolver to make TCP and UDP connections |
| // to DNS services. The host in the address parameter will |
| // always be a literal IP address and not a host name, and the |
| // port in the address parameter will be a literal port number |
| // and not a service name. |
| // If the Conn returned is also a PacketConn, sent and received DNS |
| // messages must adhere to RFC 1035 section 4.2.1, "UDP usage". |
| // Otherwise, DNS messages transmitted over Conn must adhere |
| // to RFC 7766 section 5, "Transport Protocol Selection". |
| // If nil, the default dialer is used. |
| Dial func(ctx context.Context, network, address string) (Conn, error) |
| |
| // lookupGroup merges LookupIPAddr calls together for lookups for the same |
| // host. The lookupGroup key is the LookupIPAddr.host argument. |
| // The return values are ([]IPAddr, error). |
| lookupGroup singleflight.Group |
| |
| // TODO(bradfitz): optional interface impl override hook |
| // TODO(bradfitz): Timeout time.Duration? |
| } |
| |
| func (r *Resolver) preferGo() bool { return r != nil && r.PreferGo } |
| func (r *Resolver) strictErrors() bool { return r != nil && r.StrictErrors } |
| |
| func (r *Resolver) getLookupGroup() *singleflight.Group { |
| if r == nil { |
| return &DefaultResolver.lookupGroup |
| } |
| return &r.lookupGroup |
| } |
| |
| // LookupHost looks up the given host using the local resolver. |
| // It returns a slice of that host's addresses. |
| // |
| // LookupHost uses context.Background internally; to specify the context, use |
| // Resolver.LookupHost. |
| func LookupHost(host string) (addrs []string, err error) { |
| return DefaultResolver.LookupHost(context.Background(), host) |
| } |
| |
| // LookupHost looks up the given host using the local resolver. |
| // It returns a slice of that host's addresses. |
| func (r *Resolver) LookupHost(ctx context.Context, host string) (addrs []string, err error) { |
| // Make sure that no matter what we do later, host=="" is rejected. |
| // parseIP, for example, does accept empty strings. |
| if host == "" { |
| return nil, &DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true} |
| } |
| if ip, _ := parseIPZone(host); ip != nil { |
| return []string{host}, nil |
| } |
| return r.lookupHost(ctx, host) |
| } |
| |
| // LookupIP looks up host using the local resolver. |
| // It returns a slice of that host's IPv4 and IPv6 addresses. |
| func LookupIP(host string) ([]IP, error) { |
| addrs, err := DefaultResolver.LookupIPAddr(context.Background(), host) |
| if err != nil { |
| return nil, err |
| } |
| ips := make([]IP, len(addrs)) |
| for i, ia := range addrs { |
| ips[i] = ia.IP |
| } |
| return ips, nil |
| } |
| |
| // LookupIPAddr looks up host using the local resolver. |
| // It returns a slice of that host's IPv4 and IPv6 addresses. |
| func (r *Resolver) LookupIPAddr(ctx context.Context, host string) ([]IPAddr, error) { |
| return r.lookupIPAddr(ctx, "ip", host) |
| } |
| |
| // LookupIP looks up host for the given network using the local resolver. |
| // It returns a slice of that host's IP addresses of the type specified by |
| // network. |
| // network must be one of "ip", "ip4" or "ip6". |
| func (r *Resolver) LookupIP(ctx context.Context, network, host string) ([]IP, error) { |
| afnet, _, err := parseNetwork(ctx, network, false) |
| if err != nil { |
| return nil, err |
| } |
| switch afnet { |
| case "ip", "ip4", "ip6": |
| default: |
| return nil, UnknownNetworkError(network) |
| } |
| addrs, err := r.internetAddrList(ctx, afnet, host) |
| if err != nil { |
| return nil, err |
| } |
| ips := make([]IP, 0, len(addrs)) |
| for _, addr := range addrs { |
| ips = append(ips, addr.(*IPAddr).IP) |
| } |
| return ips, nil |
| } |
| |
| // LookupNetIP looks up host using the local resolver. |
| // It returns a slice of that host's IP addresses of the type specified by |
| // network. |
| // The network must be one of "ip", "ip4" or "ip6". |
| func (r *Resolver) LookupNetIP(ctx context.Context, network, host string) ([]netip.Addr, error) { |
| // TODO(bradfitz): make this efficient, making the internal net package |
| // type throughout be netip.Addr and only converting to the net.IP slice |
| // version at the edge. But for now (2021-10-20), this is a wrapper around |
| // the old way. |
| ips, err := r.LookupIP(ctx, network, host) |
| if err != nil { |
| return nil, err |
| } |
| ret := make([]netip.Addr, 0, len(ips)) |
| for _, ip := range ips { |
| if a, ok := netip.AddrFromSlice(ip); ok { |
| ret = append(ret, a) |
| } |
| } |
| return ret, nil |
| } |
| |
| // onlyValuesCtx is a context that uses an underlying context |
| // for value lookup if the underlying context hasn't yet expired. |
| type onlyValuesCtx struct { |
| context.Context |
| lookupValues context.Context |
| } |
| |
| var _ context.Context = (*onlyValuesCtx)(nil) |
| |
| // Value performs a lookup if the original context hasn't expired. |
| func (ovc *onlyValuesCtx) Value(key any) any { |
| select { |
| case <-ovc.lookupValues.Done(): |
| return nil |
| default: |
| return ovc.lookupValues.Value(key) |
| } |
| } |
| |
| // withUnexpiredValuesPreserved returns a context.Context that only uses lookupCtx |
| // for its values, otherwise it is never canceled and has no deadline. |
| // If the lookup context expires, any looked up values will return nil. |
| // See Issue 28600. |
| func withUnexpiredValuesPreserved(lookupCtx context.Context) context.Context { |
| return &onlyValuesCtx{Context: context.Background(), lookupValues: lookupCtx} |
| } |
| |
| // lookupIPAddr looks up host using the local resolver and particular network. |
| // It returns a slice of that host's IPv4 and IPv6 addresses. |
| func (r *Resolver) lookupIPAddr(ctx context.Context, network, host string) ([]IPAddr, error) { |
| // Make sure that no matter what we do later, host=="" is rejected. |
| // parseIP, for example, does accept empty strings. |
| if host == "" { |
| return nil, &DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true} |
| } |
| if ip, zone := parseIPZone(host); ip != nil { |
| return []IPAddr{{IP: ip, Zone: zone}}, nil |
| } |
| trace, _ := ctx.Value(nettrace.TraceKey{}).(*nettrace.Trace) |
| if trace != nil && trace.DNSStart != nil { |
| trace.DNSStart(host) |
| } |
| // The underlying resolver func is lookupIP by default but it |
| // can be overridden by tests. This is needed by net/http, so it |
| // uses a context key instead of unexported variables. |
| resolverFunc := r.lookupIP |
| if alt, _ := ctx.Value(nettrace.LookupIPAltResolverKey{}).(func(context.Context, string, string) ([]IPAddr, error)); alt != nil { |
| resolverFunc = alt |
| } |
| |
| // We don't want a cancellation of ctx to affect the |
| // lookupGroup operation. Otherwise if our context gets |
| // canceled it might cause an error to be returned to a lookup |
| // using a completely different context. However we need to preserve |
| // only the values in context. See Issue 28600. |
| lookupGroupCtx, lookupGroupCancel := context.WithCancel(withUnexpiredValuesPreserved(ctx)) |
| |
| lookupKey := network + "\000" + host |
| dnsWaitGroup.Add(1) |
| ch, called := r.getLookupGroup().DoChan(lookupKey, func() (any, error) { |
| defer dnsWaitGroup.Done() |
| return testHookLookupIP(lookupGroupCtx, resolverFunc, network, host) |
| }) |
| if !called { |
| dnsWaitGroup.Done() |
| } |
| |
| select { |
| case <-ctx.Done(): |
| // Our context was canceled. If we are the only |
| // goroutine looking up this key, then drop the key |
| // from the lookupGroup and cancel the lookup. |
| // If there are other goroutines looking up this key, |
| // let the lookup continue uncanceled, and let later |
| // lookups with the same key share the result. |
| // See issues 8602, 20703, 22724. |
| if r.getLookupGroup().ForgetUnshared(lookupKey) { |
| lookupGroupCancel() |
| } else { |
| go func() { |
| <-ch |
| lookupGroupCancel() |
| }() |
| } |
| ctxErr := ctx.Err() |
| err := &DNSError{ |
| Err: mapErr(ctxErr).Error(), |
| Name: host, |
| IsTimeout: ctxErr == context.DeadlineExceeded, |
| } |
| if trace != nil && trace.DNSDone != nil { |
| trace.DNSDone(nil, false, err) |
| } |
| return nil, err |
| case r := <-ch: |
| lookupGroupCancel() |
| err := r.Err |
| if err != nil { |
| if _, ok := err.(*DNSError); !ok { |
| isTimeout := false |
| if err == context.DeadlineExceeded { |
| isTimeout = true |
| } else if terr, ok := err.(timeout); ok { |
| isTimeout = terr.Timeout() |
| } |
| err = &DNSError{ |
| Err: err.Error(), |
| Name: host, |
| IsTimeout: isTimeout, |
| } |
| } |
| } |
| if trace != nil && trace.DNSDone != nil { |
| addrs, _ := r.Val.([]IPAddr) |
| trace.DNSDone(ipAddrsEface(addrs), r.Shared, err) |
| } |
| return lookupIPReturn(r.Val, err, r.Shared) |
| } |
| } |
| |
| // lookupIPReturn turns the return values from singleflight.Do into |
| // the return values from LookupIP. |
| func lookupIPReturn(addrsi any, err error, shared bool) ([]IPAddr, error) { |
| if err != nil { |
| return nil, err |
| } |
| addrs := addrsi.([]IPAddr) |
| if shared { |
| clone := make([]IPAddr, len(addrs)) |
| copy(clone, addrs) |
| addrs = clone |
| } |
| return addrs, nil |
| } |
| |
| // ipAddrsEface returns an empty interface slice of addrs. |
| func ipAddrsEface(addrs []IPAddr) []any { |
| s := make([]any, len(addrs)) |
| for i, v := range addrs { |
| s[i] = v |
| } |
| return s |
| } |
| |
| // LookupPort looks up the port for the given network and service. |
| // |
| // LookupPort uses context.Background internally; to specify the context, use |
| // Resolver.LookupPort. |
| func LookupPort(network, service string) (port int, err error) { |
| return DefaultResolver.LookupPort(context.Background(), network, service) |
| } |
| |
| // LookupPort looks up the port for the given network and service. |
| func (r *Resolver) LookupPort(ctx context.Context, network, service string) (port int, err error) { |
| port, needsLookup := parsePort(service) |
| if needsLookup { |
| switch network { |
| case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6": |
| case "": // a hint wildcard for Go 1.0 undocumented behavior |
| network = "ip" |
| default: |
| return 0, &AddrError{Err: "unknown network", Addr: network} |
| } |
| port, err = r.lookupPort(ctx, network, service) |
| if err != nil { |
| return 0, err |
| } |
| } |
| if 0 > port || port > 65535 { |
| return 0, &AddrError{Err: "invalid port", Addr: service} |
| } |
| return port, nil |
| } |
| |
| // LookupCNAME returns the canonical name for the given host. |
| // Callers that do not care about the canonical name can call |
| // LookupHost or LookupIP directly; both take care of resolving |
| // the canonical name as part of the lookup. |
| // |
| // A canonical name is the final name after following zero |
| // or more CNAME records. |
| // LookupCNAME does not return an error if host does not |
| // contain DNS "CNAME" records, as long as host resolves to |
| // address records. |
| // |
| // The returned canonical name is validated to be a properly |
| // formatted presentation-format domain name. |
| // |
| // LookupCNAME uses context.Background internally; to specify the context, use |
| // Resolver.LookupCNAME. |
| func LookupCNAME(host string) (cname string, err error) { |
| return DefaultResolver.LookupCNAME(context.Background(), host) |
| } |
| |
| // LookupCNAME returns the canonical name for the given host. |
| // Callers that do not care about the canonical name can call |
| // LookupHost or LookupIP directly; both take care of resolving |
| // the canonical name as part of the lookup. |
| // |
| // A canonical name is the final name after following zero |
| // or more CNAME records. |
| // LookupCNAME does not return an error if host does not |
| // contain DNS "CNAME" records, as long as host resolves to |
| // address records. |
| // |
| // The returned canonical name is validated to be a properly |
| // formatted presentation-format domain name. |
| func (r *Resolver) LookupCNAME(ctx context.Context, host string) (string, error) { |
| cname, err := r.lookupCNAME(ctx, host) |
| if err != nil { |
| return "", err |
| } |
| if !isDomainName(cname) { |
| return "", &DNSError{Err: errMalformedDNSRecordsDetail, Name: host} |
| } |
| return cname, nil |
| } |
| |
| // LookupSRV tries to resolve an SRV query of the given service, |
| // protocol, and domain name. The proto is "tcp" or "udp". |
| // The returned records are sorted by priority and randomized |
| // by weight within a priority. |
| // |
| // LookupSRV constructs the DNS name to look up following RFC 2782. |
| // That is, it looks up _service._proto.name. To accommodate services |
| // publishing SRV records under non-standard names, if both service |
| // and proto are empty strings, LookupSRV looks up name directly. |
| // |
| // The returned service names are validated to be properly |
| // formatted presentation-format domain names. If the response contains |
| // invalid names, those records are filtered out and an error |
| // will be returned alongside the remaining results, if any. |
| func LookupSRV(service, proto, name string) (cname string, addrs []*SRV, err error) { |
| return DefaultResolver.LookupSRV(context.Background(), service, proto, name) |
| } |
| |
| // LookupSRV tries to resolve an SRV query of the given service, |
| // protocol, and domain name. The proto is "tcp" or "udp". |
| // The returned records are sorted by priority and randomized |
| // by weight within a priority. |
| // |
| // LookupSRV constructs the DNS name to look up following RFC 2782. |
| // That is, it looks up _service._proto.name. To accommodate services |
| // publishing SRV records under non-standard names, if both service |
| // and proto are empty strings, LookupSRV looks up name directly. |
| // |
| // The returned service names are validated to be properly |
| // formatted presentation-format domain names. If the response contains |
| // invalid names, those records are filtered out and an error |
| // will be returned alongside the remaining results, if any. |
| func (r *Resolver) LookupSRV(ctx context.Context, service, proto, name string) (string, []*SRV, error) { |
| cname, addrs, err := r.lookupSRV(ctx, service, proto, name) |
| if err != nil { |
| return "", nil, err |
| } |
| if cname != "" && !isDomainName(cname) { |
| return "", nil, &DNSError{Err: "SRV header name is invalid", Name: name} |
| } |
| filteredAddrs := make([]*SRV, 0, len(addrs)) |
| for _, addr := range addrs { |
| if addr == nil { |
| continue |
| } |
| if !isDomainName(addr.Target) { |
| continue |
| } |
| filteredAddrs = append(filteredAddrs, addr) |
| } |
| if len(addrs) != len(filteredAddrs) { |
| return cname, filteredAddrs, &DNSError{Err: errMalformedDNSRecordsDetail, Name: name} |
| } |
| return cname, filteredAddrs, nil |
| } |
| |
| // LookupMX returns the DNS MX records for the given domain name sorted by preference. |
| // |
| // The returned mail server names are validated to be properly |
| // formatted presentation-format domain names. If the response contains |
| // invalid names, those records are filtered out and an error |
| // will be returned alongside the remaining results, if any. |
| // |
| // LookupMX uses context.Background internally; to specify the context, use |
| // Resolver.LookupMX. |
| func LookupMX(name string) ([]*MX, error) { |
| return DefaultResolver.LookupMX(context.Background(), name) |
| } |
| |
| // LookupMX returns the DNS MX records for the given domain name sorted by preference. |
| // |
| // The returned mail server names are validated to be properly |
| // formatted presentation-format domain names. If the response contains |
| // invalid names, those records are filtered out and an error |
| // will be returned alongside the remaining results, if any. |
| func (r *Resolver) LookupMX(ctx context.Context, name string) ([]*MX, error) { |
| records, err := r.lookupMX(ctx, name) |
| if err != nil { |
| return nil, err |
| } |
| filteredMX := make([]*MX, 0, len(records)) |
| for _, mx := range records { |
| if mx == nil { |
| continue |
| } |
| if !isDomainName(mx.Host) { |
| continue |
| } |
| filteredMX = append(filteredMX, mx) |
| } |
| if len(records) != len(filteredMX) { |
| return filteredMX, &DNSError{Err: errMalformedDNSRecordsDetail, Name: name} |
| } |
| return filteredMX, nil |
| } |
| |
| // LookupNS returns the DNS NS records for the given domain name. |
| // |
| // The returned name server names are validated to be properly |
| // formatted presentation-format domain names. If the response contains |
| // invalid names, those records are filtered out and an error |
| // will be returned alongside the remaining results, if any. |
| // |
| // LookupNS uses context.Background internally; to specify the context, use |
| // Resolver.LookupNS. |
| func LookupNS(name string) ([]*NS, error) { |
| return DefaultResolver.LookupNS(context.Background(), name) |
| } |
| |
| // LookupNS returns the DNS NS records for the given domain name. |
| // |
| // The returned name server names are validated to be properly |
| // formatted presentation-format domain names. If the response contains |
| // invalid names, those records are filtered out and an error |
| // will be returned alongside the remaining results, if any. |
| func (r *Resolver) LookupNS(ctx context.Context, name string) ([]*NS, error) { |
| records, err := r.lookupNS(ctx, name) |
| if err != nil { |
| return nil, err |
| } |
| filteredNS := make([]*NS, 0, len(records)) |
| for _, ns := range records { |
| if ns == nil { |
| continue |
| } |
| if !isDomainName(ns.Host) { |
| continue |
| } |
| filteredNS = append(filteredNS, ns) |
| } |
| if len(records) != len(filteredNS) { |
| return filteredNS, &DNSError{Err: errMalformedDNSRecordsDetail, Name: name} |
| } |
| return filteredNS, nil |
| } |
| |
| // LookupTXT returns the DNS TXT records for the given domain name. |
| // |
| // LookupTXT uses context.Background internally; to specify the context, use |
| // Resolver.LookupTXT. |
| func LookupTXT(name string) ([]string, error) { |
| return DefaultResolver.lookupTXT(context.Background(), name) |
| } |
| |
| // LookupTXT returns the DNS TXT records for the given domain name. |
| func (r *Resolver) LookupTXT(ctx context.Context, name string) ([]string, error) { |
| return r.lookupTXT(ctx, name) |
| } |
| |
| // LookupAddr performs a reverse lookup for the given address, returning a list |
| // of names mapping to that address. |
| // |
| // The returned names are validated to be properly formatted presentation-format |
| // domain names. If the response contains invalid names, those records are filtered |
| // out and an error will be returned alongside the remaining results, if any. |
| // |
| // When using the host C library resolver, at most one result will be |
| // returned. To bypass the host resolver, use a custom Resolver. |
| // |
| // LookupAddr uses context.Background internally; to specify the context, use |
| // Resolver.LookupAddr. |
| func LookupAddr(addr string) (names []string, err error) { |
| return DefaultResolver.LookupAddr(context.Background(), addr) |
| } |
| |
| // LookupAddr performs a reverse lookup for the given address, returning a list |
| // of names mapping to that address. |
| // |
| // The returned names are validated to be properly formatted presentation-format |
| // domain names. If the response contains invalid names, those records are filtered |
| // out and an error will be returned alongside the remaining results, if any. |
| func (r *Resolver) LookupAddr(ctx context.Context, addr string) ([]string, error) { |
| names, err := r.lookupAddr(ctx, addr) |
| if err != nil { |
| return nil, err |
| } |
| filteredNames := make([]string, 0, len(names)) |
| for _, name := range names { |
| if isDomainName(name) { |
| filteredNames = append(filteredNames, name) |
| } |
| } |
| if len(names) != len(filteredNames) { |
| return filteredNames, &DNSError{Err: errMalformedDNSRecordsDetail, Name: addr} |
| } |
| return filteredNames, nil |
| } |
| |
| // errMalformedDNSRecordsDetail is the DNSError detail which is returned when a Resolver.Lookup... |
| // method receives DNS records which contain invalid DNS names. This may be returned alongside |
| // results which have had the malformed records filtered out. |
| var errMalformedDNSRecordsDetail = "DNS response contained records which contain invalid names" |