From 3f8d2bbee19c380697d2b1e07d5ef56103ba0b15 Mon Sep 17 00:00:00 2001 From: Ayush Ranjan Date: Tue, 21 Feb 2023 01:08:12 -0800 Subject: [PATCH] Deflake test/syscalls:connect_external_test_native. This native test was flaking on Buildkite because it would timeout. The test itself would pass but I think a cleanup stage would hang indefinitely. Such a hang would only happen when one of the cleaners from uds.go:createPipeReader() would fail with the following log: `Failed to kick pipe reader: open /tmp/sockets930502909/pipe/out: no such device or address`. The connect_external_test does not even use pipes. It is unnecessary setup and cleanup steps. Furthermore, test logs are very noisy from these such setup and cleanup steps for tests that are not even run. Although I am not sure why failing to kick the pipe reader would cause the test to hang, I have split out the setup done in `CreateSocketTree()` into 3 parts: 1. Creating a UDS tree so that sandbox can connect to it. 2. Create connectors outside sandbox who connect to sockets created by sandbox. 3. Creating pipes outside the sandbox that sandbox can read/write to. I also split the test targets into separate tests. These setup steps are only done for their respective tests. Makes the logs more relevant, gets rid of unnecessary steps and failures and deflakes the native connect test. PiperOrigin-RevId: 511128400 --- test/runner/defs.bzl | 40 +++++-- test/runner/main.go | 123 +++++++++++++++++--- test/syscalls/BUILD | 20 +++- test/syscalls/linux/BUILD | 15 +++ test/syscalls/linux/bind_external.cc | 111 ++++++++++++++++++ test/syscalls/linux/connect_external.cc | 57 +-------- test/syscalls/linux/pipe_external.cc | 8 +- test/uds/uds.go | 146 +++++++++++++++--------- 8 files changed, 374 insertions(+), 146 deletions(-) create mode 100644 test/syscalls/linux/bind_external.cc 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) }