diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/e2e/integration_test.go | 53 | ||||
-rw-r--r-- | test/packetimpact/tests/tcp_rack_test.go | 37 | ||||
-rw-r--r-- | test/packetimpact/tests/udp_send_recv_dgram_test.go | 1 | ||||
-rw-r--r-- | test/syscalls/BUILD | 4 | ||||
-rw-r--r-- | test/syscalls/linux/BUILD | 15 | ||||
-rw-r--r-- | test/syscalls/linux/exec_binary.cc | 28 | ||||
-rw-r--r-- | test/syscalls/linux/open_create.cc | 4 | ||||
-rw-r--r-- | test/syscalls/linux/processes.cc | 90 | ||||
-rw-r--r-- | test/syscalls/linux/rename.cc | 33 | ||||
-rw-r--r-- | test/syscalls/linux/sendfile.cc | 23 | ||||
-rw-r--r-- | test/syscalls/linux/socket_generic.cc | 2 | ||||
-rw-r--r-- | test/syscalls/linux/socket_inet_loopback.cc | 6 | ||||
-rw-r--r-- | test/syscalls/linux/socket_unix_dgram.cc | 35 | ||||
-rw-r--r-- | test/syscalls/linux/socket_unix_seqpacket.cc | 35 | ||||
-rw-r--r-- | test/syscalls/linux/socket_unix_stream.cc | 80 | ||||
-rw-r--r-- | test/syscalls/linux/udp_socket.cc | 81 |
16 files changed, 498 insertions, 29 deletions
diff --git a/test/e2e/integration_test.go b/test/e2e/integration_test.go index aaffabfd0..49cd74887 100644 --- a/test/e2e/integration_test.go +++ b/test/e2e/integration_test.go @@ -430,11 +430,44 @@ func TestTmpMount(t *testing.T) { } } +// TestSyntheticDirs checks that submounts can be created inside a readonly +// mount even if the target path does not exist. +func TestSyntheticDirs(t *testing.T) { + ctx := context.Background() + d := dockerutil.MakeContainer(ctx, t) + defer d.CleanUp(ctx) + + opts := dockerutil.RunOpts{ + Image: "basic/alpine", + // Make the root read-only to force use of synthetic dirs + // inside the root gofer mount. + ReadOnly: true, + Mounts: []mount.Mount{ + // Mount inside read-only gofer-backed root. + { + Type: mount.TypeTmpfs, + Target: "/foo/bar/baz", + }, + // Mount inside sysfs, which always uses synthetic dirs + // for submounts. + { + Type: mount.TypeTmpfs, + Target: "/sys/foo/bar/baz", + }, + }, + } + // Make sure the directories exist. + if _, err := d.Run(ctx, opts, "ls", "/foo/bar/baz", "/sys/foo/bar/baz"); err != nil { + t.Fatalf("docker run failed: %v", err) + } + +} + // TestHostOverlayfsCopyUp tests that the --overlayfs-stale-read option causes // runsc to hide the incoherence of FDs opened before and after overlayfs // copy-up on the host. func TestHostOverlayfsCopyUp(t *testing.T) { - runIntegrationTest(t, nil, "sh", "-c", "gcc -O2 -o test_copy_up test_copy_up.c && ./test_copy_up") + runIntegrationTest(t, nil, "./test_copy_up") } // TestHostOverlayfsRewindDir tests that rewinddir() "causes the directory @@ -449,14 +482,14 @@ func TestHostOverlayfsCopyUp(t *testing.T) { // automated tests yield newly-added files from readdir() even if the fsgofer // does not explicitly rewinddir(), but overlayfs does not. func TestHostOverlayfsRewindDir(t *testing.T) { - runIntegrationTest(t, nil, "sh", "-c", "gcc -O2 -o test_rewinddir test_rewinddir.c && ./test_rewinddir") + runIntegrationTest(t, nil, "./test_rewinddir") } // Basic test for linkat(2). Syscall tests requires CAP_DAC_READ_SEARCH and it // cannot use tricks like userns as root. For this reason, run a basic link test // to ensure some coverage. func TestLink(t *testing.T) { - runIntegrationTest(t, nil, "sh", "-c", "gcc -O2 -o link_test link_test.c && ./link_test") + runIntegrationTest(t, nil, "./link_test") } // This test ensures we can run ping without errors. @@ -487,6 +520,20 @@ func TestPing6Loopback(t *testing.T) { runIntegrationTest(t, []string{"NET_ADMIN"}, "./ping6.sh") } +// This test checks that the owner of the sticky directory can delete files +// inside it belonging to other users. It also checks that the owner of a file +// can always delete its file when the file is inside a sticky directory owned +// by another user. +func TestStickyDir(t *testing.T) { + if vfs2Used, err := dockerutil.UsingVFS2(); err != nil { + t.Fatalf("failed to read config for runtime %s: %v", dockerutil.Runtime(), err) + } else if !vfs2Used { + t.Skip("sticky bit test fails on VFS1.") + } + + runIntegrationTest(t, nil, "./test_sticky") +} + func runIntegrationTest(t *testing.T, capAdd []string, args ...string) { ctx := context.Background() d := dockerutil.MakeContainer(ctx, t) diff --git a/test/packetimpact/tests/tcp_rack_test.go b/test/packetimpact/tests/tcp_rack_test.go index fb2a4cc90..ef902c54d 100644 --- a/test/packetimpact/tests/tcp_rack_test.go +++ b/test/packetimpact/tests/tcp_rack_test.go @@ -168,9 +168,10 @@ func TestRACKTLPLost(t *testing.T) { closeSACKConnection(t, dut, conn, acceptFd, listenFd) } -// TestRACKTLPWithSACK tests TLP by acknowledging out of order packets. +// TestRACKWithSACK tests that RACK marks the packets as lost after receiving +// the ACK for retransmitted packets. // See: https://tools.ietf.org/html/draft-ietf-tcpm-rack-08#section-8.1 -func TestRACKTLPWithSACK(t *testing.T) { +func TestRACKWithSACK(t *testing.T) { dut, conn, acceptFd, listenFd := createSACKConnection(t) seqNum1 := *conn.RemoteSeqNum(t) @@ -180,8 +181,9 @@ func TestRACKTLPWithSACK(t *testing.T) { // We are not sending ACK for these packets. const numPkts = 3 - lastSent := sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */) + sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */) + time.Sleep(simulatedRTT) // SACK for #2 packet. sackBlock := make([]byte, 40) start := seqNum1.Add(seqnum.Size(payloadSize)) @@ -194,32 +196,25 @@ func TestRACKTLPWithSACK(t *testing.T) { }}, sackBlock[sbOff:]) conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) - // RACK marks #1 packet as lost and retransmits it. - if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, time.Second); err != nil { + rtt, _ := getRTTAndRTO(t, dut, acceptFd) + timeout := 2 * rtt + // RACK marks #1 packet as lost after RTT+reorderWindow(RTT/4) and + // retransmits it. + if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, timeout); err != nil { t.Fatalf("expected payload was not received: %s", err) } + time.Sleep(simulatedRTT) // ACK for #1 packet. conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(end))}) - // Probe Timeout (PTO) should be two times RTT. TLP will trigger for #3 - // packet. RACK adds an additional timeout of 200ms if the number of - // outstanding packets is equal to 1. - rtt, rto := getRTTAndRTO(t, dut, acceptFd) - pto := rtt*2 + (200 * time.Millisecond) - if rto < pto { - pto = rto - } - // We expect the 3rd packet (the last unacknowledged packet) to be - // retransmitted. - tlpProbe := testbench.Uint32(uint32(seqNum1) + uint32((numPkts-1)*payloadSize)) - if _, err := conn.Expect(t, testbench.TCP{SeqNum: tlpProbe}, time.Second); err != nil { + // RACK considers transmission times of the packets to mark them lost. + // As the 3rd packet was sent before the retransmitted 1st packet, RACK + // marks it as lost and retransmits it.. + expectedSeqNum := testbench.Uint32(uint32(seqNum1) + uint32((numPkts-1)*payloadSize)) + if _, err := conn.Expect(t, testbench.TCP{SeqNum: expectedSeqNum}, timeout); err != nil { t.Fatalf("expected payload was not received: %s", err) } - diff := time.Now().Sub(lastSent) - if diff < pto { - t.Fatalf("expected payload was received before the probe timeout, got: %v, want: %v", diff, pto) - } closeSACKConnection(t, dut, conn, acceptFd, listenFd) } diff --git a/test/packetimpact/tests/udp_send_recv_dgram_test.go b/test/packetimpact/tests/udp_send_recv_dgram_test.go index 6e45cb143..894d156cf 100644 --- a/test/packetimpact/tests/udp_send_recv_dgram_test.go +++ b/test/packetimpact/tests/udp_send_recv_dgram_test.go @@ -32,6 +32,7 @@ import ( func init() { testbench.Initialize(flag.CommandLine) + testbench.RPCTimeout = 500 * time.Millisecond } type udpConn interface { diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index e43f30ba3..d6658898d 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -993,3 +993,7 @@ syscall_test( syscall_test( test = "//test/syscalls/linux:proc_net_udp_test", ) + +syscall_test( + test = "//test/syscalls/linux:processes_test", +) diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 80e2837f8..42fc363a2 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -2346,6 +2346,7 @@ cc_library( deps = [ ":socket_test_util", ":unix_domain_socket_test_util", + "@com_google_absl//absl/time", gtest, "//test/util:test_util", ], @@ -2360,6 +2361,7 @@ cc_library( deps = [ ":socket_test_util", ":unix_domain_socket_test_util", + "@com_google_absl//absl/time", gtest, "//test/util:test_util", ], @@ -2678,6 +2680,7 @@ cc_binary( deps = [ ":socket_test_util", ":unix_domain_socket_test_util", + "@com_google_absl//absl/time", gtest, "//test/util:test_main", "//test/util:test_util", @@ -4160,6 +4163,18 @@ cc_binary( ) cc_binary( + name = "processes_test", + testonly = 1, + srcs = ["processes.cc"], + linkstatic = 1, + deps = [ + "//test/util:capability_util", + "//test/util:test_main", + "//test/util:test_util", + ], +) + +cc_binary( name = "xattr_test", testonly = 1, srcs = [ diff --git a/test/syscalls/linux/exec_binary.cc b/test/syscalls/linux/exec_binary.cc index 3797fd4c8..b0fb120c6 100644 --- a/test/syscalls/linux/exec_binary.cc +++ b/test/syscalls/linux/exec_binary.cc @@ -951,6 +951,34 @@ TEST(ElfTest, PIEOutOfOrderSegments) { EXPECT_EQ(execve_errno, ENOEXEC); } +TEST(ElfTest, PIEOverflow) { + ElfBinary<64> elf = StandardElf(); + + elf.header.e_type = ET_DYN; + + // Choose vaddr of the first segment so that the end address overflows if the + // segment is mapped with a non-zero offset. + elf.phdrs[1].p_vaddr = 0xfffffffffffff000UL - elf.phdrs[1].p_memsz; + + elf.UpdateOffsets(); + + TempPath file = ASSERT_NO_ERRNO_AND_VALUE(CreateElfWith(elf)); + + pid_t child; + int execve_errno; + auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( + ForkAndExec(file.path(), {file.path()}, {}, &child, &execve_errno)); + if (IsRunningOnGvisor()) { + ASSERT_EQ(execve_errno, EINVAL); + } else { + ASSERT_EQ(execve_errno, 0); + int status; + ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), + SyscallSucceedsWithValue(child)); + EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV) << status; + } +} + // Standard dynamically linked binary with an ELF interpreter. TEST(ElfTest, ELFInterpreter) { ElfBinary<64> interpreter = StandardElf(); diff --git a/test/syscalls/linux/open_create.cc b/test/syscalls/linux/open_create.cc index f8fbea79e..46f41de50 100644 --- a/test/syscalls/linux/open_create.cc +++ b/test/syscalls/linux/open_create.cc @@ -46,8 +46,10 @@ TEST(CreateTest, ExistingFile) { TEST(CreateTest, CreateAtFile) { auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto dirfd = ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY, 0666)); - EXPECT_THAT(openat(dirfd.get(), "CreateAtFile", O_RDWR | O_CREAT, 0666), + int fd; + EXPECT_THAT(fd = openat(dirfd.get(), "CreateAtFile", O_RDWR | O_CREAT, 0666), SyscallSucceeds()); + EXPECT_THAT(close(fd), SyscallSucceeds()); } TEST(CreateTest, HonorsUmask_NoRandomSave) { diff --git a/test/syscalls/linux/processes.cc b/test/syscalls/linux/processes.cc new file mode 100644 index 000000000..412582515 --- /dev/null +++ b/test/syscalls/linux/processes.cc @@ -0,0 +1,90 @@ +// Copyright 2021 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 <stdint.h> +#include <sys/syscall.h> +#include <unistd.h> + +#include "test/util/capability_util.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +int testSetPGIDOfZombie(void* arg) { + int p[2]; + + TEST_PCHECK(pipe(p) == 0); + + pid_t pid = fork(); + if (pid == 0) { + pid = fork(); + // Create a second child to repeat one of syzkaller reproducers. + if (pid == 0) { + pid = getpid(); + TEST_PCHECK(setpgid(pid, 0) == 0); + TEST_PCHECK(write(p[1], &pid, sizeof(pid)) == sizeof(pid)); + _exit(0); + } + TEST_PCHECK(pid > 0); + _exit(0); + } + close(p[1]); + TEST_PCHECK(pid > 0); + + // Get PID of the second child. + pid_t cpid; + TEST_PCHECK(read(p[0], &cpid, sizeof(cpid)) == sizeof(cpid)); + + // Wait when both child processes will die. + int c; + TEST_PCHECK(read(p[0], &c, sizeof(c)) == 0); + + // Wait the second child process to collect its zombie. + int status; + TEST_PCHECK(RetryEINTR(waitpid)(cpid, &status, 0) == cpid); + + // Set the child's group. + TEST_PCHECK(setpgid(pid, pid) == 0); + + TEST_PCHECK(RetryEINTR(waitpid)(-pid, &status, 0) == pid); + + TEST_PCHECK(status == 0); + _exit(0); +} + +TEST(Processes, SetPGIDOfZombie) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); + + // Fork a test process in a new PID namespace, because it needs to manipulate + // with reparanted processes. + struct clone_arg { + // Reserve some space for clone() to locate arguments and retcode in this + // place. + char stack[128] __attribute__((aligned(16))); + char stack_ptr[0]; + } ca; + pid_t pid; + ASSERT_THAT(pid = clone(testSetPGIDOfZombie, ca.stack_ptr, + CLONE_NEWPID | SIGCHLD, &ca), + SyscallSucceeds()); + + int status; + ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, 0), + SyscallSucceedsWithValue(pid)); + EXPECT_EQ(status, 0); +} + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/rename.cc b/test/syscalls/linux/rename.cc index 5458f54ad..22c8c19cf 100644 --- a/test/syscalls/linux/rename.cc +++ b/test/syscalls/linux/rename.cc @@ -391,6 +391,39 @@ TEST(RenameTest, FileWithOpenFd) { EXPECT_EQ(absl::string_view(buf, sizeof(buf) - 1), kContents); } +// Tests that calling rename with file path ending with . or .. causes EBUSY. +TEST(RenameTest, PathEndingWithDots) { + TempPath root_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + TempPath dir1 = + ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root_dir.path())); + TempPath dir2 = + ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root_dir.path())); + + // Try to move dir1 into dir2 but mess up the paths. + auto dir1Dot = JoinPath(dir1.path(), "."); + auto dir2Dot = JoinPath(dir2.path(), "."); + auto dir1DotDot = JoinPath(dir1.path(), ".."); + auto dir2DotDot = JoinPath(dir2.path(), ".."); + ASSERT_THAT(rename(dir1.path().c_str(), dir2Dot.c_str()), + SyscallFailsWithErrno(EBUSY)); + ASSERT_THAT(rename(dir1.path().c_str(), dir2DotDot.c_str()), + SyscallFailsWithErrno(EBUSY)); + ASSERT_THAT(rename(dir1Dot.c_str(), dir2.path().c_str()), + SyscallFailsWithErrno(EBUSY)); + ASSERT_THAT(rename(dir1DotDot.c_str(), dir2.path().c_str()), + SyscallFailsWithErrno(EBUSY)); +} + +// Calling rename with file path ending with . or .. causes EBUSY in sysfs. +TEST(RenameTest, SysfsPathEndingWithDots) { + // If a non-root user tries to rename inside /sys then we get EPERM. + SKIP_IF(geteuid() != 0); + ASSERT_THAT(rename("/sys/devices/system/cpu/online", "/sys/."), + SyscallFailsWithErrno(EBUSY)); + ASSERT_THAT(rename("/sys/devices/system/cpu/online", "/sys/.."), + SyscallFailsWithErrno(EBUSY)); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/sendfile.cc b/test/syscalls/linux/sendfile.cc index 3924e0001..93b3a94f1 100644 --- a/test/syscalls/linux/sendfile.cc +++ b/test/syscalls/linux/sendfile.cc @@ -551,6 +551,29 @@ TEST(SendFileTest, SendPipeEOF) { SyscallSucceedsWithValue(0)); } +TEST(SendFileTest, SendToFullPipeReturnsEAGAIN) { + // Create and open an empty input file. + const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor in_fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDWR)); + + // Set up the output pipe. + int fds[2]; + ASSERT_THAT(pipe2(fds, O_NONBLOCK), SyscallSucceeds()); + const FileDescriptor rfd(fds[0]); + const FileDescriptor wfd(fds[1]); + + int pipe_size = -1; + ASSERT_THAT(pipe_size = fcntl(wfd.get(), F_GETPIPE_SZ), SyscallSucceeds()); + int data_size = pipe_size * 8; + ASSERT_THAT(ftruncate(in_fd.get(), data_size), SyscallSucceeds()); + + ASSERT_THAT(sendfile(wfd.get(), in_fd.get(), 0, data_size), + SyscallSucceeds()); + EXPECT_THAT(sendfile(wfd.get(), in_fd.get(), 0, data_size), + SyscallFailsWithErrno(EAGAIN)); +} + TEST(SendFileTest, SendPipeBlocks) { // Create temp file. constexpr char kData[] = diff --git a/test/syscalls/linux/socket_generic.cc b/test/syscalls/linux/socket_generic.cc index de0b8bb11..f70047a09 100644 --- a/test/syscalls/linux/socket_generic.cc +++ b/test/syscalls/linux/socket_generic.cc @@ -379,7 +379,7 @@ TEST_P(AllSocketPairTest, RcvBufSucceeds) { EXPECT_GT(size, 0); } -TEST_P(AllSocketPairTest, SndBufSucceeds) { +TEST_P(AllSocketPairTest, GetSndBufSucceeds) { auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); int size = 0; socklen_t size_size = sizeof(size); diff --git a/test/syscalls/linux/socket_inet_loopback.cc b/test/syscalls/linux/socket_inet_loopback.cc index a11147085..344a5a22c 100644 --- a/test/syscalls/linux/socket_inet_loopback.cc +++ b/test/syscalls/linux/socket_inet_loopback.cc @@ -526,7 +526,7 @@ void TestListenWhileConnect(const TestParam& param, stopListen(listen_fd); for (auto& client : clients) { - const int kTimeout = 10000; + constexpr int kTimeout = 10000; struct pollfd pfd = { .fd = client.get(), .events = POLLIN, @@ -942,7 +942,7 @@ void setupTimeWaitClose(const TestAddress* listener, // shutdown to trigger TIME_WAIT. ASSERT_THAT(shutdown(active_closefd.get(), SHUT_WR), SyscallSucceeds()); { - const int kTimeout = 10000; + constexpr int kTimeout = 10000; struct pollfd pfd = { .fd = passive_closefd.get(), .events = POLLIN, @@ -1186,7 +1186,7 @@ TEST_P(SocketInetLoopbackTest, TCPAcceptAfterReset) { ASSERT_EQ(addrlen, listener.addr_len); // Wait for accept_fd to process the RST. - const int kTimeout = 10000; + constexpr int kTimeout = 10000; struct pollfd pfd = { .fd = accept_fd.get(), .events = POLLIN, diff --git a/test/syscalls/linux/socket_unix_dgram.cc b/test/syscalls/linux/socket_unix_dgram.cc index af0df4fb4..5b0844493 100644 --- a/test/syscalls/linux/socket_unix_dgram.cc +++ b/test/syscalls/linux/socket_unix_dgram.cc @@ -18,6 +18,8 @@ #include <sys/un.h> #include "gtest/gtest.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" #include "test/syscalls/linux/socket_test_util.h" #include "test/syscalls/linux/unix_domain_socket_test_util.h" #include "test/util/test_util.h" @@ -39,6 +41,39 @@ TEST_P(DgramUnixSocketPairTest, WriteOneSideClosed) { SyscallFailsWithErrno(ECONNREFUSED)); } +TEST_P(DgramUnixSocketPairTest, IncreasedSocketSendBufUnblocksWrites) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + int sock = sockets->first_fd(); + int buf_size = 0; + socklen_t buf_size_len = sizeof(buf_size); + ASSERT_THAT(getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &buf_size, &buf_size_len), + SyscallSucceeds()); + int opts; + ASSERT_THAT(opts = fcntl(sock, F_GETFL), SyscallSucceeds()); + opts |= O_NONBLOCK; + ASSERT_THAT(fcntl(sock, F_SETFL, opts), SyscallSucceeds()); + + std::vector<char> buf(buf_size / 4); + // Write till the socket buffer is full. + while (RetryEINTR(send)(sock, buf.data(), buf.size(), 0) != -1) { + // Sleep to give linux a chance to move data from the send buffer to the + // receive buffer. + absl::SleepFor(absl::Milliseconds(10)); // 10ms. + } + // The last error should have been EWOULDBLOCK. + ASSERT_EQ(errno, EWOULDBLOCK); + + // Now increase the socket send buffer. + buf_size = buf_size * 2; + ASSERT_THAT( + setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &buf_size, sizeof(buf_size)), + SyscallSucceeds()); + + // The send should succeed again. + ASSERT_THAT(RetryEINTR(send)(sock, buf.data(), buf.size(), 0), + SyscallSucceeds()); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/socket_unix_seqpacket.cc b/test/syscalls/linux/socket_unix_seqpacket.cc index 6d03df4d9..eb373373d 100644 --- a/test/syscalls/linux/socket_unix_seqpacket.cc +++ b/test/syscalls/linux/socket_unix_seqpacket.cc @@ -18,6 +18,8 @@ #include <sys/un.h> #include "gtest/gtest.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" #include "test/syscalls/linux/socket_test_util.h" #include "test/syscalls/linux/unix_domain_socket_test_util.h" #include "test/util/test_util.h" @@ -61,6 +63,39 @@ TEST_P(SeqpacketUnixSocketPairTest, Sendto) { SyscallSucceedsWithValue(3)); } +TEST_P(SeqpacketUnixSocketPairTest, IncreasedSocketSendBufUnblocksWrites) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + int sock = sockets->first_fd(); + int buf_size = 0; + socklen_t buf_size_len = sizeof(buf_size); + ASSERT_THAT(getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &buf_size, &buf_size_len), + SyscallSucceeds()); + int opts; + ASSERT_THAT(opts = fcntl(sock, F_GETFL), SyscallSucceeds()); + opts |= O_NONBLOCK; + ASSERT_THAT(fcntl(sock, F_SETFL, opts), SyscallSucceeds()); + + std::vector<char> buf(buf_size / 4); + // Write till the socket buffer is full. + while (RetryEINTR(send)(sock, buf.data(), buf.size(), 0) != -1) { + // Sleep to give linux a chance to move data from the send buffer to the + // receive buffer. + absl::SleepFor(absl::Milliseconds(10)); // 10ms. + } + // The last error should have been EWOULDBLOCK. + ASSERT_EQ(errno, EWOULDBLOCK); + + // Now increase the socket send buffer. + buf_size = buf_size * 2; + ASSERT_THAT( + setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &buf_size, sizeof(buf_size)), + SyscallSucceeds()); + + // The send should succeed again. + ASSERT_THAT(RetryEINTR(send)(sock, buf.data(), buf.size(), 0), + SyscallSucceeds()); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/socket_unix_stream.cc b/test/syscalls/linux/socket_unix_stream.cc index ad9c4bf37..3ff810914 100644 --- a/test/syscalls/linux/socket_unix_stream.cc +++ b/test/syscalls/linux/socket_unix_stream.cc @@ -17,6 +17,8 @@ #include <sys/un.h> #include "gtest/gtest.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" #include "test/syscalls/linux/socket_test_util.h" #include "test/syscalls/linux/unix_domain_socket_test_util.h" #include "test/util/test_util.h" @@ -134,6 +136,84 @@ TEST_P(StreamUnixSocketPairTest, GetSocketAcceptConn) { EXPECT_EQ(got, 0); } +TEST_P(StreamUnixSocketPairTest, SetSocketSendBuf) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + auto s = sockets->first_fd(); + int max = 0; + int min = 0; + { + // Discover maxmimum buffer size by setting to a really large value. + constexpr int kRcvBufSz = INT_MAX; + ASSERT_THAT( + setsockopt(s, SOL_SOCKET, SO_SNDBUF, &kRcvBufSz, sizeof(kRcvBufSz)), + SyscallSucceeds()); + + max = 0; + socklen_t max_len = sizeof(max); + ASSERT_THAT(getsockopt(s, SOL_SOCKET, SO_SNDBUF, &max, &max_len), + SyscallSucceeds()); + } + + { + // Discover minimum buffer size by setting it to zero. + constexpr int kRcvBufSz = 0; + ASSERT_THAT( + setsockopt(s, SOL_SOCKET, SO_SNDBUF, &kRcvBufSz, sizeof(kRcvBufSz)), + SyscallSucceeds()); + + socklen_t min_len = sizeof(min); + ASSERT_THAT(getsockopt(s, SOL_SOCKET, SO_SNDBUF, &min, &min_len), + SyscallSucceeds()); + } + + int quarter_sz = min + (max - min) / 4; + ASSERT_THAT( + setsockopt(s, SOL_SOCKET, SO_SNDBUF, &quarter_sz, sizeof(quarter_sz)), + SyscallSucceeds()); + + int val = 0; + socklen_t val_len = sizeof(val); + ASSERT_THAT(getsockopt(s, SOL_SOCKET, SO_SNDBUF, &val, &val_len), + SyscallSucceeds()); + + // Linux doubles the value set by SO_SNDBUF/SO_SNDBUF. + quarter_sz *= 2; + ASSERT_EQ(quarter_sz, val); +} + +TEST_P(StreamUnixSocketPairTest, IncreasedSocketSendBufUnblocksWrites) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + int sock = sockets->first_fd(); + int buf_size = 0; + socklen_t buf_size_len = sizeof(buf_size); + ASSERT_THAT(getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &buf_size, &buf_size_len), + SyscallSucceeds()); + int opts; + ASSERT_THAT(opts = fcntl(sock, F_GETFL), SyscallSucceeds()); + opts |= O_NONBLOCK; + ASSERT_THAT(fcntl(sock, F_SETFL, opts), SyscallSucceeds()); + + std::vector<char> buf(buf_size / 4); + // Write till the socket buffer is full. + while (RetryEINTR(send)(sock, buf.data(), buf.size(), 0) != -1) { + // Sleep to give linux a chance to move data from the send buffer to the + // receive buffer. + absl::SleepFor(absl::Milliseconds(10)); // 10ms. + } + // The last error should have been EWOULDBLOCK. + ASSERT_EQ(errno, EWOULDBLOCK); + + // Now increase the socket send buffer. + buf_size = buf_size * 2; + ASSERT_THAT( + setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &buf_size, sizeof(buf_size)), + SyscallSucceeds()); + + // The send should succeed again. + ASSERT_THAT(RetryEINTR(send)(sock, buf.data(), buf.size(), 0), + SyscallSucceeds()); +} + INSTANTIATE_TEST_SUITE_P( AllUnixDomainSockets, StreamUnixSocketPairTest, ::testing::ValuesIn(IncludeReversals(VecCat<SocketPairKind>( diff --git a/test/syscalls/linux/udp_socket.cc b/test/syscalls/linux/udp_socket.cc index 650f12350..50f589708 100644 --- a/test/syscalls/linux/udp_socket.cc +++ b/test/syscalls/linux/udp_socket.cc @@ -2061,11 +2061,92 @@ TEST_P(UdpSocketTest, SendToZeroPort) { SyscallSucceedsWithValue(sizeof(buf))); } +TEST_P(UdpSocketTest, ConnectToZeroPortUnbound) { + struct sockaddr_storage addr = InetLoopbackAddr(); + SetPort(&addr, 0); + ASSERT_THAT( + connect(sock_.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen_), + SyscallSucceeds()); +} + +TEST_P(UdpSocketTest, ConnectToZeroPortBound) { + struct sockaddr_storage addr = InetLoopbackAddr(); + ASSERT_NO_ERRNO( + BindSocket(sock_.get(), reinterpret_cast<struct sockaddr*>(&addr))); + + SetPort(&addr, 0); + ASSERT_THAT( + connect(sock_.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen_), + SyscallSucceeds()); + socklen_t len = sizeof(sockaddr_storage); + ASSERT_THAT( + getsockname(sock_.get(), reinterpret_cast<struct sockaddr*>(&addr), &len), + SyscallSucceeds()); + ASSERT_EQ(len, addrlen_); +} + +TEST_P(UdpSocketTest, ConnectToZeroPortConnected) { + struct sockaddr_storage addr = InetLoopbackAddr(); + ASSERT_NO_ERRNO( + BindSocket(sock_.get(), reinterpret_cast<struct sockaddr*>(&addr))); + + // Connect to an address with non-zero port should succeed. + ASSERT_THAT( + connect(sock_.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen_), + SyscallSucceeds()); + sockaddr_storage peername; + socklen_t peerlen = sizeof(peername); + ASSERT_THAT( + getpeername(sock_.get(), reinterpret_cast<struct sockaddr*>(&peername), + &peerlen), + SyscallSucceeds()); + ASSERT_EQ(peerlen, addrlen_); + ASSERT_EQ(memcmp(&peername, &addr, addrlen_), 0); + + // However connect() to an address with port 0 will make the following + // getpeername() fail. + SetPort(&addr, 0); + ASSERT_THAT( + connect(sock_.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen_), + SyscallSucceeds()); + ASSERT_THAT( + getpeername(sock_.get(), reinterpret_cast<struct sockaddr*>(&peername), + &peerlen), + SyscallFailsWithErrno(ENOTCONN)); +} + INSTANTIATE_TEST_SUITE_P(AllInetTests, UdpSocketTest, ::testing::Values(AddressFamily::kIpv4, AddressFamily::kIpv6, AddressFamily::kDualStack)); +TEST(UdpInet6SocketTest, ConnectInet4Sockaddr) { + // glibc getaddrinfo expects the invariant expressed by this test to be held. + const sockaddr_in connect_sockaddr = { + .sin_family = AF_INET, .sin_addr = {.s_addr = htonl(INADDR_LOOPBACK)}}; + auto sock_ = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)); + ASSERT_THAT( + connect(sock_.get(), + reinterpret_cast<const struct sockaddr*>(&connect_sockaddr), + sizeof(sockaddr_in)), + SyscallSucceeds()); + socklen_t len; + sockaddr_storage sockname; + ASSERT_THAT(getsockname(sock_.get(), + reinterpret_cast<struct sockaddr*>(&sockname), &len), + SyscallSucceeds()); + ASSERT_EQ(sockname.ss_family, AF_INET6); + ASSERT_EQ(len, sizeof(sockaddr_in6)); + auto sin6 = reinterpret_cast<struct sockaddr_in6*>(&sockname); + char addr_buf[INET6_ADDRSTRLEN]; + const char* addr; + ASSERT_NE(addr = inet_ntop(sockname.ss_family, &sockname, addr_buf, + sizeof(addr_buf)), + nullptr); + ASSERT_TRUE(IN6_IS_ADDR_V4MAPPED(sin6->sin6_addr.s6_addr)) << addr; +} + } // namespace } // namespace testing |