Browse code

Windows: Add support for named pipe protocol

This adds an npipe protocol option for Windows hosts, akin to unix
sockets for Linux hosts. This should become the default transport
for Windows, but this change does not yet do that.

It also does not add support for the client side yet since that
code is in engine-api, which will have to be revendored separately.

Signed-off-by: John Starks <jostarks@microsoft.com>

John Starks authored on 2016/01/31 11:45:49
Showing 9 changed files
... ...
@@ -176,12 +176,7 @@ func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (host string,
176 176
 		return "", errors.New("Please specify only one -H")
177 177
 	}
178 178
 
179
-	defaultHost := opts.DefaultTCPHost
180
-	if tlsOptions != nil {
181
-		defaultHost = opts.DefaultTLSHost
182
-	}
183
-
184
-	host, err = opts.ParseHost(defaultHost, host)
179
+	host, err = opts.ParseHost(tlsOptions != nil, host)
185 180
 	return
186 181
 }
187 182
 
... ...
@@ -4,8 +4,11 @@ package server
4 4
 
5 5
 import (
6 6
 	"errors"
7
+	"fmt"
8
+	"github.com/Microsoft/go-winio"
7 9
 	"net"
8 10
 	"net/http"
11
+	"strings"
9 12
 )
10 13
 
11 14
 // NewServer sets up the required Server and does protocol specific checking.
... ...
@@ -21,8 +24,26 @@ func (s *Server) newServer(proto, addr string) ([]*HTTPServer, error) {
21 21
 		}
22 22
 		ls = append(ls, l)
23 23
 
24
+	case "npipe":
25
+		// allow Administrators and SYSTEM, plus whatever additional users or groups were specified
26
+		sddl := "D:P(A;;GA;;;BA)(A;;GA;;;SY)"
27
+		if s.cfg.SocketGroup != "" {
28
+			for _, g := range strings.Split(s.cfg.SocketGroup, ",") {
29
+				sid, err := winio.LookupSidByName(g)
30
+				if err != nil {
31
+					return nil, err
32
+				}
33
+				sddl += fmt.Sprintf("(A;;GRGW;;;%s)", sid)
34
+			}
35
+		}
36
+		l, err := winio.ListenPipe(addr, sddl)
37
+		if err != nil {
38
+			return nil, err
39
+		}
40
+		ls = append(ls, l)
41
+
24 42
 	default:
25
-		return nil, errors.New("Invalid protocol format. Windows only supports tcp.")
43
+		return nil, errors.New("Invalid protocol format. Windows only supports tcp and npipe.")
26 44
 	}
27 45
 
28 46
 	var res []*HTTPServer
... ...
@@ -59,6 +59,7 @@ type CommonConfig struct {
59 59
 	Pidfile              string              `json:"pidfile,omitempty"`
60 60
 	RawLogs              bool                `json:"raw-logs,omitempty"`
61 61
 	Root                 string              `json:"graph,omitempty"`
62
+	SocketGroup          string              `json:"group,omitempty"`
62 63
 	TrustKeyPath         string              `json:"-"`
63 64
 
64 65
 	// ClusterStore is the storage backend used for the cluster information. It is used by both
... ...
@@ -29,7 +29,6 @@ type Config struct {
29 29
 	EnableCors           bool                     `json:"api-enable-cors,omitempty"`
30 30
 	EnableSelinuxSupport bool                     `json:"selinux-enabled,omitempty"`
31 31
 	RemappedRoot         string                   `json:"userns-remap,omitempty"`
32
-	SocketGroup          string                   `json:"group,omitempty"`
33 32
 	CgroupParent         string                   `json:"cgroup-parent,omitempty"`
34 33
 	Ulimits              map[string]*units.Ulimit `json:"default-ulimits,omitempty"`
35 34
 }
... ...
@@ -38,4 +38,5 @@ func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) strin
38 38
 
39 39
 	// Then platform-specific install flags.
