diff --git a/client/config/config.go b/client/config/config.go index 40a01385d..185b31012 100644 --- a/client/config/config.go +++ b/client/config/config.go @@ -31,6 +31,9 @@ type Config struct { // Region is the clients region Region string + // Network interface to be used in network fingerprinting + NetworkInterface string + // Servers is a list of known server addresses. These are as "host:port" Servers []string diff --git a/client/fingerprint/network_unix.go b/client/fingerprint/network_unix.go index b02040367..b74062848 100644 --- a/client/fingerprint/network_unix.go +++ b/client/fingerprint/network_unix.go @@ -3,6 +3,7 @@ package fingerprint import ( + "errors" "fmt" "io/ioutil" "log" @@ -38,10 +39,14 @@ func (f *NetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) if "darwin" == runtime.GOOS { defaultDevice = "en0" } + // User-defined override for the default interface + if cfg.NetworkInterface != "" { + defaultDevice = cfg.NetworkInterface + } newNetwork.Device = defaultDevice - if ip := f.ifConfig(defaultDevice); ip != "" { + if ip := f.ipAddress(defaultDevice); ip != "" { node.Attributes["network.ip-address"] = ip newNetwork.IP = ip newNetwork.CIDR = newNetwork.IP + "/32" @@ -129,6 +134,56 @@ func (f *NetworkFingerprint) linkSpeedEthtool(path, device string) int { return mbs } +// ipAddress returns the first IPv4 address on the configured default interface +// Tries Golang native functions and falls back onto ifconfig +func (f *NetworkFingerprint) ipAddress(device string) string { + if ip, err := f.nativeIpAddress(device); err == nil { + return ip + } + + return f.ifConfig(device) +} + +func (f *NetworkFingerprint) nativeIpAddress(device string) (string, error) { + // Find IP address on configured interface + var ip string + ifaces, err := net.Interfaces() + if err != nil { + return "", errors.New("could not retrieve interface list") + } + + // TODO: should we handle IPv6 here? How do we determine precedence? + for _, i := range ifaces { + if i.Name != device { + continue + } + + addrs, err := i.Addrs() + if err != nil { + return "", errors.New("could not retrieve interface IP addresses") + } + + for _, a := range addrs { + switch v := a.(type) { + case *net.IPNet: + if v.IP.To4() != nil { + ip = v.IP.String() + } + case *net.IPAddr: + if v.IP.To4() != nil { + ip = v.IP.String() + } + } + } + } + + if net.ParseIP(ip) == nil { + return "", errors.New(fmt.Sprintf("could not parse IP address `%s`", ip)) + } + + return ip, nil +} + // ifConfig returns the IP Address for this node according to ifConfig, for the // specified device. func (f *NetworkFingerprint) ifConfig(device string) string { diff --git a/command/agent/agent.go b/command/agent/agent.go index c05bc0ed9..f5e99c6c6 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -192,6 +192,9 @@ func (a *Agent) setupClient() error { conf.AllocDir = a.config.Client.AllocDir } conf.Servers = a.config.Client.Servers + if a.config.Client.NetworkInterface != "" { + conf.NetworkInterface = a.config.Client.NetworkInterface + } // Setup the node conf.Node = new(structs.Node) diff --git a/command/agent/config.go b/command/agent/config.go index 22fd36bc5..781ee0441 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -139,6 +139,9 @@ type ClientConfig struct { // Metadata associated with the node Meta map[string]string `hcl:"meta"` + + // Interface to use for network fingerprinting + NetworkInterface string `hcl:"network_interface"` } // ServerConfig is configuration specific to the server mode @@ -384,6 +387,9 @@ func (a *ClientConfig) Merge(b *ClientConfig) *ClientConfig { if b.NodeClass != "" { result.NodeClass = b.NodeClass } + if b.NetworkInterface != "" { + result.NetworkInterface = b.NetworkInterface + } // Add the servers result.Servers = append(result.Servers, b.Servers...) diff --git a/website/source/docs/agent/config.html.md b/website/source/docs/agent/config.html.md index 57b654d5d..9d3a4bf15 100644 --- a/website/source/docs/agent/config.html.md +++ b/website/source/docs/agent/config.html.md @@ -84,7 +84,7 @@ nodes, unless otherwise specified: TCP and UDP should be routable between the server nodes on this port. Defaults to `4648`. Only used on server nodes. -* `addresses`: Controls the bind address for individual +* `addresses`: Controls the bind address for individual network services. Any values configured in this block take precedence over the default [bind_addr](#bind_addr). The value is a map of IP addresses and supports the following keys: @@ -154,7 +154,7 @@ configured on client nodes. * `enabled`: A boolean indicating if server mode should be enabled for the local agent. All other server options depend on this value being set. Defaults to `false`. - * `bootstrap_expect`: This is an integer + * `bootstrap_expect`: This is an integer representing the number of server nodes to wait for before bootstrapping. It is most common to use the odd-numbered integers `3` or `5` for this value, depending on the cluster size. A value of `1` does not provide any fault @@ -205,8 +205,10 @@ configured on server nodes. * `node_class`: A string used to logically group client nodes by class. This can be used during job placement as a filter. This option is not required and has no default. - * `meta`: This is a key/value mapping of metadata pairs. This + * `meta`: This is a key/value mapping of metadata pairs. This is a free-form map and can contain any string values. + * `network_interface`: This is a string to force network fingerprinting to use + a specific network interface ## Atlas Options