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>
This commit is contained in:
John Starks 2016-01-30 18:45:49 -08:00 committed by Vincent Demeester
parent d611dc46c0
commit 5d648746e8
2 changed files with 70 additions and 80 deletions

View File

@ -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,53 +22,59 @@ 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)
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
return val, nil
}
// ParseHost and set defaults for a Daemon host string
func ParseHost(defaultHost, val string) (string, error) {
host, err := parseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, defaultHost, val)
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

View File

@ -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://: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")
}
}