40 40
 	cmd.StringVar(&config.bridgeConfig.VirtualSwitchName, []string{"b", "-bridge"}, "", "Attach containers to a virtual switch")
41
+	cmd.StringVar(&config.SocketGroup, []string{"G", "-group"}, "", usageFn("Users or groups that can access the named pipe"))
41 42
 }
... ...
@@ -200,11 +200,11 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
200 200
 	serverConfig := &apiserver.Config{
201 201
 		AuthorizationPluginNames: cli.Config.AuthorizationPlugins,
202 202
 		Logging:                  true,
203
+		SocketGroup:              cli.Config.SocketGroup,
203 204
 		Version:                  dockerversion.Version,
204 205
 	}
205 206
 	serverConfig = setPlatformServerConfig(serverConfig, cli.Config)
206 207
 
207
-	defaultHost := opts.DefaultHost
208 208
 	if cli.Config.TLS {
209 209
 		tlsOptions := tlsconfig.Options{
210 210
 			CAFile:   cli.Config.CommonTLSOptions.CAFile,
... ...
@@ -221,7 +221,6 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
221 221
 			logrus.Fatal(err)
222 222
 		}
223 223
 		serverConfig.TLSConfig = tlsConfig
224
-		defaultHost = opts.DefaultTLSHost
225 224
 	}
226 225
 
227 226
 	if len(cli.Config.Hosts) == 0 {
... ...
@@ -229,7 +228,7 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
229 229
 	}
