Browse Source

Merge pull request #19911 from Microsoft/jstarks/npipe

Windows: Add support for named pipe protocol
Alexander Morozov 9 years ago
parent
commit
83ee24e52b

+ 1 - 6
api/client/cli.go

@@ -176,12 +176,7 @@ func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (host string,
 		return "", errors.New("Please specify only one -H")
 	}
 
-	defaultHost := opts.DefaultTCPHost
-	if tlsOptions != nil {
-		defaultHost = opts.DefaultTLSHost
-	}
-
-	host, err = opts.ParseHost(defaultHost, host)
+	host, err = opts.ParseHost(tlsOptions != nil, host)
 	return
 }
 

+ 22 - 1
api/server/server_windows.go

@@ -4,8 +4,11 @@ package server
 
 import (
 	"errors"
+	"fmt"
+	"github.com/Microsoft/go-winio"
 	"net"
 	"net/http"
+	"strings"
 )
 
 // NewServer sets up the required Server and does protocol specific checking.
@@ -21,8 +24,26 @@ func (s *Server) newServer(proto, addr string) ([]*HTTPServer, error) {
 		}
 		ls = append(ls, l)
 
+	case "npipe":
+		// allow Administrators and SYSTEM, plus whatever additional users or groups were specified
+		sddl := "D:P(A;;GA;;;BA)(A;;GA;;;SY)"
+		if s.cfg.SocketGroup != "" {
+			for _, g := range strings.Split(s.cfg.SocketGroup, ",") {
+				sid, err := winio.LookupSidByName(g)
+				if err != nil {
+					return nil, err
+				}
+				sddl += fmt.Sprintf("(A;;GRGW;;;%s)", sid)
+			}
+		}
+		l, err := winio.ListenPipe(addr, sddl)
+		if err != nil {
+			return nil, err
+		}
+		ls = append(ls, l)
+
 	default:
-		return nil, errors.New("Invalid protocol format. Windows only supports tcp.")
+		return nil, errors.New("Invalid protocol format. Windows only supports tcp and npipe.")
 	}
 
 	var res []*HTTPServer

+ 1 - 0
daemon/config.go

@@ -68,6 +68,7 @@ type CommonConfig struct {
 	Pidfile              string              `json:"pidfile,omitempty"`
 	RawLogs              bool                `json:"raw-logs,omitempty"`
 	Root                 string              `json:"graph,omitempty"`
+	SocketGroup          string              `json:"group,omitempty"`
 	TrustKeyPath         string              `json:"-"`
 
 	// ClusterStore is the storage backend used for the cluster information. It is used by both

+ 0 - 1
daemon/config_unix.go

@@ -29,7 +29,6 @@ type Config struct {
 	EnableCors           bool                     `json:"api-enable-cors,omitempty"`
 	EnableSelinuxSupport bool                     `json:"selinux-enabled,omitempty"`
 	RemappedRoot         string                   `json:"userns-remap,omitempty"`
-	SocketGroup          string                   `json:"group,omitempty"`
 	CgroupParent         string                   `json:"cgroup-parent,omitempty"`
 	Ulimits              map[string]*units.Ulimit `json:"default-ulimits,omitempty"`
 }

+ 1 - 0
daemon/config_windows.go

@@ -38,4 +38,5 @@ func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) strin
 
 	// Then platform-specific install flags.
 	cmd.StringVar(&config.bridgeConfig.VirtualSwitchName, []string{"b", "-bridge"}, "", "Attach containers to a virtual switch")
+	cmd.StringVar(&config.SocketGroup, []string{"G", "-group"}, "", usageFn("Users or groups that can access the named pipe"))
 }

+ 2 - 3
docker/daemon.go

@@ -200,11 +200,11 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
 	serverConfig := &apiserver.Config{
 		AuthorizationPluginNames: cli.Config.AuthorizationPlugins,
 		Logging:                  true,
+		SocketGroup:              cli.Config.SocketGroup,
 		Version:                  dockerversion.Version,
 	}
 	serverConfig = setPlatformServerConfig(serverConfig, cli.Config)
 
