github.com/m10x/go/src@v0.0.0-20220112094212-ba61592315da/net/lookup.go (about) 1 // Copyright 2012 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package net 6 7 import ( 8 "context" 9 "internal/nettrace" 10 "internal/singleflight" 11 "net/netip" 12 "sync" 13 ) 14 15 // protocols contains minimal mappings between internet protocol 16 // names and numbers for platforms that don't have a complete list of 17 // protocol numbers. 18 // 19 // See https://www.iana.org/assignments/protocol-numbers 20 // 21 // On Unix, this map is augmented by readProtocols via lookupProtocol. 22 var protocols = map[string]int{ 23 "icmp": 1, 24 "igmp": 2, 25 "tcp": 6, 26 "udp": 17, 27 "ipv6-icmp": 58, 28 } 29 30 // services contains minimal mappings between services names and port 31 // numbers for platforms that don't have a complete list of port numbers. 32 // 33 // See https://www.iana.org/assignments/service-names-port-numbers 34 // 35 // On Unix, this map is augmented by readServices via goLookupPort. 36 var services = map[string]map[string]int{ 37 "udp": { 38 "domain": 53, 39 }, 40 "tcp": { 41 "ftp": 21, 42 "ftps": 990, 43 "gopher": 70, // ʕ◔ϖ◔ʔ 44 "http": 80, 45 "https": 443, 46 "imap2": 143, 47 "imap3": 220, 48 "imaps": 993, 49 "pop3": 110, 50 "pop3s": 995, 51 "smtp": 25, 52 "ssh": 22, 53 "telnet": 23, 54 }, 55 } 56 57 // dnsWaitGroup can be used by tests to wait for all DNS goroutines to 58 // complete. This avoids races on the test hooks. 59 var dnsWaitGroup sync.WaitGroup 60 61 const maxProtoLength = len("RSVP-E2E-IGNORE") + 10 // with room to grow 62 63 func lookupProtocolMap(name string) (int, error) { 64 var lowerProtocol [maxProtoLength]byte 65 n := copy(lowerProtocol[:], name) 66 lowerASCIIBytes(lowerProtocol[:n]) 67 proto, found := protocols[string(lowerProtocol[:n])] 68 if !found || n != len(name) { 69 return 0, &AddrError{Err: "unknown IP protocol specified", Addr: name} 70 } 71 return proto, nil 72 } 73 74 // maxPortBufSize is the longest reasonable name of a service 75 // (non-numeric port). 76 // Currently the longest known IANA-unregistered name is 77 // "mobility-header", so we use that length, plus some slop in case 78 // something longer is added in the future. 79 const maxPortBufSize = len("mobility-header") + 10 80 81 func lookupPortMap(network, service string) (port int, error error) { 82 switch network { 83 case "tcp4", "tcp6": 84 network = "tcp" 85 case "udp4", "udp6": 86 network = "udp" 87 } 88 89 if m, ok := services[network]; ok { 90 var lowerService [maxPortBufSize]byte 91 n := copy(lowerService[:], service) 92 lowerASCIIBytes(lowerService[:n]) 93 if port, ok := m[string(lowerService[:n])]; ok && n == len(service) { 94 return port, nil 95 } 96 } 97 return 0, &AddrError{Err: "unknown port", Addr: network + "/" + service} 98 } 99 100 // ipVersion returns the provided network's IP version: '4', '6' or 0 101 // if network does not end in a '4' or '6' byte. 102 func ipVersion(network string) byte { 103 if network == "" { 104 return 0 105 } 106 n := network[len(network)-1] 107 if n != '4' && n != '6' { 108 n = 0 109 } 110 return n 111 } 112 113 // DefaultResolver is the resolver used by the package-level Lookup 114 // functions and by Dialers without a specified Resolver. 115 var DefaultResolver = &Resolver{} 116 117 // A Resolver looks up names and numbers. 118 // 119 // A nil *Resolver is equivalent to a zero Resolver. 120 type Resolver struct { 121 // PreferGo controls whether Go's built-in DNS resolver is preferred 122 // on platforms where it's available. It is equivalent to setting 123 // GODEBUG=netdns=go, but scoped to just this resolver. 124 PreferGo bool 125 126 // StrictErrors controls the behavior of temporary errors 127 // (including timeout, socket errors, and SERVFAIL) when using 128 // Go's built-in resolver. For a query composed of multiple 129 // sub-queries (such as an A+AAAA address lookup, or walking the 130 // DNS search list), this option causes such errors to abort the 131 // whole query instead of returning a partial result. This is 132 // not enabled by default because it may affect compatibility 133 // with resolvers that process AAAA queries incorrectly. 134 StrictErrors bool 135 136 // Dial optionally specifies an alternate dialer for use by 137 // Go's built-in DNS resolver to make TCP and UDP connections 138 // to DNS services. The host in the address parameter will 139 // always be a literal IP address and not a host name, and the 140 // port in the address parameter will be a literal port number 141 // and not a service name. 142 // If the Conn returned is also a PacketConn, sent and received DNS 143 // messages must adhere to RFC 1035 section 4.2.1, "UDP usage". 144 // Otherwise, DNS messages transmitted over Conn must adhere 145 // to RFC 7766 section 5, "Transport Protocol Selection". 146 // If nil, the default dialer is used. 147 Dial func(ctx context.Context, network, address string) (Conn, error) 148 149 // lookupGroup merges LookupIPAddr calls together for lookups for the same 150 // host. The lookupGroup key is the LookupIPAddr.host argument. 151 // The return values are ([]IPAddr, error). 152 lookupGroup singleflight.Group 153 154 // TODO(bradfitz): optional interface impl override hook 155 // TODO(bradfitz): Timeout time.Duration? 156 } 157 158 func (r *Resolver) preferGo() bool { return r != nil && r.PreferGo } 159 func (r *Resolver) strictErrors() bool { return r != nil && r.StrictErrors } 160 161 func (r *Resolver) getLookupGroup() *singleflight.Group { 162 if r == nil { 163 return &DefaultResolver.lookupGroup 164 } 165 return &r.lookupGroup 166 } 167 168 // LookupHost looks up the given host using the local resolver. 169 // It returns a slice of that host's addresses. 170 // 171 // LookupHost uses context.Background internally; to specify the context, use 172 // Resolver.LookupHost. 173 func LookupHost(host string) (addrs []string, err error) { 174 return DefaultResolver.LookupHost(context.Background(), host) 175 } 176 177 // LookupHost looks up the given host using the local resolver. 178 // It returns a slice of that host's addresses. 179 func (r *Resolver) LookupHost(ctx context.Context, host string) (addrs []string, err error) { 180 // Make sure that no matter what we do later, host=="" is rejected. 181 // parseIP, for example, does accept empty strings. 182 if host == "" { 183 return nil, &DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true} 184 } 185 if ip, _ := parseIPZone(host); ip != nil { 186 return []string{host}, nil 187 } 188 return r.lookupHost(ctx, host) 189 } 190 191 // LookupIP looks up host using the local resolver. 192 // It returns a slice of that host's IPv4 and IPv6 addresses. 193 func LookupIP(host string) ([]IP, error) { 194 addrs, err := DefaultResolver.LookupIPAddr(context.Background(), host) 195 if err != nil { 196 return nil, err 197 } 198 ips := make([]IP, len(addrs)) 199 for i, ia := range addrs { 200 ips[i] = ia.IP 201 } 202 return ips, nil 203 } 204 205 // LookupIPAddr looks up host using the local resolver. 206 // It returns a slice of that host's IPv4 and IPv6 addresses. 207 func (r *Resolver) LookupIPAddr(ctx context.Context, host string) ([]IPAddr, error) { 208 return r.lookupIPAddr(ctx, "ip", host) 209 } 210 211 // LookupIP looks up host for the given network using the local resolver. 212 // It returns a slice of that host's IP addresses of the type specified by 213 // network. 214 // network must be one of "ip", "ip4" or "ip6". 215 func (r *Resolver) LookupIP(ctx context.Context, network, host string) ([]IP, error) { 216 afnet, _, err := parseNetwork(ctx, network, false) 217 if err != nil { 218 return nil, err 219 } 220 switch afnet { 221 case "ip", "ip4", "ip6": 222 default: 223 return nil, UnknownNetworkError(network) 224 } 225 addrs, err := r.internetAddrList(ctx, afnet, host) 226 if err != nil { 227 return nil, err 228 } 229 ips := make([]IP, 0, len(addrs)) 230 for _, addr := range addrs { 231 ips = append(ips, addr.(*IPAddr).IP) 232 } 233 return ips, nil 234 } 235 236 // LookupNetIP looks up host using the local resolver. 237 // It returns a slice of that host's IP addresses of the type specified by 238 // network. 239 // The network must be one of "ip", "ip4" or "ip6". 240 func (r *Resolver) LookupNetIP(ctx context.Context, network, host string) ([]netip.Addr, error) { 241 // TODO(bradfitz): make this efficient, making the internal net package 242 // type throughout be netip.Addr and only converting to the net.IP slice 243 // version at the edge. But for now (2021-10-20), this is a wrapper around 244 // the old way. 245 ips, err := r.LookupIP(ctx, network, host) 246 if err != nil { 247 return nil, err 248 } 249 ret := make([]netip.Addr, 0, len(ips)) 250 for _, ip := range ips { 251 if a, ok := netip.AddrFromSlice(ip); ok { 252 ret = append(ret, a) 253 } 254 } 255 return ret, nil 256 } 257 258 // onlyValuesCtx is a context that uses an underlying context 259 // for value lookup if the underlying context hasn't yet expired. 260 type onlyValuesCtx struct { 261 context.Context 262 lookupValues context.Context 263 } 264 265 var _ context.Context = (*onlyValuesCtx)(nil) 266 267 // Value performs a lookup if the original context hasn't expired. 268 func (ovc *onlyValuesCtx) Value(key any) any { 269 select { 270 case <-ovc.lookupValues.Done(): 271 return nil 272 default: 273 return ovc.lookupValues.Value(key) 274 } 275 } 276 277 // withUnexpiredValuesPreserved returns a context.Context that only uses lookupCtx 278 // for its values, otherwise it is never canceled and has no deadline. 279 // If the lookup context expires, any looked up values will return nil. 280 // See Issue 28600. 281 func withUnexpiredValuesPreserved(lookupCtx context.Context) context.Context { 282 return &onlyValuesCtx{Context: context.Background(), lookupValues: lookupCtx} 283 } 284 285 // lookupIPAddr looks up host using the local resolver and particular network. 286 // It returns a slice of that host's IPv4 and IPv6 addresses. 287 func (r *Resolver) lookupIPAddr(ctx context.Context, network, host string) ([]IPAddr, error) { 288 // Make sure that no matter what we do later, host=="" is rejected. 289 // parseIP, for example, does accept empty strings. 290 if host == "" { 291 return nil, &DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true} 292 } 293 if ip, zone := parseIPZone(host); ip != nil { 294 return []IPAddr{{IP: ip, Zone: zone}}, nil 295 } 296 trace, _ := ctx.Value(nettrace.TraceKey{}).(*nettrace.Trace) 297 if trace != nil && trace.DNSStart != nil { 298 trace.DNSStart(host) 299 } 300 // The underlying resolver func is lookupIP by default but it 301 // can be overridden by tests. This is needed by net/http, so it 302 // uses a context key instead of unexported variables. 303 resolverFunc := r.lookupIP 304 if alt, _ := ctx.Value(nettrace.LookupIPAltResolverKey{}).(func(context.Context, string, string) ([]IPAddr, error)); alt != nil { 305 resolverFunc = alt 306 } 307 308 // We don't want a cancellation of ctx to affect the 309 // lookupGroup operation. Otherwise if our context gets 310 // canceled it might cause an error to be returned to a lookup 311 // using a completely different context. However we need to preserve 312 // only the values in context. See Issue 28600. 313 lookupGroupCtx, lookupGroupCancel := context.WithCancel(withUnexpiredValuesPreserved(ctx)) 314 315 lookupKey := network + "\000" + host 316 dnsWaitGroup.Add(1) 317 ch, called := r.getLookupGroup().DoChan(lookupKey, func() (any, error) { 318 defer dnsWaitGroup.Done() 319 return testHookLookupIP(lookupGroupCtx, resolverFunc, network, host) 320 }) 321 if !called { 322 dnsWaitGroup.Done() 323 } 324 325 select { 326 case <-ctx.Done(): 327 // Our context was canceled. If we are the only 328 // goroutine looking up this key, then drop the key 329 // from the lookupGroup and cancel the lookup. 330 // If there are other goroutines looking up this key, 331 // let the lookup continue uncanceled, and let later 332 // lookups with the same key share the result. 333 // See issues 8602, 20703, 22724. 334 if r.getLookupGroup().ForgetUnshared(lookupKey) { 335 lookupGroupCancel() 336 } else { 337 go func() { 338 <-ch 339 lookupGroupCancel() 340 }() 341 } 342 ctxErr := ctx.Err() 343 err := &DNSError{ 344 Err: mapErr(ctxErr).Error(), 345 Name: host, 346 IsTimeout: ctxErr == context.DeadlineExceeded, 347 } 348 if trace != nil && trace.DNSDone != nil { 349 trace.DNSDone(nil, false, err) 350 } 351 return nil, err 352 case r := <-ch: 353 lookupGroupCancel() 354 err := r.Err 355 if err != nil { 356 if _, ok := err.(*DNSError); !ok { 357 isTimeout := false 358 if err == context.DeadlineExceeded { 359 isTimeout = true 360 } else if terr, ok := err.(timeout); ok { 361 isTimeout = terr.Timeout() 362 } 363 err = &DNSError{ 364 Err: err.Error(), 365 Name: host, 366 IsTimeout: isTimeout, 367 } 368 } 369 } 370 if trace != nil && trace.DNSDone != nil { 371 addrs, _ := r.Val.([]IPAddr) 372 trace.DNSDone(ipAddrsEface(addrs), r.Shared, err) 373 } 374 return lookupIPReturn(r.Val, err, r.Shared) 375 } 376 } 377 378 // lookupIPReturn turns the return values from singleflight.Do into 379 // the return values from LookupIP. 380 func lookupIPReturn(addrsi any, err error, shared bool) ([]IPAddr, error) { 381 if err != nil { 382 return nil, err 383 } 384 addrs := addrsi.([]IPAddr) 385 if shared { 386 clone := make([]IPAddr, len(addrs)) 387 copy(clone, addrs) 388 addrs = clone 389 } 390 return addrs, nil 391 } 392 393 // ipAddrsEface returns an empty interface slice of addrs. 394 func ipAddrsEface(addrs []IPAddr) []any { 395 s := make([]any, len(addrs)) 396 for i, v := range addrs { 397 s[i] = v 398 } 399 return s 400 } 401 402 // LookupPort looks up the port for the given network and service. 403 // 404 // LookupPort uses context.Background internally; to specify the context, use 405 // Resolver.LookupPort. 406 func LookupPort(network, service string) (port int, err error) { 407 return DefaultResolver.LookupPort(context.Background(), network, service) 408 } 409 410 // LookupPort looks up the port for the given network and service. 411 func (r *Resolver) LookupPort(ctx context.Context, network, service string) (port int, err error) { 412 port, needsLookup := parsePort(service) 413 if needsLookup { 414 switch network { 415 case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6": 416 case "": // a hint wildcard for Go 1.0 undocumented behavior 417 network = "ip" 418 default: 419 return 0, &AddrError{Err: "unknown network", Addr: network} 420 } 421 port, err = r.lookupPort(ctx, network, service) 422 if err != nil { 423 return 0, err 424 } 425 } 426 if 0 > port || port > 65535 { 427 return 0, &AddrError{Err: "invalid port", Addr: service} 428 } 429 return port, nil 430 } 431 432 // LookupCNAME returns the canonical name for the given host. 433 // Callers that do not care about the canonical name can call 434 // LookupHost or LookupIP directly; both take care of resolving 435 // the canonical name as part of the lookup. 436 // 437 // A canonical name is the final name after following zero 438 // or more CNAME records. 439 // LookupCNAME does not return an error if host does not 440 // contain DNS "CNAME" records, as long as host resolves to 441 // address records. 442 // 443 // The returned canonical name is validated to be a properly 444 // formatted presentation-format domain name. 445 // 446 // LookupCNAME uses context.Background internally; to specify the context, use 447 // Resolver.LookupCNAME. 448 func LookupCNAME(host string) (cname string, err error) { 449 return DefaultResolver.LookupCNAME(context.Background(), host) 450 } 451 452 // LookupCNAME returns the canonical name for the given host. 453 // Callers that do not care about the canonical name can call 454 // LookupHost or LookupIP directly; both take care of resolving 455 // the canonical name as part of the lookup. 456 // 457 // A canonical name is the final name after following zero 458 // or more CNAME records. 459 // LookupCNAME does not return an error if host does not 460 // contain DNS "CNAME" records, as long as host resolves to 461 // address records. 462 // 463 // The returned canonical name is validated to be a properly 464 // formatted presentation-format domain name. 465 func (r *Resolver) LookupCNAME(ctx context.Context, host string) (string, error) { 466 cname, err := r.lookupCNAME(ctx, host) 467 if err != nil { 468 return "", err 469 } 470 if !isDomainName(cname) { 471 return "", &DNSError{Err: errMalformedDNSRecordsDetail, Name: host} 472 } 473 return cname, nil 474 } 475 476 // LookupSRV tries to resolve an SRV query of the given service, 477 // protocol, and domain name. The proto is "tcp" or "udp". 478 // The returned records are sorted by priority and randomized 479 // by weight within a priority. 480 // 481 // LookupSRV constructs the DNS name to look up following RFC 2782. 482 // That is, it looks up _service._proto.name. To accommodate services 483 // publishing SRV records under non-standard names, if both service 484 // and proto are empty strings, LookupSRV looks up name directly. 485 // 486 // The returned service names are validated to be properly 487 // formatted presentation-format domain names. If the response contains 488 // invalid names, those records are filtered out and an error 489 // will be returned alongside the remaining results, if any. 490 func LookupSRV(service, proto, name string) (cname string, addrs []*SRV, err error) { 491 return DefaultResolver.LookupSRV(context.Background(), service, proto, name) 492 } 493 494 // LookupSRV tries to resolve an SRV query of the given service, 495 // protocol, and domain name. The proto is "tcp" or "udp". 496 // The returned records are sorted by priority and randomized 497 // by weight within a priority. 498 // 499 // LookupSRV constructs the DNS name to look up following RFC 2782. 500 // That is, it looks up _service._proto.name. To accommodate services 501 // publishing SRV records under non-standard names, if both service 502 // and proto are empty strings, LookupSRV looks up name directly. 503 // 504 // The returned service names are validated to be properly 505 // formatted presentation-format domain names. If the response contains 506 // invalid names, those records are filtered out and an error 507 // will be returned alongside the remaining results, if any. 508 func (r *Resolver) LookupSRV(ctx context.Context, service, proto, name string) (string, []*SRV, error) { 509 cname, addrs, err := r.lookupSRV(ctx, service, proto, name) 510 if err != nil { 511 return "", nil, err 512 } 513 if cname != "" && !isDomainName(cname) { 514 return "", nil, &DNSError{Err: "SRV header name is invalid", Name: name} 515 } 516 filteredAddrs := make([]*SRV, 0, len(addrs)) 517 for _, addr := range addrs { 518 if addr == nil { 519 continue 520 } 521 if !isDomainName(addr.Target) { 522 continue 523 } 524 filteredAddrs = append(filteredAddrs, addr) 525 } 526 if len(addrs) != len(filteredAddrs) { 527 return cname, filteredAddrs, &DNSError{Err: errMalformedDNSRecordsDetail, Name: name} 528 } 529 return cname, filteredAddrs, nil 530 } 531 532 // LookupMX returns the DNS MX records for the given domain name sorted by preference. 533 // 534 // The returned mail server names are validated to be properly 535 // formatted presentation-format domain names. If the response contains 536 // invalid names, those records are filtered out and an error 537 // will be returned alongside the remaining results, if any. 538 // 539 // LookupMX uses context.Background internally; to specify the context, use 540 // Resolver.LookupMX. 541 func LookupMX(name string) ([]*MX, error) { 542 return DefaultResolver.LookupMX(context.Background(), name) 543 } 544 545 // LookupMX returns the DNS MX records for the given domain name sorted by preference. 546 // 547 // The returned mail server names are validated to be properly 548 // formatted presentation-format domain names. If the response contains 549 // invalid names, those records are filtered out and an error 550 // will be returned alongside the remaining results, if any. 551 func (r *Resolver) LookupMX(ctx context.Context, name string) ([]*MX, error) { 552 records, err := r.lookupMX(ctx, name) 553 if err != nil { 554 return nil, err 555 } 556 filteredMX := make([]*MX, 0, len(records)) 557 for _, mx := range records { 558 if mx == nil { 559 continue 560 } 561 if !isDomainName(mx.Host) { 562 continue 563 } 564 filteredMX = append(filteredMX, mx) 565 } 566 if len(records) != len(filteredMX) { 567 return filteredMX, &DNSError{Err: errMalformedDNSRecordsDetail, Name: name} 568 } 569 return filteredMX, nil 570 } 571 572 // LookupNS returns the DNS NS records for the given domain name. 573 // 574 // The returned name server names are validated to be properly 575 // formatted presentation-format domain names. If the response contains 576 // invalid names, those records are filtered out and an error 577 // will be returned alongside the remaining results, if any. 578 // 579 // LookupNS uses context.Background internally; to specify the context, use 580 // Resolver.LookupNS. 581 func LookupNS(name string) ([]*NS, error) { 582 return DefaultResolver.LookupNS(context.Background(), name) 583 } 584 585 // LookupNS returns the DNS NS records for the given domain name. 586 // 587 // The returned name server names are validated to be properly 588 // formatted presentation-format domain names. If the response contains 589 // invalid names, those records are filtered out and an error 590 // will be returned alongside the remaining results, if any. 591 func (r *Resolver) LookupNS(ctx context.Context, name string) ([]*NS, error) { 592 records, err := r.lookupNS(ctx, name) 593 if err != nil { 594 return nil, err 595 } 596 filteredNS := make([]*NS, 0, len(records)) 597 for _, ns := range records { 598 if ns == nil { 599 continue 600 } 601 if !isDomainName(ns.Host) { 602 continue 603 } 604 filteredNS = append(filteredNS, ns) 605 } 606 if len(records) != len(filteredNS) { 607 return filteredNS, &DNSError{Err: errMalformedDNSRecordsDetail, Name: name} 608 } 609 return filteredNS, nil 610 } 611 612 // LookupTXT returns the DNS TXT records for the given domain name. 613 // 614 // LookupTXT uses context.Background internally; to specify the context, use 615 // Resolver.LookupTXT. 616 func LookupTXT(name string) ([]string, error) { 617 return DefaultResolver.lookupTXT(context.Background(), name) 618 } 619 620 // LookupTXT returns the DNS TXT records for the given domain name. 621 func (r *Resolver) LookupTXT(ctx context.Context, name string) ([]string, error) { 622 return r.lookupTXT(ctx, name) 623 } 624 625 // LookupAddr performs a reverse lookup for the given address, returning a list 626 // of names mapping to that address. 627 // 628 // The returned names are validated to be properly formatted presentation-format 629 // domain names. If the response contains invalid names, those records are filtered 630 // out and an error will be returned alongside the remaining results, if any. 631 // 632 // When using the host C library resolver, at most one result will be 633 // returned. To bypass the host resolver, use a custom Resolver. 634 // 635 // LookupAddr uses context.Background internally; to specify the context, use 636 // Resolver.LookupAddr. 637 func LookupAddr(addr string) (names []string, err error) { 638 return DefaultResolver.LookupAddr(context.Background(), addr) 639 } 640 641 // LookupAddr performs a reverse lookup for the given address, returning a list 642 // of names mapping to that address. 643 // 644 // The returned names are validated to be properly formatted presentation-format 645 // domain names. If the response contains invalid names, those records are filtered 646 // out and an error will be returned alongside the remaining results, if any. 647 func (r *Resolver) LookupAddr(ctx context.Context, addr string) ([]string, error) { 648 names, err := r.lookupAddr(ctx, addr) 649 if err != nil { 650 return nil, err 651 } 652 filteredNames := make([]string, 0, len(names)) 653 for _, name := range names { 654 if isDomainName(name) { 655 filteredNames = append(filteredNames, name) 656 } 657 } 658 if len(names) != len(filteredNames) { 659 return filteredNames, &DNSError{Err: errMalformedDNSRecordsDetail, Name: addr} 660 } 661 return filteredNames, nil 662 } 663 664 // errMalformedDNSRecordsDetail is the DNSError detail which is returned when a Resolver.Lookup... 665 // method receives DNS records which contain invalid DNS names. This may be returned alongside 666 // results which have had the malformed records filtered out. 667 var errMalformedDNSRecordsDetail = "DNS response contained records which contain invalid names"