Skip to content

Commit ad12276

Browse files
authored
Merge pull request #4783 from laurazard/fix-no-abstract-sockets
cli-plugins: don't use abstract sockets on macOS
2 parents a226502 + 6d0b329 commit ad12276

File tree

5 files changed

+119
-62
lines changed

5 files changed

+119
-62
lines changed

cli-plugins/plugin/plugin.go

Lines changed: 3 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,19 @@ package plugin
33
import (
44
"context"
55
"encoding/json"
6-
"errors"
76
"fmt"
8-
"io"
9-
"net"
107
"os"
118
"sync"
129

1310
"github.com/docker/cli/cli"
1411
"github.com/docker/cli/cli-plugins/manager"
12+
"github.com/docker/cli/cli-plugins/socket"
1513
"github.com/docker/cli/cli/command"
1614
"github.com/docker/cli/cli/connhelper"
1715
"github.com/docker/docker/client"
1816
"github.com/spf13/cobra"
1917
)
2018

21-
// CLIPluginSocketEnvKey is used to pass the plugin being
22-
// executed the abstract socket name it should listen on to know
23-
// when the CLI has exited.
24-
const CLIPluginSocketEnvKey = "DOCKER_CLI_PLUGIN_SOCKET"
25-
2619
// PersistentPreRunE must be called by any plugin command (or
2720
// subcommand) which uses the cobra `PersistentPreRun*` hook. Plugins
2821
// which do not make use of `PersistentPreRun*` do not need to call
@@ -33,38 +26,6 @@ const CLIPluginSocketEnvKey = "DOCKER_CLI_PLUGIN_SOCKET"
3326
// called.
3427
var PersistentPreRunE func(*cobra.Command, []string) error
3528

36-
// closeOnCLISocketClose connects to the socket specified
37-
// by the DOCKER_CLI_PLUGIN_SOCKET env var, if present, and attempts
38-
// to read from it until it receives an EOF, which signals that
39-
// the CLI is going to exit and the plugin should also exit.
40-
func closeOnCLISocketClose(cancel func()) {
41-
socketAddr, ok := os.LookupEnv(CLIPluginSocketEnvKey)
42-
if !ok {
43-
// if a plugin compiled against a more recent version of docker/cli
44-
// is executed by an older CLI binary, ignore missing environment
45-
// variable and behave as usual
46-
return
47-
}
48-
addr, err := net.ResolveUnixAddr("unix", socketAddr)
49-
if err != nil {
50-
return
51-
}
52-
cliCloseConn, err := net.DialUnix("unix", nil, addr)
53-
if err != nil {
54-
return
55-
}
56-
57-
go func() {
58-
b := make([]byte, 1)
59-
for {
60-
_, err := cliCloseConn.Read(b)
61-
if errors.Is(err, io.EOF) {
62-
cancel()
63-
}
64-
}
65-
}()
66-
}
67-
6829
// RunPlugin executes the specified plugin command
6930
func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta manager.Metadata) error {
7031
tcmd := newPluginCommand(dockerCli, plugin, meta)
@@ -81,7 +42,8 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta manager
8142
}
8243
ctx, cancel := context.WithCancel(cmdContext)
8344
cmd.SetContext(ctx)
84-
closeOnCLISocketClose(cancel)
45+
// Set up the context to cancel based on signalling via CLI socket.
46+
socket.ConnectAndWait(cancel)
8547

