github.com/thanos-io/thanos@v0.32.5/pkg/discovery/dns/resolver.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package dns 5 6 import ( 7 "context" 8 "net" 9 "strconv" 10 "strings" 11 12 "github.com/go-kit/log" 13 "github.com/go-kit/log/level" 14 15 "github.com/pkg/errors" 16 ) 17 18 type QType string 19 20 const ( 21 // A qtype performs A/AAAA lookup. 22 A = QType("dns") 23 // SRV qtype performs SRV lookup with A/AAAA lookup for each SRV result. 24 SRV = QType("dnssrv") 25 // SRVNoA qtype performs SRV lookup without any A/AAAA lookup for each SRV result. 26 SRVNoA = QType("dnssrvnoa") 27 ) 28 29 type Resolver interface { 30 // Resolve performs a DNS lookup and returns a list of records. 31 // name is the domain name to be resolved. 32 // qtype is the query type. Accepted values are `dns` for A/AAAA lookup and `dnssrv` for SRV lookup. 33 // If scheme is passed through name, it is preserved on IP results. 34 Resolve(ctx context.Context, name string, qtype QType) ([]string, error) 35 } 36 37 type ipLookupResolver interface { 38 LookupIPAddr(ctx context.Context, host string) ([]net.IPAddr, error) 39 LookupSRV(ctx context.Context, service, proto, name string) (cname string, addrs []*net.SRV, err error) 40 IsNotFound(err error) bool 41 } 42 43 type dnsSD struct { 44 resolver ipLookupResolver 45 logger log.Logger 46 } 47 48 // NewResolver creates a resolver with given underlying resolver. 49 func NewResolver(resolver ipLookupResolver, logger log.Logger) Resolver { 50 return &dnsSD{resolver: resolver, logger: logger} 51 } 52 53 func (s *dnsSD) Resolve(ctx context.Context, name string, qtype QType) ([]string, error) { 54 var ( 55 res []string 56 scheme string 57 ) 58 59 schemeSplit := strings.Split(name, "//") 60 if len(schemeSplit) > 1 { 61 scheme = schemeSplit[0] 62 name = schemeSplit[1] 63 } 64 65 // Split the host and port if present. 66 host, port, err := net.SplitHostPort(name) 67 if err != nil { 68 // The host could be missing a port. 69 host, port = name, "" 70 } 71 72 switch qtype { 73 case A: 74 if port == "" { 75 return nil, errors.Errorf("missing port in address given for dns lookup: %v", name) 76 } 77 ips, err := s.resolver.LookupIPAddr(ctx, host) 78 if err != nil { 79 // We exclude error from std Golang resolver for the case of the domain (e.g `NXDOMAIN`) not being found by DNS 80 // server. Since `miekg` does not consider this as an error, when the host cannot be found, empty slice will be 81 // returned. 82 if !s.resolver.IsNotFound(err) { 83 return nil, errors.Wrapf(err, "lookup IP addresses %q", host) 84 } 85 if ips == nil { 86 level.Error(s.logger).Log("msg", "failed to lookup IP addresses", "host", host, "err", err) 87 } 88 } 89 for _, ip := range ips { 90 res = append(res, appendScheme(scheme, net.JoinHostPort(ip.String(), port))) 91 } 92 case SRV, SRVNoA: 93 _, recs, err := s.resolver.LookupSRV(ctx, "", "", host) 94 if err != nil { 95 if !s.resolver.IsNotFound(err) { 96 return nil, errors.Wrapf(err, "lookup SRV records %q", host) 97 } 98 if len(recs) == 0 { 99 level.Error(s.logger).Log("msg", "failed to lookup SRV records", "host", host, "err", err) 100 } 101 } 102 103 for _, rec := range recs { 104 // Only use port from SRV record if no explicit port was specified. 105 resPort := port 106 if resPort == "" { 107 resPort = strconv.Itoa(int(rec.Port)) 108 } 109 110 if qtype == SRVNoA { 111 res = append(res, appendScheme(scheme, net.JoinHostPort(rec.Target, resPort))) 112 continue 113 } 114 // Do A lookup for the domain in SRV answer. 115 resIPs, err := s.resolver.LookupIPAddr(ctx, rec.Target) 116 if err != nil { 117 if !s.resolver.IsNotFound(err) { 118 return nil, errors.Wrapf(err, "lookup IP addresses %q", host) 119 } 120 if len(resIPs) == 0 { 121 level.Error(s.logger).Log("msg", "failed to lookup IP addresses", "srv", host, "a", rec.Target, "err", err) 122 } 123 } 124 for _, resIP := range resIPs { 125 res = append(res, appendScheme(scheme, net.JoinHostPort(resIP.String(), resPort))) 126 } 127 } 128 default: 129 return nil, errors.Errorf("invalid lookup scheme %q", qtype) 130 } 131 132 if res == nil && err == nil { 133 level.Warn(s.logger).Log("msg", "IP address lookup yielded no results. No host found or no addresses found", "host", host) 134 } 135 136 return res, nil 137 } 138 139 func appendScheme(scheme, host string) string { 140 if scheme == "" { 141 return host 142 } 143 return scheme + "//" + host 144 }