-	defaultHost := opts.DefaultHost
 	if cli.Config.TLS {
 		tlsOptions := tlsconfig.Options{
 			CAFile:   cli.Config.CommonTLSOptions.CAFile,
@@ -221,7 +221,6 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
 			logrus.Fatal(err)
 		}
 		serverConfig.TLSConfig = tlsConfig
-		defaultHost = opts.DefaultTLSHost
 	}
 
 	if len(cli.Config.Hosts) == 0 {
@@ -229,7 +228,7 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
 	}
 	for i := 0; i < len(cli.Config.Hosts); i++ {
 		var err error
-		if cli.Config.Hosts[i], err = opts.ParseHost(defaultHost, cli.Config.Hosts[i]); err != nil {
+		if cli.Config.Hosts[i], err = opts.ParseHost(cli.Config.TLS, cli.Config.Hosts[i]); err != nil {
 			logrus.Fatalf("error parsing -H %s : %v", cli.Config.Hosts[i], err)
 		}
 

+ 0 - 1
docker/daemon_unix.go

@@ -19,7 +19,6 @@ import (
 const defaultDaemonConfigFile = "/etc/docker/daemon.json"
 
 func setPlatformServerConfig(serverConfig *apiserver.Config, daemonCfg *daemon.Config) *apiserver.Config {
-	serverConfig.SocketGroup = daemonCfg.SocketGroup
 	serverConfig.EnableCors = daemonCfg.EnableCors
 	serverConfig.CorsHeaders = daemonCfg.CorsHeaders
 

+ 38 - 36
opts/hosts.go

@@ -4,16 +4,12 @@ import (
 	"fmt"
 	"net"
 	"net/url"
-	"runtime"
 	"strconv"
 	"strings"
 )
 
 var (
 	// DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. docker daemon -H tcp://
-	// TODO Windows. DefaultHTTPPort is only used on Windows if a -H parameter
-	// is not supplied. A better longer term solution would be to use a named
-	// pipe as the default on the Windows daemon.
 	// 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
@@ -26,13 +22,19 @@ var (
 	DefaultTCPHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort)
 	// DefaultTLSHost constant defines the default host string used by docker for TLS sockets
 	DefaultTLSHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultTLSHTTPPort)
+	// DefaultNamedPipe defines the default named pipe used by docker on Windows
+	DefaultNamedPipe = `//./pipe/docker_engine`
 )
 
 // ValidateHost validates that the specified string is a valid host and returns it.
 func ValidateHost(val string) (string, error) {
-	_, err := parseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, "", val)
-	if err != nil {
-		return val, err
+	host := strings.TrimSpace(val)
+	// The empty string means default and is not handled by parseDockerDaemonHost
+	if host != "" {
+		_, err := parseDockerDaemonHost(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
@@ -40,39 +42,39 @@ func ValidateHost(val string) (string, error) {
 }
 
 // ParseHost and set defaults for a Daemon host string
-func ParseHost(defaultHost, val string) (string, error) {
-	host, err := parseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, defaultHost, val)
-	if err != nil {
-		return val, err
+func ParseHost(defaultToTLS bool, val string) (string, error) {
+	host := strings.TrimSpace(val)
+	if host == "" {
+		if defaultToTLS {
+			host = DefaultTLSHost
+		} else {
+			host = DefaultHost
+		}
+	} else {
+		var err error
+		host, err = parseDockerDaemonHost(host)
+		if err != nil {
+			return val, err
+		}
 	}
 	return host, nil
 }
 
 // parseDockerDaemonHost parses the specified address and returns an address that will be used as the host.
-// Depending of the address specified, will use the defaultTCPAddr or defaultUnixAddr
-// defaultUnixAddr must be a absolute file path (no `unix://` prefix)
-// defaultTCPAddr must be the full `tcp://host:port` form
-func parseDockerDaemonHost(defaultTCPAddr, defaultTLSHost, defaultUnixAddr, defaultAddr, addr string) (string, error) {
-	addr = strings.TrimSpace(addr)
-	if addr == "" {
-		if defaultAddr == defaultTLSHost {
-			return defaultTLSHost, nil
-		}
-		if runtime.GOOS != "windows" {
-			return fmt.Sprintf("unix://%s", defaultUnixAddr), nil
-		}
-		return defaultTCPAddr, nil
-	}
+// Depending of the address specified, this may return one of the global Default* strings defined in hosts.go.
+func parseDockerDaemonHost(addr string) (string, error) {
 	addrParts := strings.Split(addr, "://")
-	if len(addrParts) == 1 {
+	if len(addrParts) == 1 && addrParts[0] != "" {
 		addrParts = []string{"tcp", addrParts[0]}
 	}
 
 	switch addrParts[0] {
 	case "tcp":
-		return parseTCPAddr(addrParts[1], defaultTCPAddr)
+		return parseTCPAddr(addrParts[1], DefaultTCPHost)
 	case "unix":
-		return parseUnixAddr(addrParts[1], defaultUnixAddr)
+		return parseSimpleProtoAddr("unix", addrParts[1], DefaultUnixSocket)
+	case "npipe":
+		return parseSimpleProtoAddr("npipe", addrParts[1], DefaultNamedPipe)
 	case "fd":
 		return addr, nil
 	default:
@@ -80,19 +82,19 @@ func parseDockerDaemonHost(defaultTCPAddr, defaultTLSHost, defaultUnixAddr, defa
 	}
 }
 
-// parseUnixAddr parses and validates that the specified address is a valid UNIX
-// socket address. It returns a formatted UNIX socket address, either using the
-// address parsed from addr, or the contents of defaultAddr if addr is a blank
-// string.
-func parseUnixAddr(addr string, defaultAddr string) (string, error) {
-	addr = strings.TrimPrefix(addr, "unix://")
+// 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) {
+	addr = strings.TrimPrefix(addr, proto+"://")
 	if strings.Contains(addr, "://") {
-		return "", fmt.Errorf("Invalid proto, expected unix: %s", addr)
+		return "", fmt.Errorf("Invalid proto, expected %s: %s", proto, addr)
 	}
 	if addr == "" {
 		addr = defaultAddr
 	}
-	return fmt.Sprintf("unix://%s", addr), nil
+	return fmt.Sprintf("%s://%s", proto, addr), nil
 }
 
 // parseTCPAddr parses and validates that the specified address is a valid TCP

+ 32 - 44
opts/hosts_test.go

@@ -1,7 +1,7 @@
 package opts
 
 import (
-	"runtime"
+	"fmt"
 	"testing"
 )
 
@@ -15,51 +15,41 @@ func TestParseHost(t *testing.T) {
 		"tcp://invalid":      "Invalid bind address format: invalid",
 		"tcp://invalid:port": "Invalid bind address format: invalid:port",
 	}
-	const defaultHTTPHost = "tcp://127.0.0.1:2375"
-	var defaultHOST = "unix:///var/run/docker.sock"
-
-	if runtime.GOOS == "windows" {
-		defaultHOST = defaultHTTPHost
-	}
 	valid := map[string]string{
-		"":                         defaultHOST,
+		"":                         DefaultHost,
+		" ":                        DefaultHost,
+		"  ":                       DefaultHost,
 		"fd://":                    "fd://",
 		"fd://something":           "fd://something",
-		"tcp://host:":              "tcp://host:2375",
-		"tcp://":                   "tcp://localhost:2375",
-		"tcp://:2375":              "tcp://localhost:2375", // default ip address
-		"tcp://:2376":              "tcp://localhost:2376", // default ip address
+		"tcp://host:":              fmt.Sprintf("tcp://host:%d", DefaultHTTPPort),
+		"tcp://":                   DefaultTCPHost,
+		"tcp://:2375":              fmt.Sprintf("tcp://%s:2375", DefaultHTTPHost),
+		"tcp://:2376":              fmt.Sprintf("tcp://%s:2376", DefaultHTTPHost),
 		"tcp://0.0.0.0:8080":       "tcp://0.0.0.0:8080",
 		"tcp://192.168.0.0:12000":  "tcp://192.168.0.0:12000",
 		"tcp://192.168:8080":       "tcp://192.168:8080",
 		"tcp://0.0.0.0:1234567890": "tcp://0.0.0.0:1234567890", // yeah it's valid :P
+		" tcp://:7777/path ":       fmt.Sprintf("tcp://%s:7777/path", DefaultHTTPHost),
 		"tcp://docker.com:2375":    "tcp://docker.com:2375",
-		"unix://":                  "unix:///var/run/docker.sock", // default unix:// value
+		"unix://":                  "unix://" + DefaultUnixSocket,
 		"unix://path/to/socket":    "unix://path/to/socket",
+		"npipe://":                 "npipe://" + DefaultNamedPipe,
+		"npipe:////./pipe/foo":     "npipe:////./pipe/foo",
 	}
 
 	for value, errorMessage := range invalid {
-		if _, err := ParseHost(defaultHTTPHost, value); err == nil || err.Error() != errorMessage {
-			t.Fatalf("Expected an error for %v with [%v], got [%v]", value, errorMessage, err)
+		if _, err := ParseHost(false, value); err == nil || err.Error() != errorMessage {
+			t.Errorf("Expected an error for %v with [%v], got [%v]", value, errorMessage, err)
 		}
 	}
 	for value, expected := range valid {
-		if actual, err := ParseHost(defaultHTTPHost, value); err != nil || actual != expected {
-			t.Fatalf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err)
+		if actual, err := ParseHost(false, value); err != nil || actual != expected {
+			t.Errorf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err)
 		}
 	}
 }
 
 func TestParseDockerDaemonHost(t *testing.T) {
-	var (
-		defaultHTTPHost  = "tcp://localhost:2375"
-		defaultHTTPSHost = "tcp://localhost:2376"
-		defaultUnix      = "/var/run/docker.sock"
-		defaultHOST      = "unix:///var/run/docker.sock"
-	)
-	if runtime.GOOS == "windows" {
-		defaultHOST = defaultHTTPHost
-	}
 	invalids := map[string]string{
 		"0.0.0.0":                       "Invalid bind address format: 0.0.0.0",
 		"tcp:a.b.c.d":                   "Invalid bind address format: tcp:a.b.c.d",
@@ -67,9 +57,11 @@ func TestParseDockerDaemonHost(t *testing.T) {
 		"udp://127.0.0.1":               "Invalid bind address format: udp://127.0.0.1",
 		"udp://127.0.0.1:2375":          "Invalid bind address format: udp://127.0.0.1:2375",
 		"tcp://unix:///run/docker.sock": "Invalid bind address format: unix",
-		"tcp":  "Invalid bind address format: tcp",
-		"unix": "Invalid bind address format: unix",
-		"fd":   "Invalid bind address format: fd",
+		" tcp://:7777/path ":            "Invalid bind address format:  tcp://:7777/path ",
+		"tcp":                           "Invalid bind address format: tcp",
+		"unix":                          "Invalid bind address format: unix",
+		"fd":                            "Invalid bind address format: fd",
+		"":                              "Invalid bind address format: ",
 	}
 	valids := map[string]string{
 		"0.0.0.1:":                    "tcp://0.0.0.1:2375",
@@ -79,17 +71,13 @@ func TestParseDockerDaemonHost(t *testing.T) {
 		"[::1]:5555/path":             "tcp://[::1]:5555/path",
 		"[0:0:0:0:0:0:0:1]:":          "tcp://[0:0:0:0:0:0:0:1]:2375",
 		"[0:0:0:0:0:0:0:1]:5555/path": "tcp://[0:0:0:0:0:0:0:1]:5555/path",
-		":6666":                   "tcp://localhost:6666",
-		":6666/path":              "tcp://localhost:6666/path",
-		"":                        defaultHOST,
-		" ":                       defaultHOST,
-		"  ":                      defaultHOST,
-		"tcp://":                  defaultHTTPHost,
-		"tcp://:7777":             "tcp://localhost:7777",
-		"tcp://:7777/path":        "tcp://localhost:7777/path",
-		" tcp://:7777/path ":      "tcp://localhost:7777/path",
+		":6666":                   fmt.Sprintf("tcp://%s:6666", DefaultHTTPHost),
+		":6666/path":              fmt.Sprintf("tcp://%s:6666/path", DefaultHTTPHost),
+		"tcp://":                  DefaultTCPHost,
+		"tcp://:7777":             fmt.Sprintf("tcp://%s:7777", DefaultHTTPHost),
+		"tcp://:7777/path":        fmt.Sprintf("tcp://%s:7777/path", DefaultHTTPHost),
 		"unix:///run/docker.sock": "unix:///run/docker.sock",
-		"unix://":                 "unix:///var/run/docker.sock",
+		"unix://":                 "unix://" + DefaultUnixSocket,
 		"fd://":                   "fd://",
 		"fd://something":          "fd://something",
 		"localhost:":              "tcp://localhost:2375",
@@ -97,12 +85,12 @@ func TestParseDockerDaemonHost(t *testing.T) {
 		"localhost:5555/path":     "tcp://localhost:5555/path",
 	}
 	for invalidAddr, expectedError := range invalids {
-		if addr, err := parseDockerDaemonHost(defaultHTTPHost, defaultHTTPSHost, defaultUnix, "", invalidAddr); err == nil || err.Error() != expectedError {
+		if addr, err := parseDockerDaemonHost(invalidAddr); err == nil || err.Error() != expectedError {
 			t.Errorf("tcp %v address expected error %v return, got %s and addr %v", invalidAddr, expectedError, err, addr)
 		}
 	}
 	for validAddr, expectedAddr := range valids {
-		if addr, err := parseDockerDaemonHost(defaultHTTPHost, defaultHTTPSHost, defaultUnix, "", validAddr); err != nil || addr != expectedAddr {
+		if addr, err := parseDockerDaemonHost(validAddr); err != nil || addr != expectedAddr {
 			t.Errorf("%v -> expected %v, got (%v) addr (%v)", validAddr, expectedAddr, err, addr)
 		}
 	}
@@ -152,13 +140,13 @@ func TestParseTCP(t *testing.T) {
 }
 
 func TestParseInvalidUnixAddrInvalid(t *testing.T) {
-	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" {
+	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" {
 		t.Fatalf("Expected an error, got %v", err)
 	}
-	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" {
+	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" {
 		t.Fatalf("Expected an error, got %v", err)
 	}
-	if v, err := parseUnixAddr("", "/var/run/docker.sock"); err != nil || v != "unix:///var/run/docker.sock" {
+	if v, err := parseSimpleProtoAddr("unix", "", "/var/run/docker.sock"); err != nil || v != "unix:///var/run/docker.sock" {
 		t.Fatalf("Expected an %v, got %v", v, "unix:///var/run/docker.sock")
 	}
 }