Explorar el Código

Fix listener parsering regression when no addr set

5008409b5cebe36690f653b71f171cf4e1ce114c introduced the usage of
`strings.Cut` to help parse listener addresses.
Part of that also made it error out if no addr is specified after the
protocol spec (e.g. `tcp://`).

Before the change a proto spec without an address just used the default
address for that proto.
e.g. `tcp://` would be `tcp://127.0.0.1:2375`, `unix://` would be
`unix:///var/run/docker.sock`.
Critically, socket activation (`fd://`) never has an address.

This change brings back the old behavior but keeps the usage of
`strings.Cut`.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
Brian Goff hace 2 años
padre
commit
37a9d6aabe
Se han modificado 3 ficheros con 107 adiciones y 1 borrados
  1. 1 1
      cmd/dockerd/daemon.go
  2. 90 0
      cmd/dockerd/daemon_linux_test.go
  3. 16 0
      cmd/dockerd/main_linux_test.go

+ 1 - 1
cmd/dockerd/daemon.go

@@ -655,7 +655,7 @@ func loadListeners(cli *DaemonCli, tlsConfig *tls.Config) ([]string, error) {
 	for i := 0; i < len(cli.Config.Hosts); i++ {
 	for i := 0; i < len(cli.Config.Hosts); i++ {
 		protoAddr := cli.Config.Hosts[i]
 		protoAddr := cli.Config.Hosts[i]
 		proto, addr, ok := strings.Cut(protoAddr, "://")
 		proto, addr, ok := strings.Cut(protoAddr, "://")
-		if !ok || addr == "" {
+		if !ok {
 			return nil, fmt.Errorf("bad format %s, expected PROTO://ADDR", protoAddr)
 			return nil, fmt.Errorf("bad format %s, expected PROTO://ADDR", protoAddr)
 		}
 		}
 
 

+ 90 - 0
cmd/dockerd/daemon_linux_test.go

@@ -0,0 +1,90 @@
+package main
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"os"
+	"strconv"
+	"testing"
+
+	"github.com/docker/docker/daemon/config"
+	"github.com/docker/docker/pkg/reexec"
+	"golang.org/x/sys/unix"
+	"gotest.tools/v3/assert"
+)
+
+const (
+	testListenerNoAddrCmdPhase1 = "test-listener-no-addr1"
+	testListenerNoAddrCmdPhase2 = "test-listener-no-addr2"
+)
+
+type listenerTestResponse struct {
+	Err string
+}
+
+func initListenerTestPhase1() {
+	os.Setenv("LISTEN_PID", strconv.Itoa(os.Getpid()))
+	os.Setenv("LISTEN_FDS", "1")
+
+	// NOTE: We cannot use O_CLOEXEC here because we need the fd to stay open for the child process.
+	_, err := unix.Socket(unix.AF_UNIX, unix.SOCK_STREAM, 0)
+	if err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+
+	cmd := reexec.Command(testListenerNoAddrCmdPhase2)
+	if err := unix.Exec(cmd.Path, cmd.Args, os.Environ()); err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+}
+
+func initListenerTestPhase2() {
+	cli := &DaemonCli{
+		Config: &config.Config{
+			CommonConfig: config.CommonConfig{
+				Hosts: []string{"fd://"},
+			},
+		},
+	}
+	_, err := loadListeners(cli, nil)
+	var resp listenerTestResponse
+	if err != nil {
+		resp.Err = err.Error()
+	}
+
+	if err := json.NewEncoder(os.Stdout).Encode(resp); err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+}
+
+// Test to make sure that the listen specs without an address are handled
+// It requires a 2-phase setup due to how socket activation works (which we are using to test).
+// It requires LISTEN_FDS and LISTEN_PID to be set in the environment.
+//
+// LISTEN_PID is used by socket activation to determine if the process is the one that should be activated.
+// LISTEN_FDS is used by socket activation to determine how many file descriptors are passed to the process.
+//
+// We can sort of fake this without using extra processes, but it ends up not
+// being a true test because that's not how socket activation is expected to
+// work and we'll end up with nil listeners since the test framework has other
+// file descriptors open.
+//
+// This is not currently testing `tcp://` or `unix://` listen specs without an address because those can conflict with the machine running the test.
+// This could be worked around by using linux namespaces, however that would require root privileges which unit tests don't typically have.
+func TestLoadListenerNoAddr(t *testing.T) {
+	cmd := reexec.Command(testListenerNoAddrCmdPhase1)
+	stdout := bytes.NewBuffer(nil)
+	cmd.Stdout = stdout
+	stderr := bytes.NewBuffer(nil)
+	cmd.Stderr = stderr
+
+	assert.NilError(t, cmd.Run(), stderr.String())
+
+	var resp listenerTestResponse
+	assert.NilError(t, json.NewDecoder(stdout).Decode(&resp))
+	assert.Equal(t, resp.Err, "")
+}

+ 16 - 0
cmd/dockerd/main_linux_test.go

@@ -0,0 +1,16 @@
+package main
+
+import (
+	"testing"
+
+	"github.com/docker/docker/pkg/reexec"
+)
+
+func TestMain(m *testing.M) {
+	reexec.Register(testListenerNoAddrCmdPhase1, initListenerTestPhase1)
+	reexec.Register(testListenerNoAddrCmdPhase2, initListenerTestPhase2)
+	if reexec.Init() {
+		return
+	}
+	m.Run()
+}