diff options
Diffstat (limited to 'src/net/lookup.go')
-rw-r--r-- | src/net/lookup.go | 610 |
1 files changed, 610 insertions, 0 deletions
diff --git a/src/net/lookup.go b/src/net/lookup.go new file mode 100644 index 0000000..4135122 --- /dev/null +++ b/src/net/lookup.go @@ -0,0 +1,610 @@ +// 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" + "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. +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 +} + +// 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 interface{}) interface{} { + 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() (interface{}, 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() + }() + } + err := mapErr(ctx.Err()) + if trace != nil && trace.DNSDone != nil { + trace.DNSDone(nil, false, err) + } + return nil, err + case r := <-ch: + lookupGroupCancel() + if trace != nil && trace.DNSDone != nil { + addrs, _ := r.Val.([]IPAddr) + trace.DNSDone(ipAddrsEface(addrs), r.Shared, r.Err) + } + return lookupIPReturn(r.Val, r.Err, r.Shared) + } +} + +// lookupIPReturn turns the return values from singleflight.Do into +// the return values from LookupIP. +func lookupIPReturn(addrsi interface{}, 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) []interface{} { + s := make([]interface{}, len(addrs)) + for i, v := range addrs { + s[i] = v + } + return s +} + +// LookupPort looks up the port for the given network and service. +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. +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 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 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 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 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 + } + // Bypass the hostname validity check for targets which contain only a dot, + // as this is used to represent a 'Null' MX record. + if mx.Host != "." && !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 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 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. +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 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. +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 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 recieves 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" |