diff --git a/api/server/server.go b/api/server/server.go index 97349959a3..ddc9958b3b 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -27,6 +27,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/docker/api" + "github.com/docker/docker/daemon/networkdriver/portallocator" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/listenbuffer" "github.com/docker/docker/pkg/parsers" @@ -1493,6 +1494,32 @@ func setupUnixHttp(addr string, job *engine.Job) (*HttpServer, error) { return &HttpServer{&http.Server{Addr: addr, Handler: r}, l}, nil } +func allocateDaemonPort(addr string) error { + host, port, err := net.SplitHostPort(addr) + if err != nil { + return err + } + + intPort, err := strconv.Atoi(port) + if err != nil { + return err + } + + var hostIPs []net.IP + if parsedIP := net.ParseIP(host); parsedIP != nil { + hostIPs = append(hostIPs, parsedIP) + } else if hostIPs, err = net.LookupIP(host); err != nil { + return fmt.Errorf("failed to lookup %s address in host specification", host) + } + + for _, hostIP := range hostIPs { + if _, err := portallocator.RequestPort(hostIP, "tcp", intPort); err != nil { + return fmt.Errorf("failed to allocate daemon listening port %d (err: %v)", intPort, err) + } + } + return nil +} + func setupTcpHttp(addr string, job *engine.Job) (*HttpServer, error) { if !strings.HasPrefix(addr, "127.0.0.1") && !job.GetenvBool("TlsVerify") { log.Infof("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") @@ -1508,6 +1535,10 @@ func setupTcpHttp(addr string, job *engine.Job) (*HttpServer, error) { return nil, err } + if err := allocateDaemonPort(addr); err != nil { + return nil, err + } + if job.GetenvBool("Tls") || job.GetenvBool("TlsVerify") { var tlsCa string if job.GetenvBool("TlsVerify") { diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 31bfac3f67..13b0a22a9a 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "fmt" "io/ioutil" "os" "os/exec" @@ -284,3 +285,33 @@ func TestDaemonLoggingLevel(t *testing.T) { logDone("daemon - Logging Level") } + +func TestDaemonAllocatesListeningPort(t *testing.T) { + listeningPorts := [][]string{ + {"0.0.0.0", "0.0.0.0", "5678"}, + {"127.0.0.1", "127.0.0.1", "1234"}, + {"localhost", "127.0.0.1", "1235"}, + } + + cmdArgs := []string{} + for _, hostDirective := range listeningPorts { + cmdArgs = append(cmdArgs, "--host", fmt.Sprintf("tcp://%s:%s", hostDirective[0], hostDirective[2])) + } + + d := NewDaemon(t) + if err := d.StartWithBusybox(cmdArgs...); err != nil { + t.Fatalf("Could not start daemon with busybox: %v", err) + } + defer d.Stop() + + for _, hostDirective := range listeningPorts { + output, err := d.Cmd("run", "-p", fmt.Sprintf("%s:%s:80", hostDirective[1], hostDirective[2]), "busybox", "true") + if err == nil { + t.Fatalf("Container should not start, expected port already allocated error: %q", output) + } else if !strings.Contains(output, "port is already allocated") { + t.Fatalf("Expected port is already allocated error: %q", output) + } + } + + logDone("daemon - daemon listening port is allocated") +}