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  }