summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/e2e/integration_test.go53
-rw-r--r--test/packetimpact/tests/tcp_rack_test.go37
-rw-r--r--test/packetimpact/tests/udp_send_recv_dgram_test.go1
-rw-r--r--test/syscalls/BUILD4
-rw-r--r--test/syscalls/linux/BUILD15
-rw-r--r--test/syscalls/linux/exec_binary.cc28
-rw-r--r--test/syscalls/linux/open_create.cc4
-rw-r--r--test/syscalls/linux/processes.cc90
-rw-r--r--test/syscalls/linux/rename.cc33
-rw-r--r--test/syscalls/linux/sendfile.cc23
-rw-r--r--test/syscalls/linux/socket_generic.cc2
-rw-r--r--test/syscalls/linux/socket_inet_loopback.cc6
-rw-r--r--test/syscalls/linux/socket_unix_dgram.cc35
-rw-r--r--test/syscalls/linux/socket_unix_seqpacket.cc35
-rw-r--r--test/syscalls/linux/socket_unix_stream.cc80
-rw-r--r--test/syscalls/linux/udp_socket.cc81
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