aboutsummaryrefslogtreecommitdiffstats
path: root/client/go/internal/vespa/detect_hostname.go
blob: e6b2d113ec1db4d898302f710d766b2d47ed7532 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
// Author: arnej

package vespa

import (
	"fmt"
	"net"
	"os"
	"strings"

	"github.com/vespa-engine/vespa/client/go/internal/admin/envvars"
	"github.com/vespa-engine/vespa/client/go/internal/admin/trace"
	"github.com/vespa-engine/vespa/client/go/internal/util"
)

// detect if this host is IPv6-only, in which case we want to pass
// the flag "-Djava.net.preferIPv6Addresses=true" to any java command
func HasOnlyIpV6() bool {
	hostname, err := FindOurHostname()
	if hostname == "" || err != nil {
		return false
	}
	foundV4 := false
	foundV6 := false
	ipAddrs, err := net.LookupIP(hostname)
	if err != nil {
		return false
	}
	for _, addr := range ipAddrs {
		switch {
		case addr.IsLoopback():
			// skip
		case addr.To4() != nil:
			foundV4 = true
		case addr.To16() != nil:
			foundV6 = true
		}
	}
	return foundV6 && !foundV4
}

// Find a good name for the host we're running on.
// We need something that *other* hosts can use for connnecting back
// to our services, preferably the canonical DNS name.
// If automatic detection fails, "localhost" will be returned, so
// single-node setups still have a good chance of working.
// Use the enviroment variable VESPA_HOSTNAME to override.
func FindOurHostname() (string, error) {
	env := os.Getenv(envvars.VESPA_HOSTNAME)
	if env != "" {
		// assumes: env var is already validated and OK
		return env, nil
	}
	name, err := os.Hostname()
	if err != nil {
		name = "localhost"
	}
	name, err = findOurHostnameFrom(name)
	if err == nil {
		os.Setenv(envvars.VESPA_HOSTNAME, name)
	}
	return name, err
}

func validateHostname(name string) bool {
	ipAddrs, _ := net.LookupIP(name)
	trace.Debug("lookupIP", name, "=>", ipAddrs)
	if len(ipAddrs) < 1 {
		return false
	}
	myIpAddresses := make(map[string]bool)
	interfaceAddrs, _ := net.InterfaceAddrs()
	for _, ifAddr := range interfaceAddrs {
		// note: ifAddr.String() is typically "127.0.0.1/8"
		if ipnet, ok := ifAddr.(*net.IPNet); ok {
			myIpAddresses[ipnet.IP.String()] = true
		}
	}
	trace.Debug("validate with interfaces =>", myIpAddresses)
	someGood := false
	for _, addr := range ipAddrs {
		if len(myIpAddresses) == 0 {
			// no validation possible, assume OK
			return true
		}
		if myIpAddresses[addr.String()] {
			someGood = true
		} else {
			return false
		}
	}
	return someGood
}

func goodHostname(name string) (result string, good bool) {
	result = strings.TrimSpace(name)
	result = strings.TrimSuffix(result, ".")
	if name != result {
		trace.Trace("trimmed hostname", name, "=>", result)
	}
	good = strings.Contains(result, ".") && validateHostname(result)
	trace.Debug("hostname:", result, "good =>", good)
	return
}

func findOurHostnameFrom(name string) (string, error) {
	trimmed, good := goodHostname(name)
	if good {
		return trimmed, nil
	}
	backticks := util.BackTicksIgnoreStderr
	out, err := backticks.Run("vespa-detect-hostname")
	if err != nil {
		out, err = backticks.Run("hostname", "-f")
	}
	if err != nil {
		out, err = backticks.Run("hostname")
	}
	alternate, good := goodHostname(out)
	if err == nil && good {
		return alternate, nil
	}
	if validateHostname(trimmed) {
		return trimmed, nil
	}
	if validateHostname(alternate) {
		return alternate, nil
	}
	return "localhost", fmt.Errorf("fallback to localhost [os.Hostname was '%s']", name)
}