8648
var opts []command.CLIOption
8749
if os.Getenv("DOCKER_CLI_PLUGIN_USE_DIAL_STDIO") != "" {

cli-plugins/socket/socket.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package socket
2+
3+
import (
4+
"errors"
5+
"io"
6+
"net"
7+
"os"
8+
9+
"github.com/docker/distribution/uuid"
10+
)
11+
12+
// EnvKey represents the well-known environment variable used to pass the plugin being
13+
// executed the socket name it should listen on to coordinate with the host CLI.
14+
const EnvKey = "DOCKER_CLI_PLUGIN_SOCKET"
15+
16+
// SetupConn sets up a Unix socket listener, establishes a goroutine to handle connections
17+
// and update the conn pointer, and returns the environment variable to pass to the plugin.
18+
func SetupConn(conn **net.UnixConn) (string, error) {
19+
listener, err := listen("docker_cli_" + uuid.Generate().String())
20+
if err != nil {
21+
return "", err
22+
}
23+
24+
accept(listener, conn)
25+
26+
return EnvKey + "=" + listener.Addr().String(), nil
27+
}
28+
29+
func accept(listener *net.UnixListener, conn **net.UnixConn) {
30+
defer listener.Close()
31+
32+
go func() {
33+
for {
34+
// ignore error here, if we failed to accept a connection,
35+
// conn is nil and we fallback to previous behavior
36+
*conn, _ = listener.AcceptUnix()
37+
// perform any platform-specific actions on accept (e.g. unlink non-abstract sockets)
38+
onAccept(*conn, listener)
39+
}
40+
}()
41+
}
42+
43+
// ConnectAndWait connects to the socket passed via well-known env var,
44+
// if present, and attempts to read from it until it receives an EOF, at which
45+
// point cb is called.
46+
func ConnectAndWait(cb func()) {
47+
socketAddr, ok := os.LookupEnv(EnvKey)
48+
if !ok {
49+
// if a plugin compiled against a more recent version of docker/cli
50+
// is executed by an older CLI binary, ignore missing environment
51+
// variable and behave as usual
52+
return
53+
}
54+
addr, err := net.ResolveUnixAddr("unix", socketAddr)
55+
if err != nil {
56+
return
57+
}
58+
conn, err := net.DialUnix("unix", nil, addr)
59+
if err != nil {
60+
return
61+
}
62+
63+
go func() {
64+
b := make([]byte, 1)
65+
for {
66+
_, err := conn.Read(b)
67+
if errors.Is(err, io.EOF) {
68+
cb()
69+
}
70+
}
71+
}()
72+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package socket
2+
3+
import (
4+
"net"
5+
"os"
6+
"path/filepath"
7+
"syscall"
8+
)
9+
10+
func listen(socketname string) (*net.UnixListener, error) {
11+
return net.ListenUnix("unix", &net.UnixAddr{
12+
Name: filepath.Join(os.TempDir(), socketname),
13+
Net: "unix",
14+
})
15+
}
16+
17+
func onAccept(conn *net.UnixConn, listener *net.UnixListener) {
18+
syscall.Unlink(listener.Addr().String())
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//go:build !darwin
2+
3+
package socket
4+
5+
import (
6+
"net"
7+
)
8+
9+
func listen(socketname string) (*net.UnixListener, error) {
10+
return net.ListenUnix("unix", &net.UnixAddr{
11+
Name: "@" + socketname,
12+
Net: "unix",
13+
})
14+
}
15+
16+
func onAccept(conn *net.UnixConn, listener *net.UnixListener) {
17+
// do nothing
18+
// while on darwin we would unlink here; on non-darwin the socket is abstract and not present on the filesystem
19+
}

cmd/docker/docker.go

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@ import (
1111

1212
"github.com/docker/cli/cli"
1313
pluginmanager "github.com/docker/cli/cli-plugins/manager"
14-
"github.com/docker/cli/cli-plugins/plugin"
14+
"github.com/docker/cli/cli-plugins/socket"
1515
"github.com/docker/cli/cli/command"
1616
"github.com/docker/cli/cli/command/commands"
1717
cliflags "github.com/docker/cli/cli/flags"
1818
"github.com/docker/cli/cli/version"
1919
platformsignals "github.com/docker/cli/cmd/docker/internal/signals"
20-
"github.com/docker/distribution/uuid"
2120
"github.com/docker/docker/api/types/versions"
2221
"github.com/pkg/errors"
2322
"github.com/sirupsen/logrus"
@@ -216,35 +215,21 @@ func setValidateArgs(dockerCli command.Cli, cmd *cobra.Command) {
216215
})
217216
}
218217

219-
func setupPluginSocket() (*net.UnixListener, error) {
220-
return net.ListenUnix("unix", &net.UnixAddr{
221-
Name: "@docker_cli_" + uuid.Generate().String(),
222-
Net: "unix",
223-
})
224-
}
225-
226218
func tryPluginRun(dockerCli command.Cli, cmd *cobra.Command, subcommand string, envs []string) error {
227219
plugincmd, err := pluginmanager.PluginRunCommand(dockerCli, subcommand, cmd)
228220
if err != nil {
229221
return err
230222
}
231-
plugincmd.Env = append(envs, plugincmd.Env...)
232223

224+
// Establish the plugin socket, adding it to the environment under a well-known key if successful.
233225
var conn *net.UnixConn
234-
listener, err := setupPluginSocket()
226+
socketenv, err := socket.SetupConn(&conn)
235227
if err == nil {
236-
defer listener.Close()
237-
plugincmd.Env = append(plugincmd.Env, plugin.CLIPluginSocketEnvKey+"="+listener.Addr().String())
238-
239-
go func() {
240-
for {
241-
// ignore error here, if we failed to accept a connection,
242-
// conn is nil and we fallback to previous behavior
243-
conn, _ = listener.AcceptUnix()
244-
}
245-
}()
228+
envs = append(envs, socketenv)
246229
}
247230

231+
plugincmd.Env = append(envs, plugincmd.Env...)
232+
248233
const exitLimit = 3
249234

250235
signals := make(chan os.Signal, exitLimit)

0 commit comments

Comments
 (0)