Cleanup host UDS support
This change fixes several issues with the fsgofer host UDS support. Notably, it adds support for SOCK_SEQPACKET and SOCK_DGRAM sockets [1]. It also fixes unsafe use of unet.Socket, which could cause a panic if Socket.FD is called when err != nil, and calls to Socket.FD with nothing to prevent the garbage collector from destroying and closing the socket. A set of tests is added to exercise host UDS access. This required extracting most of the syscall test runner into a library that can be used by custom tests. Updates #235 Updates #1003 [1] N.B. SOCK_DGRAM sockets are likely not particularly useful, as a server can only reply to a client that binds first. We don't allow bind, so these are unlikely to be used. PiperOrigin-RevId: 275558502
This commit is contained in:
parent
8ae70f864d
commit
49b596b98d
|
@ -8,9 +8,6 @@ go_library(
|
||||||
srcs = ["fd.go"],
|
srcs = ["fd.go"],
|
||||||
importpath = "gvisor.dev/gvisor/pkg/fd",
|
importpath = "gvisor.dev/gvisor/pkg/fd",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
|
||||||
"//pkg/unet",
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
go_test(
|
go_test(
|
||||||
|
|
|
@ -22,8 +22,6 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"gvisor.dev/gvisor/pkg/unet"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReadWriter implements io.ReadWriter, io.ReaderAt, and io.WriterAt for fd. It
|
// ReadWriter implements io.ReadWriter, io.ReaderAt, and io.WriterAt for fd. It
|
||||||
|
@ -187,12 +185,6 @@ func OpenAt(dir *FD, path string, flags int, mode uint32) (*FD, error) {
|
||||||
return New(f), nil
|
return New(f), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialUnix connects to a Unix Domain Socket and return the file descriptor.
|
|
||||||
func DialUnix(path string) (*FD, error) {
|
|
||||||
socket, err := unet.Connect(path, false)
|
|
||||||
return New(socket.FD()), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the file descriptor contained in the FD.
|
// Close closes the file descriptor contained in the FD.
|
||||||
//
|
//
|
||||||
// Close is safe to call multiple times, but will return an error after the
|
// Close is safe to call multiple times, but will return an error after the
|
||||||
|
|
|
@ -220,6 +220,18 @@ var udsSyscalls = seccomp.SyscallRules{
|
||||||
syscall.SYS_SOCKET: []seccomp.Rule{
|
syscall.SYS_SOCKET: []seccomp.Rule{
|
||||||
{
|
{
|
||||||
seccomp.AllowValue(syscall.AF_UNIX),
|
seccomp.AllowValue(syscall.AF_UNIX),
|
||||||
|
seccomp.AllowValue(syscall.SOCK_STREAM),
|
||||||
|
seccomp.AllowValue(0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
seccomp.AllowValue(syscall.AF_UNIX),
|
||||||
|
seccomp.AllowValue(syscall.SOCK_DGRAM),
|
||||||
|
seccomp.AllowValue(0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
seccomp.AllowValue(syscall.AF_UNIX),
|
||||||
|
seccomp.AllowValue(syscall.SOCK_SEQPACKET),
|
||||||
|
seccomp.AllowValue(0),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
syscall.SYS_CONNECT: []seccomp.Rule{
|
syscall.SYS_CONNECT: []seccomp.Rule{
|
||||||
|
|
|
@ -265,10 +265,10 @@ func openAnyFileFromParent(parent *localFile, name string) (*fd.FD, string, erro
|
||||||
// actual file open and is customizable by the caller.
|
// actual file open and is customizable by the caller.
|
||||||
func openAnyFile(path string, fn func(mode int) (*fd.FD, error)) (*fd.FD, error) {
|
func openAnyFile(path string, fn func(mode int) (*fd.FD, error)) (*fd.FD, error) {
|
||||||
// Attempt to open file in the following mode in order:
|
// Attempt to open file in the following mode in order:
|
||||||
// 1. RDONLY | NONBLOCK: for all files, works for directories and ro mounts too.
|
// 1. RDONLY | NONBLOCK: for all files, directories, ro mounts, FIFOs.
|
||||||
// Use non-blocking to prevent getting stuck inside open(2) for FIFOs. This option
|
// Use non-blocking to prevent getting stuck inside open(2) for
|
||||||
// has no effect on regular files.
|
// FIFOs. This option has no effect on regular files.
|
||||||
// 2. PATH: for symlinks
|
// 2. PATH: for symlinks, sockets.
|
||||||
modes := []int{syscall.O_RDONLY | syscall.O_NONBLOCK, unix.O_PATH}
|
modes := []int{syscall.O_RDONLY | syscall.O_NONBLOCK, unix.O_PATH}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
@ -1032,12 +1032,48 @@ func (l *localFile) Flush() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect implements p9.File.
|
// Connect implements p9.File.
|
||||||
func (l *localFile) Connect(p9.ConnectFlags) (*fd.FD, error) {
|
func (l *localFile) Connect(flags p9.ConnectFlags) (*fd.FD, error) {
|
||||||
// Check to see if the CLI option has been set to allow the UDS mount.
|
|
||||||
if !l.attachPoint.conf.HostUDS {
|
if !l.attachPoint.conf.HostUDS {
|
||||||
return nil, syscall.ECONNREFUSED
|
return nil, syscall.ECONNREFUSED
|
||||||
}
|
}
|
||||||
return fd.DialUnix(l.hostPath)
|
|
||||||
|
// TODO(gvisor.dev/issue/1003): Due to different app vs replacement
|
||||||
|
// mappings, the app path may have fit in the sockaddr, but we can't
|
||||||
|
// fit f.path in our sockaddr. We'd need to redirect through a shorter
|
||||||
|
// path in order to actually connect to this socket.
|
||||||
|
if len(l.hostPath) > linux.UnixPathMax {
|
||||||
|
return nil, syscall.ECONNREFUSED
|
||||||
|
}
|
||||||
|
|
||||||
|
var stype int
|
||||||
|
switch flags {
|
||||||
|
case p9.StreamSocket:
|
||||||
|
stype = syscall.SOCK_STREAM
|
||||||
|
case p9.DgramSocket:
|
||||||
|
stype = syscall.SOCK_DGRAM
|
||||||
|
case p9.SeqpacketSocket:
|
||||||
|
stype = syscall.SOCK_SEQPACKET
|
||||||
|
default:
|
||||||
|
return nil, syscall.ENXIO
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := syscall.Socket(syscall.AF_UNIX, stype, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := syscall.SetNonblock(f, true); err != nil {
|
||||||
|
syscall.Close(f)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sa := syscall.SockaddrUnix{Name: l.hostPath}
|
||||||
|
if err := syscall.Connect(f, &sa); err != nil {
|
||||||
|
syscall.Close(f)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fd.New(f), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close implements p9.File.
|
// Close implements p9.File.
|
||||||
|
|
|
@ -9,6 +9,7 @@ go_library(
|
||||||
importpath = "gvisor.dev/gvisor/runsc/testutil",
|
importpath = "gvisor.dev/gvisor/runsc/testutil",
|
||||||
visibility = ["//:sandbox"],
|
visibility = ["//:sandbox"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//pkg/log",
|
||||||
"//runsc/boot",
|
"//runsc/boot",
|
||||||
"//runsc/specutils",
|
"//runsc/specutils",
|
||||||
"@com_github_cenkalti_backoff//:go_default_library",
|
"@com_github_cenkalti_backoff//:go_default_library",
|
||||||
|
|
|
@ -25,7 +25,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -42,6 +41,7 @@ import (
|
||||||
|
|
||||||
"github.com/cenkalti/backoff"
|
"github.com/cenkalti/backoff"
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
"gvisor.dev/gvisor/pkg/log"
|
||||||
"gvisor.dev/gvisor/runsc/boot"
|
"gvisor.dev/gvisor/runsc/boot"
|
||||||
"gvisor.dev/gvisor/runsc/specutils"
|
"gvisor.dev/gvisor/runsc/specutils"
|
||||||
)
|
)
|
||||||
|
@ -286,7 +286,7 @@ func WaitForHTTP(port int, timeout time.Duration) error {
|
||||||
url := fmt.Sprintf("http://localhost:%d/", port)
|
url := fmt.Sprintf("http://localhost:%d/", port)
|
||||||
resp, err := c.Get(url)
|
resp, err := c.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Waiting %s: %v", url, err)
|
log.Infof("Waiting %s: %v", url, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
|
|
|
@ -78,6 +78,12 @@ syscall_test(test = "//test/syscalls/linux:clock_nanosleep_test")
|
||||||
|
|
||||||
syscall_test(test = "//test/syscalls/linux:concurrency_test")
|
syscall_test(test = "//test/syscalls/linux:concurrency_test")
|
||||||
|
|
||||||
|
syscall_test(
|
||||||
|
add_uds_tree = True,
|
||||||
|
test = "//test/syscalls/linux:connect_external_test",
|
||||||
|
use_tmpfs = True,
|
||||||
|
)
|
||||||
|
|
||||||
syscall_test(
|
syscall_test(
|
||||||
add_overlay = True,
|
add_overlay = True,
|
||||||
test = "//test/syscalls/linux:creat_test",
|
test = "//test/syscalls/linux:creat_test",
|
||||||
|
@ -716,6 +722,7 @@ go_binary(
|
||||||
"//runsc/specutils",
|
"//runsc/specutils",
|
||||||
"//runsc/testutil",
|
"//runsc/testutil",
|
||||||
"//test/syscalls/gtest",
|
"//test/syscalls/gtest",
|
||||||
|
"//test/uds",
|
||||||
"@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
|
"@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
|
||||||
"@org_golang_x_sys//unix:go_default_library",
|
"@org_golang_x_sys//unix:go_default_library",
|
||||||
],
|
],
|
||||||
|
|
|
@ -8,6 +8,7 @@ def syscall_test(
|
||||||
size = "small",
|
size = "small",
|
||||||
use_tmpfs = False,
|
use_tmpfs = False,
|
||||||
add_overlay = False,
|
add_overlay = False,
|
||||||
|
add_uds_tree = False,
|
||||||
tags = None):
|
tags = None):
|
||||||
_syscall_test(
|
_syscall_test(
|
||||||
test = test,
|
test = test,
|
||||||
|
@ -15,6 +16,7 @@ def syscall_test(
|
||||||
size = size,
|
size = size,
|
||||||
platform = "native",
|
platform = "native",
|
||||||
use_tmpfs = False,
|
use_tmpfs = False,
|
||||||
|
add_uds_tree = add_uds_tree,
|
||||||
tags = tags,
|
tags = tags,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,6 +26,7 @@ def syscall_test(
|
||||||
size = size,
|
size = size,
|
||||||
platform = "kvm",
|
platform = "kvm",
|
||||||
use_tmpfs = use_tmpfs,
|
use_tmpfs = use_tmpfs,
|
||||||
|
add_uds_tree = add_uds_tree,
|
||||||
tags = tags,
|
tags = tags,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -33,6 +36,7 @@ def syscall_test(
|
||||||
size = size,
|
size = size,
|
||||||
platform = "ptrace",
|
platform = "ptrace",
|
||||||
use_tmpfs = use_tmpfs,
|
use_tmpfs = use_tmpfs,
|
||||||
|
add_uds_tree = add_uds_tree,
|
||||||
tags = tags,
|
tags = tags,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,6 +47,7 @@ def syscall_test(
|
||||||
size = size,
|
size = size,
|
||||||
platform = "ptrace",
|
platform = "ptrace",
|
||||||
use_tmpfs = False, # overlay is adding a writable tmpfs on top of root.
|
use_tmpfs = False, # overlay is adding a writable tmpfs on top of root.
|
||||||
|
add_uds_tree = add_uds_tree,
|
||||||
tags = tags,
|
tags = tags,
|
||||||
overlay = True,
|
overlay = True,
|
||||||
)
|
)
|
||||||
|
@ -55,6 +60,7 @@ def syscall_test(
|
||||||
size = size,
|
size = size,
|
||||||
platform = "ptrace",
|
platform = "ptrace",
|
||||||
use_tmpfs = use_tmpfs,
|
use_tmpfs = use_tmpfs,
|
||||||
|
add_uds_tree = add_uds_tree,
|
||||||
tags = tags,
|
tags = tags,
|
||||||
file_access = "shared",
|
file_access = "shared",
|
||||||
)
|
)
|
||||||
|
@ -67,7 +73,8 @@ def _syscall_test(
|
||||||
use_tmpfs,
|
use_tmpfs,
|
||||||
tags,
|
tags,
|
||||||
file_access = "exclusive",
|
file_access = "exclusive",
|
||||||
overlay = False):
|
overlay = False,
|
||||||
|
add_uds_tree = False):
|
||||||
test_name = test.split(":")[1]
|
test_name = test.split(":")[1]
|
||||||
|
|
||||||
# Prepend "runsc" to non-native platform names.
|
# Prepend "runsc" to non-native platform names.
|
||||||
|
@ -103,6 +110,7 @@ def _syscall_test(
|
||||||
"--use-tmpfs=" + str(use_tmpfs),
|
"--use-tmpfs=" + str(use_tmpfs),
|
||||||
"--file-access=" + file_access,
|
"--file-access=" + file_access,
|
||||||
"--overlay=" + str(overlay),
|
"--overlay=" + str(overlay),
|
||||||
|
"--add-uds-tree=" + str(add_uds_tree),
|
||||||
]
|
]
|
||||||
|
|
||||||
sh_test(
|
sh_test(
|
||||||
|
|
|
@ -479,6 +479,21 @@ cc_binary(
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cc_binary(
|
||||||
|
name = "connect_external_test",
|
||||||
|
testonly = 1,
|
||||||
|
srcs = ["connect_external.cc"],
|
||||||
|
linkstatic = 1,
|
||||||
|
deps = [
|
||||||
|
":socket_test_util",
|
||||||
|
"//test/util:file_descriptor",
|
||||||
|
"//test/util:fs_util",
|
||||||
|
"//test/util:test_main",
|
||||||
|
"//test/util:test_util",
|
||||||
|
"@com_google_googletest//:gtest",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
cc_binary(
|
cc_binary(
|
||||||
name = "creat_test",
|
name = "creat_test",
|
||||||
testonly = 1,
|
testonly = 1,
|
||||||
|
|
|
@ -140,6 +140,18 @@ TEST_P(AllSocketPairTest, Connect) {
|
||||||
SyscallSucceeds());
|
SyscallSucceeds());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_P(AllSocketPairTest, ConnectNonListening) {
|
||||||
|
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
|
||||||
|
|
||||||
|
ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
|
||||||
|
sockets->first_addr_size()),
|
||||||
|
SyscallSucceeds());
|
||||||
|
|
||||||
|
ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
|
||||||
|
sockets->first_addr_size()),
|
||||||
|
SyscallFailsWithErrno(ECONNREFUSED));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_P(AllSocketPairTest, ConnectToFilePath) {
|
TEST_P(AllSocketPairTest, ConnectToFilePath) {
|
||||||
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
|
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,164 @@
|
||||||
|
// 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 <errno.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "test/syscalls/linux/socket_test_util.h"
|
||||||
|
#include "test/util/file_descriptor.h"
|
||||||
|
#include "test/util/fs_util.h"
|
||||||
|
#include "test/util/test_util.h"
|
||||||
|
|
||||||
|
// This file contains tests specific to connecting to host UDS managed 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<std::tuple<std::string, ProtocolSocket>>;
|
||||||
|
|
||||||
|
// Connect to a socket and verify that write/read work.
|
||||||
|
//
|
||||||
|
// An "echo" socket doesn't work for dgram sockets because our socket is
|
||||||
|
// unnamed. The server thus has no way to reply to us.
|
||||||
|
TEST_P(GoferStreamSeqpacketTest, Echo) {
|
||||||
|
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, "echo");
|
||||||
|
|
||||||
|
struct sockaddr_un addr = {};
|
||||||
|
addr.sun_family = AF_UNIX;
|
||||||
|
memcpy(addr.sun_path, socket_path.c_str(), socket_path.length());
|
||||||
|
|
||||||
|
ASSERT_THAT(connect(sock.get(), reinterpret_cast<struct sockaddr *>(&addr),
|
||||||
|
sizeof(addr)),
|
||||||
|
SyscallSucceeds());
|
||||||
|
|
||||||
|
constexpr int kBufferSize = 64;
|
||||||
|
char send_buffer[kBufferSize];
|
||||||
|
memset(send_buffer, 'a', sizeof(send_buffer));
|
||||||
|
|
||||||
|
ASSERT_THAT(WriteFd(sock.get(), send_buffer, sizeof(send_buffer)),
|
||||||
|
SyscallSucceedsWithValue(sizeof(send_buffer)));
|
||||||
|
|
||||||
|
char recv_buffer[kBufferSize];
|
||||||
|
ASSERT_THAT(ReadFd(sock.get(), recv_buffer, sizeof(recv_buffer)),
|
||||||
|
SyscallSucceedsWithValue(sizeof(recv_buffer)));
|
||||||
|
ASSERT_EQ(0, memcmp(send_buffer, recv_buffer, sizeof(send_buffer)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is not possible to connect to a bound but non-listening socket.
|
||||||
|
TEST_P(GoferStreamSeqpacketTest, NonListening) {
|
||||||
|
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, "nonlistening");
|
||||||
|
|
||||||
|
struct sockaddr_un addr = {};
|
||||||
|
addr.sun_family = AF_UNIX;
|
||||||
|
memcpy(addr.sun_path, socket_path.c_str(), socket_path.length());
|
||||||
|
|
||||||
|
ASSERT_THAT(connect(sock.get(), reinterpret_cast<struct sockaddr *>(&addr),
|
||||||
|
sizeof(addr)),
|
||||||
|
SyscallFailsWithErrno(ECONNREFUSED));
|
||||||
|
}
|
||||||
|
|
||||||
|
INSTANTIATE_TEST_SUITE_P(
|
||||||
|
StreamSeqpacket, GoferStreamSeqpacketTest,
|
||||||
|
::testing::Combine(
|
||||||
|
// Test access via standard path and attach point.
|
||||||
|
::testing::Values("TEST_UDS_TREE", "TEST_UDS_ATTACH_TREE"),
|
||||||
|
::testing::Values(ProtocolSocket{SOCK_STREAM, "stream"},
|
||||||
|
ProtocolSocket{SOCK_SEQPACKET, "seqpacket"})));
|
||||||
|
|
||||||
|
// Parameter is socket root dir.
|
||||||
|
using GoferDgramTest = ::testing::TestWithParam<std::string>;
|
||||||
|
|
||||||
|
// Connect to a socket and verify that write works.
|
||||||
|
//
|
||||||
|
// An "echo" socket doesn't work for dgram sockets because our socket is
|
||||||
|
// unnamed. The server thus has no way to reply to us.
|
||||||
|
TEST_P(GoferDgramTest, Null) {
|
||||||
|
std::string env = 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, SOCK_DGRAM, 0));
|
||||||
|
|
||||||
|
std::string socket_path = JoinPath(root, "dgram/null");
|
||||||
|
|
||||||
|
struct sockaddr_un addr = {};
|
||||||
|
addr.sun_family = AF_UNIX;
|
||||||
|
memcpy(addr.sun_path, socket_path.c_str(), socket_path.length());
|
||||||
|
|
||||||
|
ASSERT_THAT(connect(sock.get(), reinterpret_cast<struct sockaddr *>(&addr),
|
||||||
|
sizeof(addr)),
|
||||||
|
SyscallSucceeds());
|
||||||
|
|
||||||
|
constexpr int kBufferSize = 64;
|
||||||
|
char send_buffer[kBufferSize];
|
||||||
|
memset(send_buffer, 'a', sizeof(send_buffer));
|
||||||
|
|
||||||
|
ASSERT_THAT(WriteFd(sock.get(), send_buffer, sizeof(send_buffer)),
|
||||||
|
SyscallSucceedsWithValue(sizeof(send_buffer)));
|
||||||
|
}
|
||||||
|
|
||||||
|
INSTANTIATE_TEST_SUITE_P(Dgram, GoferDgramTest,
|
||||||
|
// Test access via standard path and attach point.
|
||||||
|
::testing::Values("TEST_UDS_TREE",
|
||||||
|
"TEST_UDS_ATTACH_TREE"));
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
} // namespace testing
|
||||||
|
} // namespace gvisor
|
|
@ -35,6 +35,7 @@ import (
|
||||||
"gvisor.dev/gvisor/runsc/specutils"
|
"gvisor.dev/gvisor/runsc/specutils"
|
||||||
"gvisor.dev/gvisor/runsc/testutil"
|
"gvisor.dev/gvisor/runsc/testutil"
|
||||||
"gvisor.dev/gvisor/test/syscalls/gtest"
|
"gvisor.dev/gvisor/test/syscalls/gtest"
|
||||||
|
"gvisor.dev/gvisor/test/uds"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Location of syscall tests, relative to the repo root.
|
// Location of syscall tests, relative to the repo root.
|
||||||
|
@ -50,6 +51,8 @@ var (
|
||||||
overlay = flag.Bool("overlay", false, "wrap filesystem mounts with writable tmpfs overlay")
|
overlay = flag.Bool("overlay", false, "wrap filesystem mounts with writable tmpfs overlay")
|
||||||
parallel = flag.Bool("parallel", false, "run tests in parallel")
|
parallel = flag.Bool("parallel", false, "run tests in parallel")
|
||||||
runscPath = flag.String("runsc", "", "path to runsc binary")
|
runscPath = flag.String("runsc", "", "path to runsc binary")
|
||||||
|
|
||||||
|
addUDSTree = flag.Bool("add-uds-tree", false, "expose a tree of UDS utilities for use in tests")
|
||||||
)
|
)
|
||||||
|
|
||||||
// runTestCaseNative runs the test case directly on the host machine.
|
// runTestCaseNative runs the test case directly on the host machine.
|
||||||
|
@ -86,6 +89,19 @@ func runTestCaseNative(testBin string, tc gtest.TestCase, t *testing.T) {
|
||||||
// intepret them.
|
// intepret them.
|
||||||
env = filterEnv(env, []string{"TEST_SHARD_INDEX", "TEST_TOTAL_SHARDS", "GTEST_SHARD_INDEX", "GTEST_TOTAL_SHARDS"})
|
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 err != nil {
|
||||||
|
t.Fatalf("failed to create socket tree: %v", err)
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
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.
|
||||||
|
env = append(env, "TEST_UDS_ATTACH_TREE="+socketDir)
|
||||||
|
}
|
||||||
|
|
||||||
cmd := exec.Command(testBin, gtest.FilterTestFlag+"="+tc.FullName())
|
cmd := exec.Command(testBin, gtest.FilterTestFlag+"="+tc.FullName())
|
||||||
cmd.Env = env
|
cmd.Env = env
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
|
@ -96,14 +112,186 @@ func runTestCaseNative(testBin string, tc gtest.TestCase, t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// runsTestCaseRunsc runs the test case in runsc.
|
// runRunsc runs spec in runsc in a standard test configuration.
|
||||||
func runTestCaseRunsc(testBin string, tc gtest.TestCase, t *testing.T) {
|
//
|
||||||
|
// runsc logs will be saved to a path in TEST_UNDECLARED_OUTPUTS_DIR.
|
||||||
|
//
|
||||||
|
// Returns an error if the sandboxed application exits non-zero.
|
||||||
|
func runRunsc(tc gtest.TestCase, spec *specs.Spec) error {
|
||||||
|
bundleDir, err := testutil.SetupBundleDir(spec)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("SetupBundleDir failed: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(bundleDir)
|
||||||
|
|
||||||
rootDir, err := testutil.SetupRootDir()
|
rootDir, err := testutil.SetupRootDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("SetupRootDir failed: %v", err)
|
return fmt.Errorf("SetupRootDir failed: %v", err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(rootDir)
|
defer os.RemoveAll(rootDir)
|
||||||
|
|
||||||
|
name := tc.FullName()
|
||||||
|
id := testutil.UniqueContainerID()
|
||||||
|
log.Infof("Running test %q in container %q", name, id)
|
||||||
|
specutils.LogSpec(spec)
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-root", rootDir,
|
||||||
|
"-network=none",
|
||||||
|
"-log-format=text",
|
||||||
|
"-TESTONLY-unsafe-nonroot=true",
|
||||||
|
"-net-raw=true",
|
||||||
|
fmt.Sprintf("-panic-signal=%d", syscall.SIGTERM),
|
||||||
|
"-watchdog-action=panic",
|
||||||
|
"-platform", *platform,
|
||||||
|
"-file-access", *fileAccess,
|
||||||
|
}
|
||||||
|
if *overlay {
|
||||||
|
args = append(args, "-overlay")
|
||||||
|
}
|
||||||
|
if *debug {
|
||||||
|
args = append(args, "-debug", "-log-packets=true")
|
||||||
|
}
|
||||||
|
if *strace {
|
||||||
|
args = append(args, "-strace")
|
||||||
|
}
|
||||||
|
if *addUDSTree {
|
||||||
|
args = append(args, "-fsgofer-host-uds")
|
||||||
|
}
|
||||||
|
|
||||||
|
if outDir, ok := syscall.Getenv("TEST_UNDECLARED_OUTPUTS_DIR"); ok {
|
||||||
|
tdir := filepath.Join(outDir, strings.Replace(name, "/", "_", -1))
|
||||||
|
if err := os.MkdirAll(tdir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("could not create test dir: %v", err)
|
||||||
|
}
|
||||||
|
debugLogDir, err := ioutil.TempDir(tdir, "runsc")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create temp dir: %v", err)
|
||||||
|
}
|
||||||
|
debugLogDir += "/"
|
||||||
|
log.Infof("runsc logs: %s", debugLogDir)
|
||||||
|
args = append(args, "-debug-log", debugLogDir)
|
||||||
|
|
||||||
|
// Default -log sends messages to stderr which makes reading the test log
|
||||||
|
// difficult. Instead, drop them when debug log is enabled given it's a
|
||||||
|
// better place for these messages.
|
||||||
|
args = append(args, "-log=/dev/null")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current process doesn't have CAP_SYS_ADMIN, create user namespace and run
|
||||||
|
// as root inside that namespace to get it.
|
||||||
|
rArgs := append(args, "run", "--bundle", bundleDir, id)
|
||||||
|
cmd := exec.Command(*runscPath, rArgs...)
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
Cloneflags: syscall.CLONE_NEWUSER | syscall.CLONE_NEWNS,
|
||||||
|
// Set current user/group as root inside the namespace.
|
||||||
|
UidMappings: []syscall.SysProcIDMap{
|
||||||
|
{ContainerID: 0, HostID: os.Getuid(), Size: 1},
|
||||||
|
},
|
||||||
|
GidMappings: []syscall.SysProcIDMap{
|
||||||
|
{ContainerID: 0, HostID: os.Getgid(), Size: 1},
|
||||||
|
},
|
||||||
|
GidMappingsEnableSetgroups: false,
|
||||||
|
Credential: &syscall.Credential{
|
||||||
|
Uid: 0,
|
||||||
|
Gid: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
sig := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sig, syscall.SIGTERM)
|
||||||
|
go func() {
|
||||||
|
s, ok := <-sig
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Warningf("%s: Got signal: %v", name, s)
|
||||||
|
done := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
dArgs := append(args, "-alsologtostderr=true", "debug", "--stacks", id)
|
||||||
|
cmd := exec.Command(*runscPath, dArgs...)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Run()
|
||||||
|
done <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
timeout := time.After(3 * time.Second)
|
||||||
|
select {
|
||||||
|
case <-timeout:
|
||||||
|
log.Infof("runsc debug --stacks is timeouted")
|
||||||
|
case <-done:
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Warningf("Send SIGTERM to the sandbox process")
|
||||||
|
dArgs := append(args, "debug",
|
||||||
|
fmt.Sprintf("--signal=%d", syscall.SIGTERM),
|
||||||
|
id)
|
||||||
|
cmd = exec.Command(*runscPath, dArgs...)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Run()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = cmd.Run()
|
||||||
|
|
||||||
|
signal.Stop(sig)
|
||||||
|
close(sig)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupUDSTree updates the spec to expose a UDS tree for gofer socket testing.
|
||||||
|
func setupUDSTree(spec *specs.Spec) (cleanup func(), err error) {
|
||||||
|
socketDir, cleanup, err := uds.CreateSocketTree("/tmp")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create socket tree: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard access to entire tree.
|
||||||
|
spec.Mounts = append(spec.Mounts, specs.Mount{
|
||||||
|
Destination: "/tmp/sockets",
|
||||||
|
Source: socketDir,
|
||||||
|
Type: "bind",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Individial attach points for each socket to test mounts that attach
|
||||||
|
// directly to the sockets.
|
||||||
|
spec.Mounts = append(spec.Mounts, specs.Mount{
|
||||||
|
Destination: "/tmp/sockets-attach/stream/echo",
|
||||||
|
Source: filepath.Join(socketDir, "stream/echo"),
|
||||||
|
Type: "bind",
|
||||||
|
})
|
||||||
|
spec.Mounts = append(spec.Mounts, specs.Mount{
|
||||||
|
Destination: "/tmp/sockets-attach/stream/nonlistening",
|
||||||
|
Source: filepath.Join(socketDir, "stream/nonlistening"),
|
||||||
|
Type: "bind",
|
||||||
|
})
|
||||||
|
spec.Mounts = append(spec.Mounts, specs.Mount{
|
||||||
|
Destination: "/tmp/sockets-attach/seqpacket/echo",
|
||||||
|
Source: filepath.Join(socketDir, "seqpacket/echo"),
|
||||||
|
Type: "bind",
|
||||||
|
})
|
||||||
|
spec.Mounts = append(spec.Mounts, specs.Mount{
|
||||||
|
Destination: "/tmp/sockets-attach/seqpacket/nonlistening",
|
||||||
|
Source: filepath.Join(socketDir, "seqpacket/nonlistening"),
|
||||||
|
Type: "bind",
|
||||||
|
})
|
||||||
|
spec.Mounts = append(spec.Mounts, specs.Mount{
|
||||||
|
Destination: "/tmp/sockets-attach/dgram/null",
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// runsTestCaseRunsc runs the test case in runsc.
|
||||||
|
func runTestCaseRunsc(testBin string, tc gtest.TestCase, t *testing.T) {
|
||||||
// Run a new container with the test executable and filter for the
|
// Run a new container with the test executable and filter for the
|
||||||
// given test suite and name.
|
// given test suite and name.
|
||||||
spec := testutil.NewSpecWithArgs(testBin, gtest.FilterTestFlag+"="+tc.FullName())
|
spec := testutil.NewSpecWithArgs(testBin, gtest.FilterTestFlag+"="+tc.FullName())
|
||||||
|
@ -171,115 +359,17 @@ func runTestCaseRunsc(testBin string, tc gtest.TestCase, t *testing.T) {
|
||||||
|
|
||||||
spec.Process.Env = env
|
spec.Process.Env = env
|
||||||
|
|
||||||
bundleDir, err := testutil.SetupBundleDir(spec)
|
if *addUDSTree {
|
||||||
if err != nil {
|
cleanup, err := setupUDSTree(spec)
|
||||||
t.Fatalf("SetupBundleDir failed: %v", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(bundleDir)
|
|
||||||
|
|
||||||
id := testutil.UniqueContainerID()
|
|
||||||
log.Infof("Running test %q in container %q", tc.FullName(), id)
|
|
||||||
specutils.LogSpec(spec)
|
|
||||||
|
|
||||||
args := []string{
|
|
||||||
"-platform", *platform,
|
|
||||||
"-root", rootDir,
|
|
||||||
"-file-access", *fileAccess,
|
|
||||||
"-network=none",
|
|
||||||
"-log-format=text",
|
|
||||||
"-TESTONLY-unsafe-nonroot=true",
|
|
||||||
"-net-raw=true",
|
|
||||||
fmt.Sprintf("-panic-signal=%d", syscall.SIGTERM),
|
|
||||||
"-watchdog-action=panic",
|
|
||||||
}
|
|
||||||
if *overlay {
|
|
||||||
args = append(args, "-overlay")
|
|
||||||
}
|
|
||||||
if *debug {
|
|
||||||
args = append(args, "-debug", "-log-packets=true")
|
|
||||||
}
|
|
||||||
if *strace {
|
|
||||||
args = append(args, "-strace")
|
|
||||||
}
|
|
||||||
if outDir, ok := syscall.Getenv("TEST_UNDECLARED_OUTPUTS_DIR"); ok {
|
|
||||||
tdir := filepath.Join(outDir, strings.Replace(tc.FullName(), "/", "_", -1))
|
|
||||||
if err := os.MkdirAll(tdir, 0755); err != nil {
|
|
||||||
t.Fatalf("could not create test dir: %v", err)
|
|
||||||
}
|
|
||||||
debugLogDir, err := ioutil.TempDir(tdir, "runsc")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not create temp dir: %v", err)
|
t.Fatalf("error creating UDS tree: %v", err)
|
||||||
}
|
}
|
||||||
debugLogDir += "/"
|
defer cleanup()
|
||||||
log.Infof("runsc logs: %s", debugLogDir)
|
|
||||||
args = append(args, "-debug-log", debugLogDir)
|
|
||||||
|
|
||||||
// Default -log sends messages to stderr which makes reading the test log
|
|
||||||
// difficult. Instead, drop them when debug log is enabled given it's a
|
|
||||||
// better place for these messages.
|
|
||||||
args = append(args, "-log=/dev/null")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current process doesn't have CAP_SYS_ADMIN, create user namespace and run
|
if err := runRunsc(tc, spec); err != nil {
|
||||||
// as root inside that namespace to get it.
|
t.Errorf("test %q failed with error %v, want nil", tc.FullName(), err)
|
||||||
rArgs := append(args, "run", "--bundle", bundleDir, id)
|
|
||||||
cmd := exec.Command(*runscPath, rArgs...)
|
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
|
||||||
Cloneflags: syscall.CLONE_NEWUSER | syscall.CLONE_NEWNS,
|
|
||||||
// Set current user/group as root inside the namespace.
|
|
||||||
UidMappings: []syscall.SysProcIDMap{
|
|
||||||
{ContainerID: 0, HostID: os.Getuid(), Size: 1},
|
|
||||||
},
|
|
||||||
GidMappings: []syscall.SysProcIDMap{
|
|
||||||
{ContainerID: 0, HostID: os.Getgid(), Size: 1},
|
|
||||||
},
|
|
||||||
GidMappingsEnableSetgroups: false,
|
|
||||||
Credential: &syscall.Credential{
|
|
||||||
Uid: 0,
|
|
||||||
Gid: 0,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
sig := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sig, syscall.SIGTERM)
|
|
||||||
go func() {
|
|
||||||
s, ok := <-sig
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.Errorf("%s: Got signal: %v", tc.FullName(), s)
|
|
||||||
done := make(chan bool)
|
|
||||||
go func() {
|
|
||||||
dArgs := append(args, "-alsologtostderr=true", "debug", "--stacks", id)
|
|
||||||
cmd := exec.Command(*runscPath, dArgs...)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
cmd.Run()
|
|
||||||
done <- true
|
|
||||||
}()
|
|
||||||
|
|
||||||
timeout := time.Tick(3 * time.Second)
|
|
||||||
select {
|
|
||||||
case <-timeout:
|
|
||||||
t.Logf("runsc debug --stacks is timeouted")
|
|
||||||
case <-done:
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("Send SIGTERM to the sandbox process")
|
|
||||||
dArgs := append(args, "debug",
|
|
||||||
fmt.Sprintf("--signal=%d", syscall.SIGTERM),
|
|
||||||
id)
|
|
||||||
cmd = exec.Command(*runscPath, dArgs...)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
cmd.Run()
|
|
||||||
}()
|
|
||||||
if err = cmd.Run(); err != nil {
|
|
||||||
t.Errorf("test %q exited with status %v, want 0", tc.FullName(), err)
|
|
||||||
}
|
|
||||||
signal.Stop(sig)
|
|
||||||
close(sig)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterEnv returns an environment with the blacklisted variables removed.
|
// filterEnv returns an environment with the blacklisted variables removed.
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
package(
|
||||||
|
default_visibility = ["//:sandbox"],
|
||||||
|
licenses = ["notice"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "uds",
|
||||||
|
testonly = 1,
|
||||||
|
srcs = ["uds.go"],
|
||||||
|
importpath = "gvisor.dev/gvisor/test/uds",
|
||||||
|
deps = [
|
||||||
|
"//pkg/log",
|
||||||
|
"//pkg/unet",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,228 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Package uds contains helpers for testing external UDS functionality.
|
||||||
|
package uds
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"gvisor.dev/gvisor/pkg/log"
|
||||||
|
"gvisor.dev/gvisor/pkg/unet"
|
||||||
|
)
|
||||||
|
|
||||||
|
// createEchoSocket creates a socket that echoes back anything received.
|
||||||
|
//
|
||||||
|
// Only works for stream, seqpacket sockets.
|
||||||
|
func createEchoSocket(path string, protocol int) (cleanup func(), err error) {
|
||||||
|
fd, err := syscall.Socket(syscall.AF_UNIX, protocol, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating echo(%d) socket: %v", protocol, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := syscall.Bind(fd, &syscall.SockaddrUnix{Name: path}); err != nil {
|
||||||
|
return nil, fmt.Errorf("error binding echo(%d) socket: %v", protocol, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := syscall.Listen(fd, 0); err != nil {
|
||||||
|
return nil, fmt.Errorf("error listening echo(%d) socket: %v", protocol, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err := unet.NewServerSocket(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating echo(%d) unet socket: %v", protocol, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptAndEchoOne := func() error {
|
||||||
|
s, err := server.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to accept: %v", err)
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
buf := make([]byte, 512)
|
||||||
|
for {
|
||||||
|
n, err := s.Read(buf)
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read: %d, %v", n, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err = s.Write(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write: %d, %v", n, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
if err := acceptAndEchoOne(); err != nil {
|
||||||
|
log.Warningf("Failed to handle echo(%d) socket: %v", protocol, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
cleanup = func() {
|
||||||
|
if err := server.Close(); err != nil {
|
||||||
|
log.Warningf("Failed to close echo(%d) socket: %v", protocol, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createNonListeningSocket creates a socket that is bound but not listening.
|
||||||
|
//
|
||||||
|
// Only relevant for stream, seqpacket sockets.
|
||||||
|
func createNonListeningSocket(path string, protocol int) (cleanup func(), err error) {
|
||||||
|
fd, err := syscall.Socket(syscall.AF_UNIX, protocol, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating nonlistening(%d) socket: %v", protocol, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := syscall.Bind(fd, &syscall.SockaddrUnix{Name: path}); err != nil {
|
||||||
|
return nil, fmt.Errorf("error binding nonlistening(%d) socket: %v", protocol, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup = func() {
|
||||||
|
if err := syscall.Close(fd); err != nil {
|
||||||
|
log.Warningf("Failed to close nonlistening(%d) socket: %v", protocol, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createNullSocket creates a socket that reads anything received.
|
||||||
|
//
|
||||||
|
// Only works for dgram sockets.
|
||||||
|
func createNullSocket(path string, protocol int) (cleanup func(), err error) {
|
||||||
|
fd, err := syscall.Socket(syscall.AF_UNIX, protocol, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating null(%d) socket: %v", protocol, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := syscall.Bind(fd, &syscall.SockaddrUnix{Name: path}); err != nil {
|
||||||
|
return nil, fmt.Errorf("error binding null(%d) socket: %v", protocol, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := unet.NewSocket(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating null(%d) unet socket: %v", protocol, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
buf := make([]byte, 512)
|
||||||
|
for {
|
||||||
|
n, err := s.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Warningf("failed to read: %d, %v", n, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
cleanup = func() {
|
||||||
|
if err := s.Close(); err != nil {
|
||||||
|
log.Warningf("Failed to close null(%d) socket: %v", protocol, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type socketCreator func(path string, proto int) (cleanup func(), err error)
|
||||||
|
|
||||||
|
// CreateSocketTree creates a local tree of unix domain sockets for use in
|
||||||
|
// testing:
|
||||||
|
// * /stream/echo
|
||||||
|
// * /stream/nonlistening
|
||||||
|
// * /seqpacket/echo
|
||||||
|
// * /seqpacket/nonlistening
|
||||||
|
// * /dgram/null
|
||||||
|
func CreateSocketTree(baseDir string) (dir string, cleanup func(), err error) {
|
||||||
|
dir, err = ioutil.TempDir(baseDir, "sockets")
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, fmt.Errorf("error creating temp dir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var protocols = []struct {
|
||||||
|
protocol int
|
||||||
|
name string
|
||||||
|
sockets map[string]socketCreator
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
protocol: syscall.SOCK_STREAM,
|
||||||
|
name: "stream",
|
||||||
|
sockets: map[string]socketCreator{
|
||||||
|
"echo": createEchoSocket,
|
||||||
|
"nonlistening": createNonListeningSocket,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
protocol: syscall.SOCK_SEQPACKET,
|
||||||
|
name: "seqpacket",
|
||||||
|
sockets: map[string]socketCreator{
|
||||||
|
"echo": createEchoSocket,
|
||||||
|
"nonlistening": createNonListeningSocket,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
protocol: syscall.SOCK_DGRAM,
|
||||||
|
name: "dgram",
|
||||||
|
sockets: map[string]socketCreator{
|
||||||
|
"null": createNullSocket,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var cleanups []func()
|
||||||
|
for _, proto := range protocols {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, fn := range proto.sockets {
|
||||||
|
path := filepath.Join(protoDir, name)
|
||||||
|
cleanup, err := fn(path, proto.protocol)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, fmt.Errorf("error creating %s %s socket: %v", proto.name, name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanups = append(cleanups, cleanup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup = func() {
|
||||||
|
for _, c := range cleanups {
|
||||||
|
c()
|
||||||
|
}
|
||||||
|
|
||||||
|
os.RemoveAll(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir, cleanup, nil
|
||||||
|
}
|
Loading…
Reference in New Issue