230 230
 	for i := 0; i < len(cli.Config.Hosts); i++ {
231 231
 		var err error
232
-		if cli.Config.Hosts[i], err = opts.ParseHost(defaultHost, cli.Config.Hosts[i]); err != nil {
232
+		if cli.Config.Hosts[i], err = opts.ParseHost(cli.Config.TLS, cli.Config.Hosts[i]); err != nil {
233 233
 			logrus.Fatalf("error parsing -H %s : %v", cli.Config.Hosts[i], err)
234 234
 		}
235 235
 
... ...
@@ -19,7 +19,6 @@ import (
19 19
 const defaultDaemonConfigFile = "/etc/docker/daemon.json"
20 20
 
21 21
 func setPlatformServerConfig(serverConfig *apiserver.Config, daemonCfg *daemon.Config) *apiserver.Config {
22
-	serverConfig.SocketGroup = daemonCfg.SocketGroup
23 22
 	serverConfig.EnableCors = daemonCfg.EnableCors
24 23
 	serverConfig.CorsHeaders = daemonCfg.CorsHeaders
25 24
 
... ...
@@ -4,16 +4,12 @@ import (
4 4
 	"fmt"
5 5
 	"net"
6 6
 	"net/url"
7
-	"runtime"
8 7
 	"strconv"
9 8
 	"strings"
10 9
 )
11 10
 
12 11
 var (
13 12
 	// DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. docker daemon -H tcp://
14
-	// TODO Windows. DefaultHTTPPort is only used on Windows if a -H parameter
15
-	// is not supplied. A better longer term solution would be to use a named
16
-	// pipe as the default on the Windows daemon.
17 13
 	// These are the IANA registered port numbers for use with Docker
18 14
 	// see http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=docker
19 15
 	DefaultHTTPPort = 2375 // Default HTTP Port
... ...
@@ -26,13 +22,19 @@ var (
26 26
 	DefaultTCPHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort)
27 27
 	// DefaultTLSHost constant defines the default host string used by docker for TLS sockets
28 28
 	DefaultTLSHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultTLSHTTPPort)
29
+	// DefaultNamedPipe defines the default named pipe used by docker on Windows
30
+	DefaultNamedPipe = `//./pipe/docker_engine`
29 31
 )
30 32
 
31 33
 // ValidateHost validates that the specified string is a valid host and returns it.
32 34
 func ValidateHost(val string) (string, error) {
33
-	_, err := parseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, "", val)
34
-	if err != nil {
35
-		return val, err
35
+	host := strings.TrimSpace(val)
36
+	// The empty string means default and is not handled by parseDockerDaemonHost
37
+	if host != "" {
38
+		_, err := parseDockerDaemonHost(host)
39
+		if err != nil {
40
+			return val, err
41
+		}
36 42
 	}
37 43
 	// Note: unlike most flag validators, we don't return the mutated value here
38 44
 	//       we need to know what the user entered later (using ParseHost) to adjust for tls
... ...
@@ -40,39 +42,39 @@ func ValidateHost(val string) (string, error) {
40 40
 }
41 41
 
42 42
 // ParseHost and set defaults for a Daemon host string
43
-func ParseHost(defaultHost, val string) (string, error) {
44
-	host, err := parseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, defaultHost, val)
45
-	if err != nil {
46
-		return val, err
43
+func ParseHost(defaultToTLS bool, val string) (string, error) {
44
+	host := strings.TrimSpace(val)
45
+	if host == "" {
46
+		if defaultToTLS {
47
+			host = DefaultTLSHost
48
+		} else {
49
+			host = DefaultHost
50
+		}
51
+	} else {
52
+		var err error
53
+		host, err = parseDockerDaemonHost(host)
54
+		if err != nil {
55
+			return val, err
56
+		}
47 57
 	}
48 58
 	return host, nil
49 59
 }
50 60
 
51 61
 // parseDockerDaemonHost parses the specified address and returns an address that will be used as the host.
52
-// Depending of the address specified, will use the defaultTCPAddr or defaultUnixAddr
53
-// defaultUnixAddr must be a absolute file path (no `unix://` prefix)
54
-// defaultTCPAddr must be the full `tcp://host:port` form
55
-func parseDockerDaemonHost(defaultTCPAddr, defaultTLSHost, defaultUnixAddr, defaultAddr, addr string) (string, error) {
56
-	addr = strings.TrimSpace(addr)
57
-	if addr == "" {
58
-		if defaultAddr == defaultTLSHost {
59
-			return defaultTLSHost, nil
60
-		}
61
-		if runtime.GOOS != "windows" {
62
-			return fmt.Sprintf("unix://%s", defaultUnixAddr), nil
63
-		}
64
-		return defaultTCPAddr, nil
65
-	}
62
+// Depending of the address specified, this may return one of the global Default* strings defined in hosts.go.
63
+func parseDockerDaemonHost(addr string) (string, error) {
66 64
 	addrParts := strings.Split(addr, "://")
67
-	if len(addrParts) == 1 {
65
+	if len(addrParts) == 1 && addrParts[0] != "" {
68 66
 		addrParts = []string{"tcp", addrParts[0]}
69 67
 	}
70 68
 
71 69
 	switch addrParts[0] {
72 70
 	case "tcp":
73
-		return parseTCPAddr(addrParts[1], defaultTCPAddr)
71
+		return parseTCPAddr(addrParts[1], DefaultTCPHost)
74 72
 	case "unix":
75
-		return parseUnixAddr(addrParts[1], defaultUnixAddr)
73
+		return parseSimpleProtoAddr("unix", addrParts[1], DefaultUnixSocket)
74
+	case "npipe":
75
+		return parseSimpleProtoAddr("npipe", addrParts[1], DefaultNamedPipe)
76 76
 	case "fd":
77 77
 		return addr, nil
78 78
 	default:
... ...
@@ -80,19 +82,19 @@ func parseDockerDaemonHost(defaultTCPAddr, defaultTLSHost, defaultUnixAddr, defa
80 80
 	}
81 81
 }
82 82
 
83
-// parseUnixAddr parses and validates that the specified address is a valid UNIX
84
-// socket address. It returns a formatted UNIX socket address, either using the
85
-// address parsed from addr, or the contents of defaultAddr if addr is a blank
86
-// string.
87
-func parseUnixAddr(addr string, defaultAddr string) (string, error) {
88
-	addr = strings.TrimPrefix(addr, "unix://")
83
+// parseSimpleProtoAddr parses and validates that the specified address is a valid
84
+// socket address for simple protocols like unix and npipe. It returns a formatted
85
+// socket address, either using the address parsed from addr, or the contents of
86
+// defaultAddr if addr is a blank string.
87
+func parseSimpleProtoAddr(proto, addr, defaultAddr string) (string, error) {
88
+	addr = strings.TrimPrefix(addr, proto+"://")
89 89
 	if strings.Contains(addr, "://") {
90
-		return "", fmt.Errorf("Invalid proto, expected unix: %s", addr)
90
+		return "", fmt.Errorf("Invalid proto, expected %s: %s", proto, addr)
91 91
 	}
92 92
 	if addr == "" {
93 93
 		addr = defaultAddr
94 94
 	}
95
-	return fmt.Sprintf("unix://%s", addr), nil
95
+	return fmt.Sprintf("%s://%s", proto, addr), nil
96 96
 }
97 97
 
98 98
 // parseTCPAddr parses and validates that the specified address is a valid TCP
... ...
@@ -1,7 +1,7 @@
1 1
 package opts
2 2
 
3 3
 import (
4
-	"runtime"
4
+	"fmt"
5 5
 	"testing"
6 6
 )
7 7
 
... ...
@@ -15,51 +15,41 @@ func TestParseHost(t *testing.T) {
15 15
 		"tcp://invalid":      "Invalid bind address format: invalid",
16 16
 		"tcp://invalid:port": "Invalid bind address format: invalid:port",
17 17
 	}
18
-	const defaultHTTPHost = "tcp://127.0.0.1:2375"
19
-	var defaultHOST = "unix:///var/run/docker.sock"
20
-
21
-	if runtime.GOOS == "windows" {
22
-		defaultHOST = defaultHTTPHost
23
-	}
24 18
 	valid := map[string]string{
25
-		"":                         defaultHOST,
19
+		"":                         DefaultHost,
20
+		" ":                        DefaultHost,
21
+		"  ":                       DefaultHost,
26 22
 		"fd://":                    "fd://",
27 23
 		"fd://something":           "fd://something",
28
-		"tcp://host:":              "tcp://host:2375",
29
-		"tcp://":                   "tcp://localhost:2375",
30
-		"tcp://:2375":              "tcp://localhost:2375", // default ip address
31
-		"tcp://:2376":              "tcp://localhost:2376", // default ip address
24
+		"tcp://host:":              fmt.Sprintf("tcp://host:%d", DefaultHTTPPort),
25
+		"tcp://":                   DefaultTCPHost,
26
+		"tcp://:2375":              fmt.Sprintf("tcp://%s:2375", DefaultHTTPHost),
27
+		"tcp://:2376":              fmt.Sprintf("tcp://%s:2376", DefaultHTTPHost),
32 28
 		"tcp://0.0.0.0:8080":       "tcp://0.0.0.0:8080",
33 29
 		"tcp://192.168.0.0:12000":  "tcp://192.168.0.0:12000",
34 30
 		"tcp://192.168:8080":       "tcp://192.168:8080",
35 31
 		"tcp://0.0.0.0:1234567890": "tcp://0.0.0.0:1234567890", // yeah it's valid :P
32
+		" tcp://:7777/path ":       fmt.Sprintf("tcp://%s:7777/path", DefaultHTTPHost),
36 33
 		"tcp://docker.com:2375":    "tcp://docker.com:2375",
37
-		"unix://":                  "unix:///var/run/docker.sock", // default unix:// value
34
+		"unix://":                  "unix://" + DefaultUnixSocket,
38 35
 		"unix://path/to/socket":    "unix://path/to/socket",
36
+		"npipe://":                 "npipe://" + DefaultNamedPipe,
37
+		"npipe:////./pipe/foo":     "npipe:////./pipe/foo",
39 38
 	}
40 39
 
41 40
 	for value, errorMessage := range invalid {
42
-		if _, err := ParseHost(defaultHTTPHost, value); err == nil || err.Error() != errorMessage {
43
-			t.Fatalf("Expected an error for %v with [%v], got [%v]", value, errorMessage, err)
41
+		if _, err := ParseHost(false, value); err == nil || err.Error() != errorMessage {
42
+			t.Errorf("Expected an error for %v with [%v], got [%v]", value, errorMessage, err)
44 43
 		}
45 44
 	}
46 45
 	for value, expected := range valid {
47
-		if actual, err := ParseHost(defaultHTTPHost, value); err != nil || actual != expected {
48
-			t.Fatalf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err)
46
+		if actual, err := ParseHost(false, value); err != nil || actual != expected {
47
+			t.Errorf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err)
49 48
 		}
50 49
 	}
51 50
 }
52 51
 
53 52
 func TestParseDockerDaemonHost(t *testing.T) {
54
-	var (
55
-		defaultHTTPHost  = "tcp://localhost:2375"
56
-		defaultHTTPSHost = "tcp://localhost:2376"
57
-		defaultUnix      = "/var/run/docker.sock"
58
-		defaultHOST      = "unix:///var/run/docker.sock"
59
-	)
60
-	if runtime.GOOS == "windows" {
61
-		defaultHOST = defaultHTTPHost
62
-	}
63 53
 	invalids := map[string]string{
64 54
 		"0.0.0.0":                       "Invalid bind address format: 0.0.0.0",
65 55
 		"tcp:a.b.c.d":                   "Invalid bind address format: tcp:a.b.c.d",
... ...
@@ -67,9 +57,11 @@ func TestParseDockerDaemonHost(t *testing.T) {
67 67
 		"udp://127.0.0.1":               "Invalid bind address format: udp://127.0.0.1",
68 68
 		"udp://127.0.0.1:2375":          "Invalid bind address format: udp://127.0.0.1:2375",
69 69
 		"tcp://unix:///run/docker.sock": "Invalid bind address format: unix",
70
-		"tcp":  "Invalid bind address format: tcp",
71
-		"unix": "Invalid bind address format: unix",
72
-		"fd":   "Invalid bind address format: fd",
70
+		" tcp://:7777/path ":            "Invalid bind address format:  tcp://:7777/path ",
71
+		"tcp":                           "Invalid bind address format: tcp",
72
+		"unix":                          "Invalid bind address format: unix",
73
+		"fd":                            "Invalid bind address format: fd",
74
+		"":                              "Invalid bind address format: ",
73 75
 	}
74 76
 	valids := map[string]string{
75 77
 		"0.0.0.1:":                    "tcp://0.0.0.1:2375",
... ...
@@ -79,17 +71,13 @@ func TestParseDockerDaemonHost(t *testing.T) {
79 79
 		"[::1]:5555/path":             "tcp://[::1]:5555/path",
80 80
 		"[0:0:0:0:0:0:0:1]:":          "tcp://[0:0:0:0:0:0:0:1]:2375",
81 81
 		"[0:0:0:0:0:0:0:1]:5555/path": "tcp://[0:0:0:0:0:0:0:1]:5555/path",
82
-		":6666":                   "tcp://localhost:6666",
83
-		":6666/path":              "tcp://localhost:6666/path",
84
-		"":                        defaultHOST,
85
-		" ":                       defaultHOST,
86
-		"  ":                      defaultHOST,
87
-		"tcp://":                  defaultHTTPHost,
88
-		"tcp://:7777":             "tcp://localhost:7777",
89
-		"tcp://:7777/path":        "tcp://localhost:7777/path",
90
-		" tcp://:7777/path ":      "tcp://localhost:7777/path",
82
+		":6666":                   fmt.Sprintf("tcp://%s:6666", DefaultHTTPHost),
83
+		":6666/path":              fmt.Sprintf("tcp://%s:6666/path", DefaultHTTPHost),
84
+		"tcp://":                  DefaultTCPHost,
85
+		"tcp://:7777":             fmt.Sprintf("tcp://%s:7777", DefaultHTTPHost),
86
+		"tcp://:7777/path":        fmt.Sprintf("tcp://%s:7777/path", DefaultHTTPHost),
91 87
 		"unix:///run/docker.sock": "unix:///run/docker.sock",
92
-		"unix://":                 "unix:///var/run/docker.sock",
88
+		"unix://":                 "unix://" + DefaultUnixSocket,
93 89
 		"fd://":                   "fd://",
94 90
 		"fd://something":          "fd://something",
95 91
 		"localhost:":              "tcp://localhost:2375",
... ...
@@ -97,12 +85,12 @@ func TestParseDockerDaemonHost(t *testing.T) {
97 97
 		"localhost:5555/path":     "tcp://localhost:5555/path",
98 98
 	}
99 99
 	for invalidAddr, expectedError := range invalids {
100
-		if addr, err := parseDockerDaemonHost(defaultHTTPHost, defaultHTTPSHost, defaultUnix, "", invalidAddr); err == nil || err.Error() != expectedError {
100
+		if addr, err := parseDockerDaemonHost(invalidAddr); err == nil || err.Error() != expectedError {
101 101
 			t.Errorf("tcp %v address expected error %v return, got %s and addr %v", invalidAddr, expectedError, err, addr)
102 102
 		}
103 103
 	}
104 104
 	for validAddr, expectedAddr := range valids {
105
-		if addr, err := parseDockerDaemonHost(defaultHTTPHost, defaultHTTPSHost, defaultUnix, "", validAddr); err != nil || addr != expectedAddr {
105
+		if addr, err := parseDockerDaemonHost(validAddr); err != nil || addr != expectedAddr {
106 106
 			t.Errorf("%v -> expected %v, got (%v) addr (%v)", validAddr, expectedAddr, err, addr)
107 107
 		}
108 108
 	}
... ...
@@ -152,13 +140,13 @@ func TestParseTCP(t *testing.T) {
152 152
 }
153 153
 
154 154
 func TestParseInvalidUnixAddrInvalid(t *testing.T) {
155
-	if _, err := parseUnixAddr("tcp://127.0.0.1", "unix:///var/run/docker.sock"); err == nil || err.Error() != "Invalid proto, expected unix: tcp://127.0.0.1" {
155
+	if _, err := parseSimpleProtoAddr("unix", "tcp://127.0.0.1", "unix:///var/run/docker.sock"); err == nil || err.Error() != "Invalid proto, expected unix: tcp://127.0.0.1" {
156 156
 		t.Fatalf("Expected an error, got %v", err)
157 157
 	}
158
-	if _, err := parseUnixAddr("unix://tcp://127.0.0.1", "/var/run/docker.sock"); err == nil || err.Error() != "Invalid proto, expected unix: tcp://127.0.0.1" {
158
+	if _, err := parseSimpleProtoAddr("unix", "unix://tcp://127.0.0.1", "/var/run/docker.sock"); err == nil || err.Error() != "Invalid proto, expected unix: tcp://127.0.0.1" {
159 159
 		t.Fatalf("Expected an error, got %v", err)
160 160
 	}
161
-	if v, err := parseUnixAddr("", "/var/run/docker.sock"); err != nil || v != "unix:///var/run/docker.sock" {
161
+	if v, err := parseSimpleProtoAddr("unix", "", "/var/run/docker.sock"); err != nil || v != "unix:///var/run/docker.sock" {
162 162
 		t.Fatalf("Expected an %v, got %v", v, "unix:///var/run/docker.sock")
163 163
 	}
164 164
 }