diff --git a/daemon/networkdriver/portmapper/proxy.go b/daemon/networkdriver/portmapper/proxy.go index 1e8e0c3987..341f0605e5 100644 --- a/daemon/networkdriver/portmapper/proxy.go +++ b/daemon/networkdriver/portmapper/proxy.go @@ -36,16 +36,18 @@ type proxyCommand struct { // execProxy is the reexec function that is registered to start the userland proxies func execProxy() { + f := os.NewFile(3, "signal-parent") host, container := parseHostContainerAddrs() p, err := proxy.NewProxy(host, container) if err != nil { - os.Stdout.WriteString("1\n") - fmt.Fprint(os.Stderr, err) + fmt.Fprintf(f, "1\n%s", err) + f.Close() os.Exit(1) } go handleStopSignals(p) - os.Stdout.WriteString("0\n") + fmt.Fprint(f, "0\n") + f.Close() // Run will block until the proxy stops p.Run() @@ -111,27 +113,24 @@ func NewProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net. } func (p *proxyCommand) Start() error { - stdout, err := p.cmd.StdoutPipe() + r, w, err := os.Pipe() if err != nil { - return err + return fmt.Errorf("proxy unable to open os.Pipe %s", err) } - defer stdout.Close() - stderr, err := p.cmd.StderrPipe() - if err != nil { - return err - } - defer stderr.Close() + defer r.Close() + p.cmd.ExtraFiles = []*os.File{w} if err := p.cmd.Start(); err != nil { return err } + w.Close() errchan := make(chan error, 1) go func() { buf := make([]byte, 2) - stdout.Read(buf) + r.Read(buf) if string(buf) != "0\n" { - errStr, _ := ioutil.ReadAll(stderr) + errStr, _ := ioutil.ReadAll(r) errchan <- fmt.Errorf("Error starting userland proxy: %s", errStr) return } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 3e11de96f6..0c3ea4fd4a 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -2093,6 +2093,39 @@ func TestRunPortInUse(t *testing.T) { logDone("run - fail if port already in use") } +// https://github.com/docker/docker/issues/8428 +func TestRunPortProxy(t *testing.T) { + defer deleteAllContainers() + + port := "12345" + cmd := exec.Command(dockerBinary, "run", "-p", port+":80", "busybox", "true") + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatalf("Failed to run and bind port %s, output: %s, error: %s", port, out, err) + } + + // connect for 10 times here. This will trigger 10 EPIPES in the child + // process and kill it when it writes to a closed stdout/stderr + for i := 0; i < 10; i++ { + net.Dial("tcp", fmt.Sprintf("0.0.0.0:%s", port)) + } + + listPs := exec.Command("sh", "-c", "ps ax | grep docker") + out, _, err = runCommandWithOutput(listPs) + if err != nil { + t.Errorf("list docker process failed with output %s, error %s", out, err) + } + if strings.Contains(out, "docker ") { + t.Errorf("Unexpected defunct docker process") + } + if !strings.Contains(out, "docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 12345") { + t.Errorf("Failed to find docker-proxy process, got %s", out) + } + + logDone("run - proxy should work with unavailable port") +} + // Regression test for #7792 func TestRunMountOrdering(t *testing.T) { tmpDir, err := ioutil.TempDir("", "docker_nested_mount_test")