123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667 |
- // 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"
|