diff --git a/test/runner/defs.bzl b/test/runner/defs.bzl index 99f24f43b..1cec565f6 100644 --- a/test/runner/defs.bzl +++ b/test/runner/defs.bzl @@ -68,7 +68,9 @@ def _syscall_test( network = "none", file_access = "exclusive", overlay = False, - add_host_communication = False, + add_host_uds = False, + add_host_connector = False, + add_host_fifo = False, iouring = False, container = None, one_sandbox = True, @@ -137,7 +139,9 @@ def _syscall_test( "--fusefs=" + str(fusefs), "--file-access=" + file_access, "--overlay=" + str(overlay), - "--add-host-communication=" + str(add_host_communication), + "--add-host-uds=" + str(add_host_uds), + "--add-host-connector=" + str(add_host_connector), + "--add-host-fifo=" + str(add_host_fifo), "--strace=" + str(debug), "--debug=" + str(debug), "--container=" + str(container), @@ -169,7 +173,9 @@ def syscall_test( use_tmpfs = False, add_fusefs = False, add_overlay = False, - add_host_communication = False, + add_host_uds = False, + add_host_connector = False, + add_host_fifo = False, add_hostinet = False, one_sandbox = True, iouring = False, @@ -185,7 +191,9 @@ def syscall_test( use_tmpfs: use tmpfs in the defined tests. add_fusefs: add a fusefs test. add_overlay: add an overlay test. - add_host_communication: setup UDS and pipe external communication for tests. + add_host_uds: setup bound UDS on the host. + add_host_connector: setup host threads to connect to bound UDS created by sandbox. + add_host_fifo: setup FIFO files on the host. add_hostinet: add a hostinet test. one_sandbox: runs each unit test in a new sandbox instance. iouring: enable IO_URING support. @@ -203,7 +211,9 @@ def syscall_test( test = test, platform = "native", use_tmpfs = False, - add_host_communication = add_host_communication, + add_host_uds = add_host_uds, + add_host_connector = add_host_connector, + add_host_fifo = add_host_fifo, tags = tags, iouring = iouring, debug = debug, @@ -217,7 +227,9 @@ def syscall_test( test = test, platform = platform, use_tmpfs = use_tmpfs, - add_host_communication = add_host_communication, + add_host_uds = add_host_uds, + add_host_connector = add_host_connector, + add_host_fifo = add_host_fifo, tags = platform_tags + tags, iouring = iouring, debug = debug, @@ -231,7 +243,9 @@ def syscall_test( test = test, platform = default_platform, use_tmpfs = use_tmpfs, - add_host_communication = add_host_communication, + add_host_uds = add_host_uds, + add_host_connector = add_host_connector, + add_host_fifo = add_host_fifo, tags = platforms.get(default_platform, []) + tags, debug = debug, iouring = iouring, @@ -246,7 +260,9 @@ def syscall_test( platform = default_platform, use_tmpfs = use_tmpfs, network = "host", - add_host_communication = add_host_communication, + add_host_uds = add_host_uds, + add_host_connector = add_host_connector, + add_host_fifo = add_host_fifo, tags = platforms.get(default_platform, []) + tags, debug = debug, iouring = iouring, @@ -260,7 +276,9 @@ def syscall_test( test = test, platform = default_platform, use_tmpfs = use_tmpfs, - add_host_communication = add_host_communication, + add_host_uds = add_host_uds, + add_host_connector = add_host_connector, + add_host_fifo = add_host_fifo, tags = platforms.get(default_platform, []) + tags, iouring = iouring, debug = debug, @@ -275,7 +293,9 @@ def syscall_test( platform = default_platform, use_tmpfs = True, fusefs = True, - add_host_communication = add_host_communication, + add_host_uds = add_host_uds, + add_host_connector = add_host_connector, + add_host_fifo = add_host_fifo, tags = platforms.get(default_platform, []) + tags, debug = debug, container = container, diff --git a/test/runner/main.go b/test/runner/main.go index 4b0114432..a9ae64688 100644 --- a/test/runner/main.go +++ b/test/runner/main.go @@ -56,8 +56,10 @@ var ( setupContainerPath = flag.String("setup-container", "", "path to setup_container binary (for use with --container)") trace = flag.Bool("trace", false, "enables all trace points") - addUDSTree = flag.Bool("add-host-communication", false, "expose a tree of UDS and pipe utilities to test communication with the host") - ioUring = flag.Bool("iouring", false, "Enables IO_URING API for asynchronous I/O") + addHostUDS = flag.Bool("add-host-uds", false, "expose a tree of UDS to test communication with the host") + addHostConnector = flag.Bool("add-host-connector", false, "create goroutines that connect to bound UDS that will be created by sandbox") + addHostFIFO = flag.Bool("add-host-fifo", false, "expose a tree of FIFO to test communication with the host") + ioUring = flag.Bool("iouring", false, "Enables IO_URING API for asynchronous I/O") // TODO(gvisor.dev/issue/4572): properly support leak checking for runsc, and // set to true as the default for the test runner. leakCheck = flag.Bool("leak-check", false, "check for reference leaks") @@ -109,8 +111,8 @@ func runTestCaseNative(testBin string, tc *gtest.TestCase, args []string, t *tes // interpret them. env = filterEnv(env, []string{"TEST_SHARD_INDEX", "TEST_TOTAL_SHARDS", "GTEST_SHARD_INDEX", "GTEST_TOTAL_SHARDS"}) - if *addUDSTree { - socketDir, cleanup, err := uds.CreateSocketTree("/tmp") + if *addHostUDS { + socketDir, cleanup, err := uds.CreateBoundUDSTree("/tmp") if err != nil { t.Fatalf("failed to create socket tree: %v", err) } @@ -118,10 +120,33 @@ func runTestCaseNative(testBin string, tc *gtest.TestCase, args []string, t *tes env = append(env, "TEST_UDS_TREE="+socketDir) // On Linux, the concept of "attach" location doesn't exist. - // Just pass the same path to make these test identical. + // Just pass the same path to make these tests identical. env = append(env, "TEST_UDS_ATTACH_TREE="+socketDir) } + if *addHostConnector { + connectorDir, cleanup, err := uds.CreateSocketConnectors("/tmp") + if err != nil { + t.Fatalf("failed to create socket connectors: %v", err) + } + defer cleanup() + + env = append(env, "TEST_CONNECTOR_TREE="+connectorDir) + } + + if *addHostFIFO { + pipeDir, cleanup, err := uds.CreateFifoTree("/tmp") + if err != nil { + t.Fatalf("failed to create pipe tree: %v", err) + } + defer cleanup() + + env = append(env, "TEST_FIFO_TREE="+pipeDir) + // On Linux, the concept of "attach" location doesn't exist. + // Just pass the same path to make these tests identical. + env = append(env, "TEST_FIFO_ATTACH_TREE="+pipeDir) + } + if *platformSupport != "" { env = append(env, fmt.Sprintf("%s=%s", platformSupportEnvVar, *platformSupport)) } @@ -214,8 +239,14 @@ func runRunsc(tc *gtest.TestCase, spec *specs.Spec) error { if *strace { args = append(args, "-strace") } - if *addUDSTree { - args = append(args, "-host-uds=all", "-host-fifo=open") + if *addHostUDS { + args = append(args, "-host-uds=open") + } + if *addHostConnector { + args = append(args, "-host-uds=create") + } + if *addHostFIFO { + args = append(args, "-host-fifo=open") } if *leakCheck { args = append(args, "-ref-leak-mode=log-names") @@ -325,10 +356,10 @@ func runRunsc(tc *gtest.TestCase, spec *specs.Spec) error { return err } -// setupHostCommTree updates the spec to expose a UDS and pipe files tree for -// testing communication with the host. -func setupHostCommTree(spec *specs.Spec) (cleanup func(), err error) { - socketDir, cleanup, err := uds.CreateSocketTree("/tmp") +// setupHostUDSTree updates the spec to expose a UDS files tree for testing +// communication with the host. +func setupHostUDSTree(spec *specs.Spec) (cleanup func(), err error) { + socketDir, cleanup, err := uds.CreateBoundUDSTree("/tmp") if err != nil { return nil, fmt.Errorf("failed to create socket tree: %v", err) } @@ -356,16 +387,60 @@ func setupHostCommTree(spec *specs.Spec) (cleanup func(), err error) { Source: filepath.Join(socketDir, "dgram/null"), Type: "bind", }) + + spec.Process.Env = append(spec.Process.Env, "TEST_UDS_TREE=/tmp/sockets") + spec.Process.Env = append(spec.Process.Env, "TEST_UDS_ATTACH_TREE=/tmp/sockets-attach") + + return cleanup, nil +} + +// setupHostFifoTree starts goroutines that will attempt to connect to sockets +// in a directory that will be bind mounted into the container. +func setupHostConnectorTree(spec *specs.Spec) (cleanup func(), err error) { + connectorDir, cleanup, err := uds.CreateSocketConnectors("/tmp") + if err != nil { + return nil, fmt.Errorf("failed to create connector tree: %v", err) + } + + // Standard access to entire tree. + spec.Mounts = append(spec.Mounts, specs.Mount{ + Destination: "/tmp/connectors", + Source: connectorDir, + Type: "bind", + }) + // We can not create individual attach points for sockets that have not been + // created yet. + spec.Process.Env = append(spec.Process.Env, "TEST_CONNECTOR_TREE=/tmp/connectors") + return cleanup, nil +} + +// setupHostFifoTree updates the spec to expose FIFO file tree for testing +// communication with the host. +func setupHostFifoTree(spec *specs.Spec) (cleanup func(), err error) { + fifoDir, cleanup, err := uds.CreateFifoTree("/tmp") + if err != nil { + return nil, fmt.Errorf("failed to create FIFO tree: %v", err) + } + + // Standard access to entire tree. + spec.Mounts = append(spec.Mounts, specs.Mount{ + Destination: "/tmp/pipes", + Source: fifoDir, + Type: "bind", + }) + + // Individual attach points for each pipe to test mounts that attach + // directly to the pipe. for _, name := range []string{"in", "out"} { spec.Mounts = append(spec.Mounts, specs.Mount{ - Destination: filepath.Join("/tmp/sockets-attach/pipe", name), - Source: filepath.Join(socketDir, "pipe", name), + Destination: filepath.Join("/tmp/pipes-attach", name), + Source: filepath.Join(fifoDir, name), Type: "bind", }) } - spec.Process.Env = append(spec.Process.Env, "TEST_UDS_TREE=/tmp/sockets") - spec.Process.Env = append(spec.Process.Env, "TEST_UDS_ATTACH_TREE=/tmp/sockets-attach") + spec.Process.Env = append(spec.Process.Env, "TEST_FIFO_TREE=/tmp/pipes") + spec.Process.Env = append(spec.Process.Env, "TEST_FIFO_ATTACH_TREE=/tmp/pipes-attach") return cleanup, nil } @@ -476,13 +551,27 @@ func runTestCaseRunsc(testBin string, tc *gtest.TestCase, args []string, t *test spec.Process.Env = env - if *addUDSTree { - cleanup, err := setupHostCommTree(spec) + if *addHostUDS { + cleanup, err := setupHostUDSTree(spec) if err != nil { t.Fatalf("error creating UDS tree: %v", err) } defer cleanup() } + if *addHostConnector { + cleanup, err := setupHostConnectorTree(spec) + if err != nil { + t.Fatalf("error creating connector tree: %v", err) + } + defer cleanup() + } + if *addHostFIFO { + cleanup, err := setupHostFifoTree(spec) + if err != nil { + t.Fatalf("error creating FIFO tree: %v", err) + } + defer cleanup() + } if err := runRunsc(tc, spec); err != nil { t.Errorf("test %q failed with error %v, want nil", tc.FullName(), err) diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index f25cd2f44..78eb83631 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -117,21 +117,31 @@ syscall_test( ) syscall_test( - add_host_communication = True, + add_host_connector = True, add_hostinet = True, one_sandbox = False, - test = "//test/syscalls/linux:connect_external_test", + test = "//test/syscalls/linux:bind_external_test", # Shared mode tests replace /tmp which hides the files created for - # add_host_communication. use_tmpfs makes shared mode be skipped. + # add_host_connector. use_tmpfs makes shared mode be skipped. use_tmpfs = True, ) syscall_test( - add_host_communication = True, + add_host_uds = True, + add_hostinet = True, + one_sandbox = False, + test = "//test/syscalls/linux:connect_external_test", + # Shared mode tests replace /tmp which hides the files created for + # add_host_uds. use_tmpfs makes shared mode be skipped. + use_tmpfs = True, +) + +syscall_test( + add_host_fifo = True, one_sandbox = False, test = "//test/syscalls/linux:pipe_external_test", # Shared mode tests replace /tmp which hides the files created for - # add_host_communication. use_tmpfs makes shared mode be skipped. + # add_host_fifo. use_tmpfs makes shared mode be skipped. use_tmpfs = True, ) diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 2307fc6ec..84007ee7e 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -536,6 +536,21 @@ cc_binary( ], ) +cc_binary( + name = "bind_external_test", + testonly = 1, + srcs = ["bind_external.cc"], + linkstatic = 1, + deps = [ + "//test/util:file_descriptor", + "//test/util:fs_util", + "//test/util:socket_util", + gtest, + "//test/util:test_main", + "//test/util:test_util", + ], +) + cc_binary( name = "connect_external_test", testonly = 1, diff --git a/test/syscalls/linux/bind_external.cc b/test/syscalls/linux/bind_external.cc new file mode 100644 index 000000000..8e86c8002 --- /dev/null +++ b/test/syscalls/linux/bind_external.cc @@ -0,0 +1,111 @@ +// Copyright 2019 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "test/util/file_descriptor.h" +#include "test/util/fs_util.h" +#include "test/util/socket_util.h" +#include "test/util/test_util.h" + +// This file contains tests specific to binding to host UDS that will be +// connected to from outside the sandbox / test. +// +// A set of ultity sockets will be created externally in $TEST_UDS_TREE and +// $TEST_UDS_ATTACH_TREE for these tests to interact with. + +namespace gvisor { +namespace testing { + +namespace { + +struct ProtocolSocket { + int protocol; + std::string name; +}; + +// Parameter is (socket root dir, ProtocolSocket). +using GoferStreamSeqpacketTest = + ::testing::TestWithParam>; + +// Bind to a socket, then Listen and Accept. +TEST_P(GoferStreamSeqpacketTest, BindListenAccept) { + std::string env; + ProtocolSocket proto; + std::tie(env, proto) = GetParam(); + + char* val = getenv(env.c_str()); + ASSERT_NE(val, nullptr); + std::string root(val); + + FileDescriptor sock = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, proto.protocol, 0)); + + std::string socket_path = JoinPath(root, proto.name, "created-in-sandbox"); + + struct sockaddr_un addr = {}; + addr.sun_family = AF_UNIX; + memcpy(addr.sun_path, socket_path.c_str(), socket_path.length()); + + ASSERT_THAT( + bind(sock.get(), reinterpret_cast(&addr), sizeof(addr)), + SyscallSucceeds()); + ASSERT_THAT(listen(sock.get(), 1), SyscallSucceeds()); + + // Bind again on that socket with a diff address should fail. + std::string socket_path2 = socket_path + "-fail"; + struct sockaddr_un addr2 = {}; + addr2.sun_family = AF_UNIX; + memcpy(addr2.sun_path, socket_path2.c_str(), socket_path2.length()); + ASSERT_THAT(bind(sock.get(), reinterpret_cast(&addr2), + sizeof(addr2)), + SyscallFailsWithErrno(EINVAL)); + + FileDescriptor accSock = + ASSERT_NO_ERRNO_AND_VALUE(Accept(sock.get(), NULL, NULL)); + + // Other socket should be echo server. + constexpr int kBufferSize = 64; + char send_buffer[kBufferSize]; + memset(send_buffer, 'a', sizeof(send_buffer)); + + ASSERT_THAT(WriteFd(accSock.get(), send_buffer, sizeof(send_buffer)), + SyscallSucceedsWithValue(sizeof(send_buffer))); + + char recv_buffer[kBufferSize]; + ASSERT_THAT(ReadFd(accSock.get(), recv_buffer, sizeof(recv_buffer)), + SyscallSucceedsWithValue(sizeof(recv_buffer))); + ASSERT_EQ(0, memcmp(send_buffer, recv_buffer, sizeof(send_buffer))); +} + +INSTANTIATE_TEST_SUITE_P( + StreamSeqpacket, GoferStreamSeqpacketTest, + ::testing::Combine(::testing::Values("TEST_CONNECTOR_TREE"), + ::testing::Values(ProtocolSocket{SOCK_STREAM, "stream"}, + ProtocolSocket{SOCK_SEQPACKET, + "seqpacket"}))); + +} // namespace + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/connect_external.cc b/test/syscalls/linux/connect_external.cc index 4e44de0c9..9636e9a9a 100644 --- a/test/syscalls/linux/connect_external.cc +++ b/test/syscalls/linux/connect_external.cc @@ -12,17 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include #include #include #include -#include #include #include #include #include +#include "gmock/gmock.h" #include "gtest/gtest.h" #include "test/util/file_descriptor.h" #include "test/util/fs_util.h" @@ -112,60 +111,6 @@ TEST_P(GoferStreamSeqpacketTest, NonListening) { SyscallFailsWithErrno(ECONNREFUSED)); } -// Bind to a socket, then Listen and Accept. -TEST_P(GoferStreamSeqpacketTest, BindListenAccept) { - std::string env; - ProtocolSocket proto; - std::tie(env, proto) = GetParam(); - - // Do not parametrize this test with attach tree variant. This test creates a - // new UDS via bind(2). It is not possible to bind mount a non-existing file. - SKIP_IF(!strcmp("TEST_UDS_ATTACH_TREE", env.c_str())); - - char* val = getenv(env.c_str()); - ASSERT_NE(val, nullptr); - std::string root(val); - - FileDescriptor sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, proto.protocol, 0)); - - std::string socket_path = JoinPath(root, proto.name, "created-in-sandbox"); - - struct sockaddr_un addr = {}; - addr.sun_family = AF_UNIX; - memcpy(addr.sun_path, socket_path.c_str(), socket_path.length()); - - ASSERT_THAT( - bind(sock.get(), reinterpret_cast(&addr), sizeof(addr)), - SyscallSucceeds()); - ASSERT_THAT(listen(sock.get(), 1), SyscallSucceeds()); - - // Bind again on that socket with a diff address should fail. - std::string socket_path2 = socket_path + "-fail"; - struct sockaddr_un addr2 = {}; - addr2.sun_family = AF_UNIX; - memcpy(addr2.sun_path, socket_path2.c_str(), socket_path2.length()); - ASSERT_THAT(bind(sock.get(), reinterpret_cast(&addr2), - sizeof(addr2)), - SyscallFailsWithErrno(EINVAL)); - - FileDescriptor accSock = - ASSERT_NO_ERRNO_AND_VALUE(Accept(sock.get(), NULL, NULL)); - - // Other socket should be echo server. - constexpr int kBufferSize = 64; - char send_buffer[kBufferSize]; - memset(send_buffer, 'a', sizeof(send_buffer)); - - ASSERT_THAT(WriteFd(accSock.get(), send_buffer, sizeof(send_buffer)), - SyscallSucceedsWithValue(sizeof(send_buffer))); - - char recv_buffer[kBufferSize]; - ASSERT_THAT(ReadFd(accSock.get(), recv_buffer, sizeof(recv_buffer)), - SyscallSucceedsWithValue(sizeof(recv_buffer))); - ASSERT_EQ(0, memcmp(send_buffer, recv_buffer, sizeof(send_buffer))); -} - INSTANTIATE_TEST_SUITE_P( StreamSeqpacket, GoferStreamSeqpacketTest, ::testing::Combine( diff --git a/test/syscalls/linux/pipe_external.cc b/test/syscalls/linux/pipe_external.cc index ef77f9bb8..4ce85c3cc 100644 --- a/test/syscalls/linux/pipe_external.cc +++ b/test/syscalls/linux/pipe_external.cc @@ -53,7 +53,7 @@ TEST_P(HostPipeTest, Read) { ASSERT_NE(val, nullptr); const std::string root(val); - const std::string path = JoinPath(root, "pipe", "in"); + const std::string path = JoinPath(root, "in"); FileDescriptor reader = ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_RDONLY)); char lastValue = 0; @@ -78,7 +78,7 @@ TEST_P(HostPipeTest, Write) { ASSERT_NE(val, nullptr); const std::string root(val); - const std::string path = JoinPath(root, "pipe", "out"); + const std::string path = JoinPath(root, "out"); FileDescriptor writer = ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_WRONLY)); char lastValue = 0; @@ -98,8 +98,8 @@ TEST_P(HostPipeTest, Write) { INSTANTIATE_TEST_SUITE_P(Paths, HostPipeTest, // Test access via standard path and attach point. - ::testing::Values("TEST_UDS_TREE", - "TEST_UDS_ATTACH_TREE")); + ::testing::Values("TEST_FIFO_TREE", + "TEST_FIFO_ATTACH_TREE")); } // namespace diff --git a/test/uds/uds.go b/test/uds/uds.go index d860c199a..941fcfcf9 100644 --- a/test/uds/uds.go +++ b/test/uds/uds.go @@ -267,12 +267,12 @@ func createPipeReader(path string) (func(), error) { cleanup := func() { // Kick the goroutine in case it's blocked waiting for a reader. - if kicker, err := os.OpenFile(path, os.O_WRONLY|unix.O_NONBLOCK, 0); err != nil { + kicker, err := os.OpenFile(path, os.O_WRONLY|unix.O_NONBLOCK, 0) + if err != nil { log.Warningf("Failed to kick pipe reader: %v", err) return - } else { - _ = kicker.Close() } + _ = kicker.Close() reader := <-readerCh if reader != nil { @@ -285,23 +285,15 @@ func createPipeReader(path string) (func(), error) { } type socketCreator func(path string, proto int) (cleanup func(), err error) -type pipeCreator func(path string) (cleanup func(), err error) +type socketCreatorSpec struct { + protocol int + name string + sockets map[string]socketCreator +} -// CreateSocketTree creates a local tree of unix domain sockets and pipes for -// use in testing: -// - /stream/echo -// - /stream/nonlistening -// - /seqpacket/echo -// - /seqpacket/nonlistening -// - /dgram/null -// - /pipe/in -// - /pipe/out -// -// Additionally, it will attempt to connect to sockets at the following -// locations, and turn into an echo server once connected: -// - /stream/created-in-sandbox -// - /seqpacket/created-in-sandbox -func CreateSocketTree(baseDir string) (string, func(), error) { +// createSocketTree creates a local tree of unix domain sockets for use in +// testing as per specs. +func createSocketTree(baseDir string, specs []socketCreatorSpec) (string, func(), error) { dir, err := ioutil.TempDir(baseDir, "sockets") if err != nil { return "", nil, fmt.Errorf("error creating temp dir: %v", err) @@ -311,37 +303,7 @@ func CreateSocketTree(baseDir string) (string, func(), error) { }) defer cu.Clean() - for _, proto := range []struct { - protocol int - name string - sockets map[string]socketCreator - }{ - { - protocol: unix.SOCK_STREAM, - name: "stream", - sockets: map[string]socketCreator{ - "echo": createEchoSocket, - "nonlistening": createNonListeningSocket, - "created-in-sandbox": connectAndBecomeEcho, - }, - }, - { - protocol: unix.SOCK_SEQPACKET, - name: "seqpacket", - sockets: map[string]socketCreator{ - "echo": createEchoSocket, - "nonlistening": createNonListeningSocket, - "created-in-sandbox": connectAndBecomeEcho, - }, - }, - { - protocol: unix.SOCK_DGRAM, - name: "dgram", - sockets: map[string]socketCreator{ - "null": createNullSocket, - }, - }, - } { + for _, proto := range specs { protoDir := filepath.Join(dir, proto.name) if err := os.Mkdir(protoDir, 0755); err != nil { return "", nil, fmt.Errorf("error creating %s dir: %v", proto.name, err) @@ -357,10 +319,86 @@ func CreateSocketTree(baseDir string) (string, func(), error) { } } - pipeDir := filepath.Join(dir, "pipe") - if err := os.Mkdir(pipeDir, 0755); err != nil { - return "", nil, err + return dir, cu.Release(), nil +} + +// CreateBoundUDSTree creates a local tree of bound unix domain sockets that +// are ready to accept connections. +// +// These are created at locations: +// - /stream/echo +// - /stream/nonlistening +// - /seqpacket/echo +// - /seqpacket/nonlistening +// - /dgram/null +func CreateBoundUDSTree(baseDir string) (string, func(), error) { + return createSocketTree(baseDir, []socketCreatorSpec{ + { + protocol: unix.SOCK_STREAM, + name: "stream", + sockets: map[string]socketCreator{ + "echo": createEchoSocket, + "nonlistening": createNonListeningSocket, + }, + }, + { + protocol: unix.SOCK_SEQPACKET, + name: "seqpacket", + sockets: map[string]socketCreator{ + "echo": createEchoSocket, + "nonlistening": createNonListeningSocket, + }, + }, + { + protocol: unix.SOCK_DGRAM, + name: "dgram", + sockets: map[string]socketCreator{ + "null": createNullSocket, + }, + }, + }) +} + +// CreateSocketConnectors creates goroutines that will attempt to connect to +// sockets at the following locations, and turn into an echo server once +// connected: +// - /stream/created-in-sandbox +// - /seqpacket/created-in-sandbox +func CreateSocketConnectors(baseDir string) (string, func(), error) { + return createSocketTree(baseDir, []socketCreatorSpec{ + { + protocol: unix.SOCK_STREAM, + name: "stream", + sockets: map[string]socketCreator{ + "created-in-sandbox": connectAndBecomeEcho, + }, + }, + { + protocol: unix.SOCK_SEQPACKET, + name: "seqpacket", + sockets: map[string]socketCreator{ + "created-in-sandbox": connectAndBecomeEcho, + }, + }, + }) +} + +type pipeCreator func(path string) (cleanup func(), err error) + +// CreateFifoTree creates a local tree of fifo files for use in testing: +// +// - /in +// - /out +func CreateFifoTree(baseDir string) (string, func(), error) { + dir, err := ioutil.TempDir(baseDir, "pipes") + if err != nil { + return "", nil, fmt.Errorf("error creating temp dir: %v", err) } + cu := cleanup.Make(func() { + _ = os.RemoveAll(dir) + }) + defer cu.Clean() + for _, pipe := range []struct { name string ctor pipeCreator @@ -372,7 +410,7 @@ func CreateSocketTree(baseDir string) (string, func(), error) { name: "out", ctor: createPipeReader, }, } { - cleanup, err := pipe.ctor(filepath.Join(pipeDir, pipe.name)) + cleanup, err := pipe.ctor(filepath.Join(dir, pipe.name)) if err != nil { return "", nil, fmt.Errorf("error creating %q pipe: %w", pipe.name, err) }