1package autoconf
2
3import (
4	"fmt"
5	"net"
6	"strconv"
7	"strings"
8
9	"github.com/hashicorp/consul/lib"
10	"github.com/hashicorp/go-discover"
11	discoverk8s "github.com/hashicorp/go-discover/provider/k8s"
12
13	"github.com/hashicorp/go-hclog"
14)
15
16func (ac *AutoConfig) discoverServers(servers []string) ([]string, error) {
17	providers := make(map[string]discover.Provider)
18	for k, v := range discover.Providers {
19		providers[k] = v
20	}
21	providers["k8s"] = &discoverk8s.Provider{}
22
23	disco, err := discover.New(
24		discover.WithUserAgent(lib.UserAgent()),
25		discover.WithProviders(providers),
26	)
27
28	if err != nil {
29		return nil, fmt.Errorf("Failed to create go-discover resolver: %w", err)
30	}
31
32	var addrs []string
33	for _, addr := range servers {
34		switch {
35		case strings.Contains(addr, "provider="):
36			resolved, err := disco.Addrs(addr, ac.logger.StandardLogger(&hclog.StandardLoggerOptions{InferLevels: true}))
37			if err != nil {
38				ac.logger.Error("failed to resolve go-discover auto-config servers", "configuration", addr, "err", err)
39				continue
40			}
41
42			addrs = append(addrs, resolved...)
43			ac.logger.Debug("discovered auto-config servers", "servers", resolved)
44		default:
45			addrs = append(addrs, addr)
46		}
47	}
48
49	return addrs, nil
50}
51
52// autoConfigHosts is responsible for taking the list of server addresses
53// and resolving any go-discover provider invocations. It will then return
54// a list of hosts. These might be hostnames and is expected that DNS resolution
55// may be performed after this function runs. Additionally these may contain
56// ports so SplitHostPort could also be necessary.
57func (ac *AutoConfig) autoConfigHosts() ([]string, error) {
58	// use servers known to gossip if there are any
59	if ac.acConfig.ServerProvider != nil {
60		if srv := ac.acConfig.ServerProvider.FindLANServer(); srv != nil {
61			return []string{srv.Addr.String()}, nil
62		}
63	}
64
65	addrs, err := ac.discoverServers(ac.config.AutoConfig.ServerAddresses)
66	if err != nil {
67		return nil, err
68	}
69
70	if len(addrs) == 0 {
71		return nil, fmt.Errorf("no auto-config server addresses available for use")
72	}
73
74	return addrs, nil
75}
76
77// resolveHost will take a single host string and convert it to a list of TCPAddrs
78// This will process any port in the input as well as looking up the hostname using
79// normal DNS resolution.
80func (ac *AutoConfig) resolveHost(hostPort string) []net.TCPAddr {
81	port := ac.config.ServerPort
82	host, portStr, err := net.SplitHostPort(hostPort)
83	if err != nil {
84		if strings.Contains(err.Error(), "missing port in address") {
85			host = hostPort
86		} else {
87			ac.logger.Warn("error splitting host address into IP and port", "address", hostPort, "error", err)
88			return nil
89		}
90	} else {
91		port, err = strconv.Atoi(portStr)
92		if err != nil {
93			ac.logger.Warn("Parsed port is not an integer", "port", portStr, "error", err)
94			return nil
95		}
96	}
97
98	// resolve the host to a list of IPs
99	ips, err := net.LookupIP(host)
100	if err != nil {
101		ac.logger.Warn("IP resolution failed", "host", host, "error", err)
102		return nil
103	}
104
105	var addrs []net.TCPAddr
106	for _, ip := range ips {
107		addrs = append(addrs, net.TCPAddr{IP: ip, Port: port})
108	}
109
110	return addrs
111}
112