summaryrefslogtreecommitdiff
path: root/opts/hosts.go
blob: 412c431fd478384f9a4ee7dea093d2c10327c098 (plain)
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
package opts // import "github.com/docker/docker/opts"

import (
	"net"
	"net/url"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/docker/docker/pkg/homedir"
	"github.com/pkg/errors"
)

const (
	// DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. dockerd -H tcp://
	// These are the IANA registered port numbers for use with Docker
	// see http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=docker
	DefaultHTTPPort = 2375 // Default HTTP Port
	// DefaultTLSHTTPPort Default HTTP Port used when TLS enabled
	DefaultTLSHTTPPort = 2376 // Default TLS encrypted HTTP Port
	// DefaultUnixSocket Path for the unix socket.
	// Docker daemon by default always listens on the default unix socket
	DefaultUnixSocket = "/var/run/docker.sock"
	// DefaultTCPHost constant defines the default host string used by docker on Windows
	DefaultTCPHost = "tcp://" + DefaultHTTPHost + ":2375"
	// DefaultTLSHost constant defines the default host string used by docker for TLS sockets
	DefaultTLSHost = "tcp://" + DefaultHTTPHost + ":2376"
	// DefaultNamedPipe defines the default named pipe used by docker on Windows
	DefaultNamedPipe = `//./pipe/docker_engine`
	// HostGatewayName is the string value that can be passed
	// to the IPAddr section in --add-host that is replaced by
	// the value of HostGatewayIP daemon config value
	HostGatewayName = "host-gateway"
)

// ValidateHost validates that the specified string is a valid host and returns it.
func ValidateHost(val string) (string, error) {
	host := strings.TrimSpace(val)
	// The empty string means default and is not handled by parseDaemonHost
	if host != "" {
		_, err := parseDaemonHost(host)
		if err != nil {
			return val, err
		}
	}
	// Note: unlike most flag validators, we don't return the mutated value here
	//       we need to know what the user entered later (using ParseHost) to adjust for TLS
	return val, nil
}

// ParseHost and set defaults for a Daemon host string.
// defaultToTLS is preferred over defaultToUnixXDG.
func ParseHost(defaultToTLS, defaultToUnixXDG bool, val string) (string, error) {
	host := strings.TrimSpace(val)
	if host == "" {
		if defaultToTLS {
			host = DefaultTLSHost
		} else if defaultToUnixXDG {
			runtimeDir, err := homedir.GetRuntimeDir()
			if err != nil {
				return "", err
			}
			host = "unix://" + filepath.Join(runtimeDir, "docker.sock")
		} else {
			host = DefaultHost
		}
	} else {
		var err error
		host, err = parseDaemonHost(host)
		if err != nil {
			return val, err
		}
	}
	return host, nil
}

// parseDaemonHost parses the specified address and returns an address that will be used as the host.
// Depending on the address specified, this may return one of the global Default* strings defined in hosts.go.
func parseDaemonHost(address string) (string, error) {
	proto, addr, ok := strings.Cut(address, "://")
	if !ok && proto != "" {
		addr = proto
		proto = "tcp"
	}

	switch proto {
	case "tcp":
		return ParseTCPAddr(address, DefaultTCPHost)
	case "unix":
		a, err := parseSimpleProtoAddr(proto, addr, DefaultUnixSocket)
		if err != nil {
			return "", errors.Wrapf(err, "invalid bind address (%s)", address)
		}
		return a, nil
	case "npipe":
		a, err := parseSimpleProtoAddr(proto, addr, DefaultNamedPipe)
		if err != nil {
			return "", errors.Wrapf(err, "invalid bind address (%s)", address)
		}
		return a, nil
	case "fd":
		return address, nil
	default:
		return "", errors.Errorf("invalid bind address (%s): unsupported proto '%s'", address, proto)
	}
}

// parseSimpleProtoAddr parses and validates that the specified address is a valid
// socket address for simple protocols like unix and npipe. It returns a formatted
// socket address, either using the address parsed from addr, or the contents of
// defaultAddr if addr is a blank string.
func parseSimpleProtoAddr(proto, addr, defaultAddr string) (string, error) {
	if strings.Contains(addr, "://") {
		return "", errors.Errorf("invalid %s address: %s", proto, addr)
	}
	if addr == "" {
		addr = defaultAddr
	}
	return proto + "://" + addr, nil
}

// ParseTCPAddr parses and validates that the specified address is a valid TCP
// address. It returns a formatted TCP address, either using the address parsed
// from tryAddr, or the contents of defaultAddr if tryAddr is a blank string.
// tryAddr is expected to have already been Trim()'d
// defaultAddr must be in the full `tcp://host:port` form
func ParseTCPAddr(tryAddr string, defaultAddr string) (string, error) {
	def, err := parseTCPAddr(defaultAddr, true)
	if err != nil {
		return "", errors.Wrapf(err, "invalid default address (%s)", defaultAddr)
	}

	addr, err := parseTCPAddr(tryAddr, false)
	if err != nil {
		return "", errors.Wrapf(err, "invalid bind address (%s)", tryAddr)
	}

	host := addr.Hostname()
	if host == "" {
		host = def.Hostname()
	}
	port := addr.Port()
	if port == "" {
		port = def.Port()
	}

	return "tcp://" + net.JoinHostPort(host, port), nil
}

// parseTCPAddr parses the given addr and validates if it is in the expected
// format. If strict is enabled, the address must contain a scheme (tcp://),
// a host (or IP-address) and a port number.
func parseTCPAddr(address string, strict bool) (*url.URL, error) {
	if !strict && !strings.Contains(address, "://") {
		address = "tcp://" + address
	}
	parsedURL, err := url.Parse(address)
	if err != nil {
		return nil, err
	}
	if parsedURL.Scheme != "tcp" {
		return nil, errors.Errorf("unsupported proto '%s'", parsedURL.Scheme)
	}
	if parsedURL.Path != "" {
		return nil, errors.New("should not contain a path element")
	}
	if strict && parsedURL.Host == "" {
		return nil, errors.New("no host or IP address")
	}
	if parsedURL.Port() != "" || strict {
		if p, err := strconv.Atoi(parsedURL.Port()); err != nil || p == 0 {
			return nil, errors.Errorf("invalid port: %s", parsedURL.Port())
		}
	}
	return parsedURL, nil
}

// ValidateExtraHost validates that the specified string is a valid extrahost and returns it.
// ExtraHost is in the form of name:ip where the ip has to be a valid ip (IPv4 or IPv6).
func ValidateExtraHost(val string) (string, error) {
	// allow for IPv6 addresses in extra hosts by only splitting on first ":"
	name, ip, ok := strings.Cut(val, ":")
	if !ok || name == "" {
		return "", errors.Errorf("bad format for add-host: %q", val)
	}
	// Skip IPaddr validation for special "host-gateway" string
	if ip != HostGatewayName {
		if _, err := ValidateIPAddress(ip); err != nil {
			return "", errors.Errorf("invalid IP address in add-host: %q", ip)
		}
	}
	return val, nil
}