diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/packetimpact/README.md | 7 | ||||
-rw-r--r-- | test/packetimpact/runner/defs.bzl | 169 | ||||
-rw-r--r-- | test/packetimpact/tests/BUILD | 83 | ||||
-rw-r--r-- | test/syscalls/linux/BUILD | 1 | ||||
-rw-r--r-- | test/syscalls/linux/flock.cc | 46 | ||||
-rw-r--r-- | test/syscalls/linux/mknod.cc | 16 | ||||
-rw-r--r-- | test/syscalls/linux/pipe.cc | 30 | ||||
-rw-r--r-- | test/syscalls/linux/socket_ip_tcp_generic.cc | 51 | ||||
-rw-r--r-- | test/syscalls/linux/socket_netlink_route.cc | 44 | ||||
-rw-r--r-- | test/syscalls/linux/socket_netlink_util.cc | 11 | ||||
-rw-r--r-- | test/syscalls/linux/socket_netlink_util.h | 8 | ||||
-rw-r--r-- | test/syscalls/linux/timers.cc | 94 | ||||
-rw-r--r-- | test/util/save_util_linux.cc | 2 | ||||
-rw-r--r-- | test/util/timer_util.cc | 18 | ||||
-rw-r--r-- | test/util/timer_util.h | 94 |
15 files changed, 493 insertions, 181 deletions
diff --git a/test/packetimpact/README.md b/test/packetimpact/README.md index ffa96ba98..fe0976ba5 100644 --- a/test/packetimpact/README.md +++ b/test/packetimpact/README.md @@ -694,6 +694,13 @@ func TestMyTcpTest(t *testing.T) { } ``` +### Adding a new packetimpact test + +* Create a go test in the [tests directory](tests/) +* Add a `packetimpact_testbench` rule in [BUILD](tests/BUILD) +* Add the test into the `ALL_TESTS` list in [defs.bzl](runner/defs.bzl), + otherwise you will see an error message complaining about a missing test. + ## Other notes * The time between receiving a SYN-ACK and replying with an ACK in `Handshake` diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl index f56d3c42e..1546d0d51 100644 --- a/test/packetimpact/runner/defs.bzl +++ b/test/packetimpact/runner/defs.bzl @@ -110,29 +110,15 @@ def packetimpact_netstack_test( **kwargs ) -def packetimpact_go_test(name, size = "small", pure = True, expect_native_failure = False, expect_netstack_failure = False, **kwargs): +def packetimpact_go_test(name, expect_native_failure = False, expect_netstack_failure = False): """Add packetimpact tests written in go. Args: name: name of the test - size: size of the test - pure: make a static go binary expect_native_failure: the test must fail natively expect_netstack_failure: the test must fail for Netstack - **kwargs: all the other args, forwarded to go_test """ testbench_binary = name + "_test" - go_test( - name = testbench_binary, - size = size, - pure = pure, - nogo = False, # FIXME(gvisor.dev/issue/3374): Not working with all build systems. - tags = [ - "local", - "manual", - ], - **kwargs - ) packetimpact_native_test( name = name, expect_failure = expect_native_failure, @@ -143,3 +129,156 @@ def packetimpact_go_test(name, size = "small", pure = True, expect_native_failur expect_failure = expect_netstack_failure, testbench_binary = testbench_binary, ) + +def packetimpact_testbench(name, size = "small", pure = True, **kwargs): + """Build packetimpact testbench written in go. + + Args: + name: name of the test + size: size of the test + pure: make a static go binary + **kwargs: all the other args, forwarded to go_test + """ + go_test( + name = name + "_test", + size = size, + pure = pure, + nogo = False, # FIXME(gvisor.dev/issue/3374): Not working with all build systems. + tags = [ + "local", + "manual", + ], + **kwargs + ) + +PacketimpactTestInfo = provider( + doc = "Provide information for packetimpact tests", + fields = ["name", "expect_netstack_failure"], +) + +ALL_TESTS = [ + PacketimpactTestInfo( + name = "fin_wait2_timeout", + ), + PacketimpactTestInfo( + name = "ipv4_id_uniqueness", + ), + PacketimpactTestInfo( + name = "udp_discard_mcast_source_addr", + ), + PacketimpactTestInfo( + name = "udp_recv_mcast_bcast", + ), + PacketimpactTestInfo( + name = "udp_any_addr_recv_unicast", + ), + PacketimpactTestInfo( + name = "udp_icmp_error_propagation", + ), + PacketimpactTestInfo( + name = "tcp_reordering", + # TODO(b/139368047): Fix netstack then remove the line below. + expect_netstack_failure = True, + ), + PacketimpactTestInfo( + name = "tcp_window_shrink", + ), + PacketimpactTestInfo( + name = "tcp_zero_window_probe", + ), + PacketimpactTestInfo( + name = "tcp_zero_window_probe_retransmit", + ), + PacketimpactTestInfo( + name = "tcp_zero_window_probe_usertimeout", + ), + PacketimpactTestInfo( + name = "tcp_retransmits", + ), + PacketimpactTestInfo( + name = "tcp_outside_the_window", + ), + PacketimpactTestInfo( + name = "tcp_noaccept_close_rst", + ), + PacketimpactTestInfo( + name = "tcp_send_window_sizes_piggyback", + ), + PacketimpactTestInfo( + name = "tcp_unacc_seq_ack", + ), + PacketimpactTestInfo( + name = "tcp_paws_mechanism", + # TODO(b/156682000): Fix netstack then remove the line below. + expect_netstack_failure = True, + ), + PacketimpactTestInfo( + name = "tcp_user_timeout", + ), + PacketimpactTestInfo( + name = "tcp_queue_receive_in_syn_sent", + ), + PacketimpactTestInfo( + name = "tcp_synsent_reset", + ), + PacketimpactTestInfo( + name = "tcp_synrcvd_reset", + ), + PacketimpactTestInfo( + name = "tcp_network_unreachable", + ), + PacketimpactTestInfo( + name = "tcp_cork_mss", + ), + PacketimpactTestInfo( + name = "tcp_handshake_window_size", + ), + PacketimpactTestInfo( + name = "tcp_timewait_reset", + # TODO(b/168523247): Fix netstack then remove the line below. + expect_netstack_failure = True, + ), + PacketimpactTestInfo( + name = "tcp_queue_send_in_syn_sent", + ), + PacketimpactTestInfo( + name = "icmpv6_param_problem", + # TODO(b/153485026): Fix netstack then remove the line below. + expect_netstack_failure = True, + ), + PacketimpactTestInfo( + name = "ipv6_unknown_options_action", + # TODO(b/159928940): Fix netstack then remove the line below. + expect_netstack_failure = True, + ), + PacketimpactTestInfo( + name = "ipv6_fragment_reassembly", + ), + PacketimpactTestInfo( + name = "udp_send_recv_dgram", + ), + PacketimpactTestInfo( + name = "tcp_linger", + ), + PacketimpactTestInfo( + name = "tcp_rcv_buf_space", + ), +] + +def validate_all_tests(): + """ + Make sure that ALL_TESTS list is in sync with the rules in BUILD. + + This function is order-dependent, it is intended to be used after + all packetimpact_testbench rules and before using ALL_TESTS list + at the end of BUILD. + """ + all_tests_dict = {} # there is no set, using dict to approximate. + for test in ALL_TESTS: + rule_name = test.name + "_test" + all_tests_dict[rule_name] = True + if not native.existing_rule(rule_name): + fail("%s does not have a packetimpact_testbench rule in BUILD" % test.name) + for name in native.existing_rules(): + if name.endswith("_test") and name not in all_tests_dict: + fail("%s is not declared in ALL_TESTS list in defs.bzl" % name[:-5]) diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD index 11db49e39..8c2de5a9f 100644 --- a/test/packetimpact/tests/BUILD +++ b/test/packetimpact/tests/BUILD @@ -1,11 +1,11 @@ -load("//test/packetimpact/runner:defs.bzl", "packetimpact_go_test") +load("//test/packetimpact/runner:defs.bzl", "ALL_TESTS", "packetimpact_go_test", "packetimpact_testbench", "validate_all_tests") package( default_visibility = ["//test/packetimpact:__subpackages__"], licenses = ["notice"], ) -packetimpact_go_test( +packetimpact_testbench( name = "fin_wait2_timeout", srcs = ["fin_wait2_timeout_test.go"], deps = [ @@ -15,7 +15,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "ipv4_id_uniqueness", srcs = ["ipv4_id_uniqueness_test.go"], deps = [ @@ -26,7 +26,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "udp_discard_mcast_source_addr", srcs = ["udp_discard_mcast_source_addr_test.go"], deps = [ @@ -37,7 +37,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "udp_recv_mcast_bcast", srcs = ["udp_recv_mcast_bcast_test.go"], deps = [ @@ -49,7 +49,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "udp_any_addr_recv_unicast", srcs = ["udp_any_addr_recv_unicast_test.go"], deps = [ @@ -60,7 +60,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "udp_icmp_error_propagation", srcs = ["udp_icmp_error_propagation_test.go"], deps = [ @@ -71,11 +71,9 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_reordering", srcs = ["tcp_reordering_test.go"], - # TODO(b/139368047): Fix netstack then remove the line below. - expect_netstack_failure = True, deps = [ "//pkg/tcpip/header", "//pkg/tcpip/seqnum", @@ -84,7 +82,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_window_shrink", srcs = ["tcp_window_shrink_test.go"], deps = [ @@ -94,7 +92,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_zero_window_probe", srcs = ["tcp_zero_window_probe_test.go"], deps = [ @@ -104,7 +102,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_zero_window_probe_retransmit", srcs = ["tcp_zero_window_probe_retransmit_test.go"], deps = [ @@ -114,7 +112,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_zero_window_probe_usertimeout", srcs = ["tcp_zero_window_probe_usertimeout_test.go"], deps = [ @@ -124,7 +122,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_retransmits", srcs = ["tcp_retransmits_test.go"], deps = [ @@ -134,7 +132,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_outside_the_window", srcs = ["tcp_outside_the_window_test.go"], deps = [ @@ -145,7 +143,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_noaccept_close_rst", srcs = ["tcp_noaccept_close_rst_test.go"], deps = [ @@ -155,7 +153,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_send_window_sizes_piggyback", srcs = ["tcp_send_window_sizes_piggyback_test.go"], deps = [ @@ -165,7 +163,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_unacc_seq_ack", srcs = ["tcp_unacc_seq_ack_test.go"], deps = [ @@ -176,11 +174,9 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_paws_mechanism", srcs = ["tcp_paws_mechanism_test.go"], - # TODO(b/156682000): Fix netstack then remove the line below. - expect_netstack_failure = True, deps = [ "//pkg/tcpip/header", "//pkg/tcpip/seqnum", @@ -189,7 +185,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_user_timeout", srcs = ["tcp_user_timeout_test.go"], deps = [ @@ -199,7 +195,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_queue_receive_in_syn_sent", srcs = ["tcp_queue_receive_in_syn_sent_test.go"], deps = [ @@ -209,7 +205,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_synsent_reset", srcs = ["tcp_synsent_reset_test.go"], deps = [ @@ -219,7 +215,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_synrcvd_reset", srcs = ["tcp_synrcvd_reset_test.go"], deps = [ @@ -229,7 +225,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_network_unreachable", srcs = ["tcp_network_unreachable_test.go"], deps = [ @@ -239,7 +235,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_cork_mss", srcs = ["tcp_cork_mss_test.go"], deps = [ @@ -249,7 +245,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_handshake_window_size", srcs = ["tcp_handshake_window_size_test.go"], deps = [ @@ -259,11 +255,9 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_timewait_reset", srcs = ["tcp_timewait_reset_test.go"], - # TODO(b/168523247): Fix netstack then remove the line below. - expect_netstack_failure = True, deps = [ "//pkg/tcpip/header", "//test/packetimpact/testbench", @@ -271,7 +265,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_queue_send_in_syn_sent", srcs = ["tcp_queue_send_in_syn_sent_test.go"], deps = [ @@ -281,11 +275,9 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "icmpv6_param_problem", srcs = ["icmpv6_param_problem_test.go"], - # TODO(b/153485026): Fix netstack then remove the line below. - expect_netstack_failure = True, deps = [ "//pkg/tcpip", "//pkg/tcpip/header", @@ -294,11 +286,9 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "ipv6_unknown_options_action", srcs = ["ipv6_unknown_options_action_test.go"], - # TODO(b/159928940): Fix netstack then remove the line below. - expect_netstack_failure = True, deps = [ "//pkg/tcpip", "//pkg/tcpip/header", @@ -307,7 +297,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "ipv6_fragment_reassembly", srcs = ["ipv6_fragment_reassembly_test.go"], deps = [ @@ -319,7 +309,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "udp_send_recv_dgram", srcs = ["udp_send_recv_dgram_test.go"], deps = [ @@ -329,7 +319,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_linger", srcs = ["tcp_linger_test.go"], deps = [ @@ -339,7 +329,7 @@ packetimpact_go_test( ], ) -packetimpact_go_test( +packetimpact_testbench( name = "tcp_rcv_buf_space", srcs = ["tcp_rcv_buf_space_test.go"], deps = [ @@ -348,3 +338,10 @@ packetimpact_go_test( "@org_golang_x_sys//unix:go_default_library", ], ) + +validate_all_tests() + +[packetimpact_go_test( + name = t.name, + expect_netstack_failure = hasattr(t, "expect_netstack_failure"), +) for t in ALL_TESTS] diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 36b7f1b97..572f39a5d 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -3624,6 +3624,7 @@ cc_binary( "//test/util:signal_util", "//test/util:test_util", "//test/util:thread_util", + "//test/util:timer_util", ], ) diff --git a/test/syscalls/linux/flock.cc b/test/syscalls/linux/flock.cc index 549141cbb..b286e84fe 100644 --- a/test/syscalls/linux/flock.cc +++ b/test/syscalls/linux/flock.cc @@ -216,14 +216,29 @@ TEST_F(FlockTest, TestSharedLockFailExclusiveHolderBlocking_NoRandomSave) { const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); - // Register a signal handler for SIGALRM and set an alarm that will go off - // while blocking in the subsequent flock() call. This will interrupt flock() - // and cause it to return EINTR. + // Make sure that a blocking flock() call will return EINTR when interrupted + // by a signal. Create a timer that will go off while blocking on flock(), and + // register the corresponding signal handler. + auto timer = ASSERT_NO_ERRNO_AND_VALUE( + TimerCreate(CLOCK_MONOTONIC, sigevent_t{ + .sigev_signo = SIGALRM, + .sigev_notify = SIGEV_SIGNAL, + })); + struct sigaction act = {}; act.sa_handler = trivial_handler; ASSERT_THAT(sigaction(SIGALRM, &act, NULL), SyscallSucceeds()); - ASSERT_THAT(ualarm(10000, 0), SyscallSucceeds()); + + // Now that the signal handler is registered, set the timer. Set an interval + // so that it's ok if the timer goes off before we call flock. + ASSERT_NO_ERRNO( + timer.Set(0, itimerspec{ + .it_interval = absl::ToTimespec(absl::Milliseconds(10)), + .it_value = absl::ToTimespec(absl::Milliseconds(10)), + })); + ASSERT_THAT(flock(fd.get(), LOCK_SH), SyscallFailsWithErrno(EINTR)); + timer.reset(); // Unlock ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); @@ -258,14 +273,29 @@ TEST_F(FlockTest, TestExclusiveLockFailExclusiveHolderBlocking_NoRandomSave) { const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR)); - // Register a signal handler for SIGALRM and set an alarm that will go off - // while blocking in the subsequent flock() call. This will interrupt flock() - // and cause it to return EINTR. + // Make sure that a blocking flock() call will return EINTR when interrupted + // by a signal. Create a timer that will go off while blocking on flock(), and + // register the corresponding signal handler. + auto timer = ASSERT_NO_ERRNO_AND_VALUE( + TimerCreate(CLOCK_MONOTONIC, sigevent_t{ + .sigev_signo = SIGALRM, + .sigev_notify = SIGEV_SIGNAL, + })); + struct sigaction act = {}; act.sa_handler = trivial_handler; ASSERT_THAT(sigaction(SIGALRM, &act, NULL), SyscallSucceeds()); - ASSERT_THAT(ualarm(10000, 0), SyscallSucceeds()); + + // Now that the signal handler is registered, set the timer. Set an interval + // so that it's ok if the timer goes off before we call flock. + ASSERT_NO_ERRNO( + timer.Set(0, itimerspec{ + .it_interval = absl::ToTimespec(absl::Milliseconds(10)), + .it_value = absl::ToTimespec(absl::Milliseconds(10)), + })); + ASSERT_THAT(flock(fd.get(), LOCK_EX), SyscallFailsWithErrno(EINTR)); + timer.reset(); // Unlock ASSERT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceedsWithValue(0)); diff --git a/test/syscalls/linux/mknod.cc b/test/syscalls/linux/mknod.cc index ae65d366b..b96907b30 100644 --- a/test/syscalls/linux/mknod.cc +++ b/test/syscalls/linux/mknod.cc @@ -93,15 +93,15 @@ TEST(MknodTest, MknodOnExistingPathFails) { } TEST(MknodTest, UnimplementedTypesReturnError) { - const std::string path = NewTempAbsPath(); + // TODO(gvisor.dev/issue/1624): These file types are supported by some + // filesystems in VFS2, so this test should be deleted along with VFS1. + SKIP_IF(!IsRunningWithVFS1()); - if (IsRunningWithVFS1()) { - ASSERT_THAT(mknod(path.c_str(), S_IFSOCK, 0), - SyscallFailsWithErrno(EOPNOTSUPP)); - } - // These will fail on linux as well since we don't have CAP_MKNOD. - ASSERT_THAT(mknod(path.c_str(), S_IFCHR, 0), SyscallFailsWithErrno(EPERM)); - ASSERT_THAT(mknod(path.c_str(), S_IFBLK, 0), SyscallFailsWithErrno(EPERM)); + const std::string path = NewTempAbsPath(); + EXPECT_THAT(mknod(path.c_str(), S_IFSOCK, 0), + SyscallFailsWithErrno(EOPNOTSUPP)); + EXPECT_THAT(mknod(path.c_str(), S_IFCHR, 0), SyscallFailsWithErrno(EPERM)); + EXPECT_THAT(mknod(path.c_str(), S_IFBLK, 0), SyscallFailsWithErrno(EPERM)); } TEST(MknodTest, Socket) { diff --git a/test/syscalls/linux/pipe.cc b/test/syscalls/linux/pipe.cc index c097c9187..06d9dbf65 100644 --- a/test/syscalls/linux/pipe.cc +++ b/test/syscalls/linux/pipe.cc @@ -569,30 +569,38 @@ TEST_P(PipeTest, Streaming) { // Size() requires 2 syscalls, call it once and remember the value. const int pipe_size = Size(); + const size_t streamed_bytes = 4 * pipe_size; absl::Notification notify; - ScopedThread t([this, ¬ify, pipe_size]() { + ScopedThread t([&, this]() { + std::vector<char> buf(1024); // Don't start until it's full. notify.WaitForNotification(); - for (int i = 0; i < pipe_size; i++) { - int rbuf; - ASSERT_THAT(read(rfd_.get(), &rbuf, sizeof(rbuf)), - SyscallSucceedsWithValue(sizeof(rbuf))); - EXPECT_EQ(rbuf, i); + ssize_t total = 0; + while (total < streamed_bytes) { + ASSERT_THAT(read(rfd_.get(), buf.data(), buf.size()), + SyscallSucceedsWithValue(buf.size())); + total += buf.size(); } }); // Write 4 bytes * pipe_size. It will fill up the pipe once, notify the reader // to start. Then we write pipe size worth 3 more times to ensure the reader // can follow along. + // + // The size of each write (which is determined by buf.size()) must be smaller + // than the size of the pipe (which, in the "smallbuffer" configuration, is 1 + // page) for the check for notify.Notify() below to be correct. + std::vector<char> buf(1024); + RandomizeBuffer(buf.data(), buf.size()); ssize_t total = 0; - for (int i = 0; i < pipe_size; i++) { - ssize_t written = write(wfd_.get(), &i, sizeof(i)); - ASSERT_THAT(written, SyscallSucceedsWithValue(sizeof(i))); - total += written; + while (total < streamed_bytes) { + ASSERT_THAT(write(wfd_.get(), buf.data(), buf.size()), + SyscallSucceedsWithValue(buf.size())); + total += buf.size(); // Is the next write about to fill up the buffer? Wake up the reader once. - if (total < pipe_size && (total + written) >= pipe_size) { + if (total < pipe_size && (total + buf.size()) >= pipe_size) { notify.Notify(); } } diff --git a/test/syscalls/linux/socket_ip_tcp_generic.cc b/test/syscalls/linux/socket_ip_tcp_generic.cc index f4b69c46c..831d96262 100644 --- a/test/syscalls/linux/socket_ip_tcp_generic.cc +++ b/test/syscalls/linux/socket_ip_tcp_generic.cc @@ -14,6 +14,7 @@ #include "test/syscalls/linux/socket_ip_tcp_generic.h" +#include <fcntl.h> #include <netinet/in.h> #include <netinet/tcp.h> #include <poll.h> @@ -979,6 +980,56 @@ TEST_P(TCPSocketPairTest, SetTCPUserTimeoutAboveZero) { EXPECT_EQ(get, kAbove); } +#ifdef __linux__ +TEST_P(TCPSocketPairTest, SpliceFromPipe) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + int fds[2]; + ASSERT_THAT(pipe(fds), SyscallSucceeds()); + FileDescriptor rfd(fds[0]); + FileDescriptor wfd(fds[1]); + + // Fill with some random data. + std::vector<char> buf(kPageSize / 2); + RandomizeBuffer(buf.data(), buf.size()); + ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), + SyscallSucceedsWithValue(buf.size())); + + EXPECT_THAT( + splice(rfd.get(), nullptr, sockets->first_fd(), nullptr, kPageSize, 0), + SyscallSucceedsWithValue(buf.size())); + + std::vector<char> rbuf(buf.size()); + ASSERT_THAT(read(sockets->second_fd(), rbuf.data(), rbuf.size()), + SyscallSucceedsWithValue(buf.size())); + EXPECT_EQ(memcmp(rbuf.data(), buf.data(), buf.size()), 0); +} + +TEST_P(TCPSocketPairTest, SpliceToPipe) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + int fds[2]; + ASSERT_THAT(pipe(fds), SyscallSucceeds()); + FileDescriptor rfd(fds[0]); + FileDescriptor wfd(fds[1]); + + // Fill with some random data. + std::vector<char> buf(kPageSize / 2); + RandomizeBuffer(buf.data(), buf.size()); + ASSERT_THAT(write(sockets->first_fd(), buf.data(), buf.size()), + SyscallSucceedsWithValue(buf.size())); + shutdown(sockets->first_fd(), SHUT_WR); + EXPECT_THAT( + splice(sockets->second_fd(), nullptr, wfd.get(), nullptr, kPageSize, 0), + SyscallSucceedsWithValue(buf.size())); + + std::vector<char> rbuf(buf.size()); + ASSERT_THAT(read(rfd.get(), rbuf.data(), rbuf.size()), + SyscallSucceedsWithValue(buf.size())); + EXPECT_EQ(memcmp(rbuf.data(), buf.data(), buf.size()), 0); +} +#endif // __linux__ + TEST_P(TCPSocketPairTest, SetTCPWindowClampBelowMinRcvBufConnectedSocket) { auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); // Discover minimum receive buf by setting a really low value diff --git a/test/syscalls/linux/socket_netlink_route.cc b/test/syscalls/linux/socket_netlink_route.cc index b3fcf8e7c..241ddad74 100644 --- a/test/syscalls/linux/socket_netlink_route.cc +++ b/test/syscalls/linux/socket_netlink_route.cc @@ -13,6 +13,7 @@ // limitations under the License. #include <arpa/inet.h> +#include <fcntl.h> #include <ifaddrs.h> #include <linux/if.h> #include <linux/netlink.h> @@ -335,6 +336,49 @@ TEST(NetlinkRouteTest, MsgHdrMsgTrunc) { EXPECT_EQ((msg.msg_flags & MSG_TRUNC), MSG_TRUNC); } +TEST(NetlinkRouteTest, SpliceFromPipe) { + Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink()); + FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); + + int fds[2]; + ASSERT_THAT(pipe(fds), SyscallSucceeds()); + FileDescriptor rfd(fds[0]); + FileDescriptor wfd(fds[1]); + + struct request { + struct nlmsghdr hdr; + struct ifinfomsg ifm; + }; + + struct request req = {}; + req.hdr.nlmsg_len = sizeof(req); + req.hdr.nlmsg_type = RTM_GETLINK; + req.hdr.nlmsg_flags = NLM_F_REQUEST; + req.hdr.nlmsg_seq = kSeq; + req.ifm.ifi_family = AF_UNSPEC; + req.ifm.ifi_index = loopback_link.index; + + ASSERT_THAT(write(wfd.get(), &req, sizeof(req)), + SyscallSucceedsWithValue(sizeof(req))); + + EXPECT_THAT(splice(rfd.get(), nullptr, fd.get(), nullptr, sizeof(req) + 1, 0), + SyscallSucceedsWithValue(sizeof(req))); + close(wfd.release()); + EXPECT_THAT(splice(rfd.get(), nullptr, fd.get(), nullptr, sizeof(req) + 1, 0), + SyscallSucceedsWithValue(0)); + + bool found = false; + ASSERT_NO_ERRNO(NetlinkResponse( + fd, + [&](const struct nlmsghdr* hdr) { + CheckLinkMsg(hdr, loopback_link); + found = true; + }, + false)); + EXPECT_TRUE(found) << "Netlink response does not contain any links."; +} + TEST(NetlinkRouteTest, MsgTruncMsgHdrMsgTrunc) { FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); diff --git a/test/syscalls/linux/socket_netlink_util.cc b/test/syscalls/linux/socket_netlink_util.cc index 952eecfe8..bdebea321 100644 --- a/test/syscalls/linux/socket_netlink_util.cc +++ b/test/syscalls/linux/socket_netlink_util.cc @@ -67,10 +67,21 @@ PosixError NetlinkRequestResponse( RETURN_ERROR_IF_SYSCALL_FAIL(RetryEINTR(sendmsg)(fd.get(), &msg, 0)); + return NetlinkResponse(fd, fn, expect_nlmsgerr); +} + +PosixError NetlinkResponse( + const FileDescriptor& fd, + const std::function<void(const struct nlmsghdr* hdr)>& fn, + bool expect_nlmsgerr) { constexpr size_t kBufferSize = 4096; std::vector<char> buf(kBufferSize); + struct iovec iov = {}; iov.iov_base = buf.data(); iov.iov_len = buf.size(); + struct msghdr msg = {}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; // If NLM_F_MULTI is set, response is a series of messages that ends with a // NLMSG_DONE message. diff --git a/test/syscalls/linux/socket_netlink_util.h b/test/syscalls/linux/socket_netlink_util.h index e13ead406..f97276d44 100644 --- a/test/syscalls/linux/socket_netlink_util.h +++ b/test/syscalls/linux/socket_netlink_util.h @@ -41,6 +41,14 @@ PosixError NetlinkRequestResponse( const std::function<void(const struct nlmsghdr* hdr)>& fn, bool expect_nlmsgerr); +// Call fn on all response netlink messages. +// +// To be used on requests with NLM_F_MULTI reponses. +PosixError NetlinkResponse( + const FileDescriptor& fd, + const std::function<void(const struct nlmsghdr* hdr)>& fn, + bool expect_nlmsgerr); + // Send the passed request and call fn on all response netlink messages. // // To be used on requests without NLM_F_MULTI reponses. diff --git a/test/syscalls/linux/timers.cc b/test/syscalls/linux/timers.cc index 4b3c44527..cac94d9e1 100644 --- a/test/syscalls/linux/timers.cc +++ b/test/syscalls/linux/timers.cc @@ -33,6 +33,7 @@ #include "test/util/signal_util.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" +#include "test/util/timer_util.h" ABSL_FLAG(bool, timers_test_sleep, false, "If true, sleep forever instead of running tests."); @@ -215,99 +216,6 @@ TEST(TimerTest, ProcessKilledOnCPUHardLimit) { EXPECT_GE(cpu, kHardLimit); } -// RAII type for a kernel "POSIX" interval timer. (The kernel provides system -// calls such as timer_create that behave very similarly, but not identically, -// to those described by timer_create(2); in particular, the kernel does not -// implement SIGEV_THREAD. glibc builds POSIX-compliant interval timers based on -// these kernel interval timers.) -// -// Compare implementation to FileDescriptor. -class IntervalTimer { - public: - IntervalTimer() = default; - - explicit IntervalTimer(int id) { set_id(id); } - - IntervalTimer(IntervalTimer&& orig) : id_(orig.release()) {} - - IntervalTimer& operator=(IntervalTimer&& orig) { - if (this == &orig) return *this; - reset(orig.release()); - return *this; - } - - IntervalTimer(const IntervalTimer& other) = delete; - IntervalTimer& operator=(const IntervalTimer& other) = delete; - - ~IntervalTimer() { reset(); } - - int get() const { return id_; } - - int release() { - int const id = id_; - id_ = -1; - return id; - } - - void reset() { reset(-1); } - - void reset(int id) { - if (id_ >= 0) { - TEST_PCHECK(syscall(SYS_timer_delete, id_) == 0); - MaybeSave(); - } - set_id(id); - } - - PosixErrorOr<struct itimerspec> Set( - int flags, const struct itimerspec& new_value) const { - struct itimerspec old_value = {}; - if (syscall(SYS_timer_settime, id_, flags, &new_value, &old_value) < 0) { - return PosixError(errno, "timer_settime"); - } - MaybeSave(); - return old_value; - } - - PosixErrorOr<struct itimerspec> Get() const { - struct itimerspec curr_value = {}; - if (syscall(SYS_timer_gettime, id_, &curr_value) < 0) { - return PosixError(errno, "timer_gettime"); - } - MaybeSave(); - return curr_value; - } - - PosixErrorOr<int> Overruns() const { - int rv = syscall(SYS_timer_getoverrun, id_); - if (rv < 0) { - return PosixError(errno, "timer_getoverrun"); - } - MaybeSave(); - return rv; - } - - private: - void set_id(int id) { id_ = std::max(id, -1); } - - // Kernel timer_t is int; glibc timer_t is void*. - int id_ = -1; -}; - -PosixErrorOr<IntervalTimer> TimerCreate(clockid_t clockid, - const struct sigevent& sev) { - int timerid; - int ret = syscall(SYS_timer_create, clockid, &sev, &timerid); - if (ret < 0) { - return PosixError(errno, "timer_create"); - } - if (ret > 0) { - return PosixError(EINVAL, "timer_create should never return positive"); - } - MaybeSave(); - return IntervalTimer(timerid); -} - // See timerfd.cc:TimerSlack() for rationale. constexpr absl::Duration kTimerSlack = absl::Milliseconds(500); diff --git a/test/util/save_util_linux.cc b/test/util/save_util_linux.cc index d0aea8e6a..fbac94912 100644 --- a/test/util/save_util_linux.cc +++ b/test/util/save_util_linux.cc @@ -46,4 +46,4 @@ void MaybeSave() { } // namespace testing } // namespace gvisor -#endif +#endif // __linux__ diff --git a/test/util/timer_util.cc b/test/util/timer_util.cc index 43a26b0d3..75cfc4f40 100644 --- a/test/util/timer_util.cc +++ b/test/util/timer_util.cc @@ -23,5 +23,23 @@ absl::Time Now(clockid_t id) { return absl::TimeFromTimespec(now); } +#ifdef __linux__ + +PosixErrorOr<IntervalTimer> TimerCreate(clockid_t clockid, + const struct sigevent& sev) { + int timerid; + int ret = syscall(SYS_timer_create, clockid, &sev, &timerid); + if (ret < 0) { + return PosixError(errno, "timer_create"); + } + if (ret > 0) { + return PosixError(EINVAL, "timer_create should never return positive"); + } + MaybeSave(); + return IntervalTimer(timerid); +} + +#endif // __linux__ + } // namespace testing } // namespace gvisor diff --git a/test/util/timer_util.h b/test/util/timer_util.h index 31aea4fc6..926e6632f 100644 --- a/test/util/timer_util.h +++ b/test/util/timer_util.h @@ -16,6 +16,9 @@ #define GVISOR_TEST_UTIL_TIMER_UTIL_H_ #include <errno.h> +#ifdef __linux__ +#include <sys/syscall.h> +#endif #include <sys/time.h> #include <functional> @@ -30,6 +33,9 @@ namespace gvisor { namespace testing { +// Returns the current time. +absl::Time Now(clockid_t id); + // MonotonicTimer is a simple timer that uses a monotonic clock. class MonotonicTimer { public: @@ -65,8 +71,92 @@ inline PosixErrorOr<Cleanup> ScopedItimer(int which, })); } -// Returns the current time. -absl::Time Now(clockid_t id); +#ifdef __linux__ + +// RAII type for a kernel "POSIX" interval timer. (The kernel provides system +// calls such as timer_create that behave very similarly, but not identically, +// to those described by timer_create(2); in particular, the kernel does not +// implement SIGEV_THREAD. glibc builds POSIX-compliant interval timers based on +// these kernel interval timers.) +// +// Compare implementation to FileDescriptor. +class IntervalTimer { + public: + IntervalTimer() = default; + + explicit IntervalTimer(int id) { set_id(id); } + + IntervalTimer(IntervalTimer&& orig) : id_(orig.release()) {} + + IntervalTimer& operator=(IntervalTimer&& orig) { + if (this == &orig) return *this; + reset(orig.release()); + return *this; + } + + IntervalTimer(const IntervalTimer& other) = delete; + IntervalTimer& operator=(const IntervalTimer& other) = delete; + + ~IntervalTimer() { reset(); } + + int get() const { return id_; } + + int release() { + int const id = id_; + id_ = -1; + return id; + } + + void reset() { reset(-1); } + + void reset(int id) { + if (id_ >= 0) { + TEST_PCHECK(syscall(SYS_timer_delete, id_) == 0); + MaybeSave(); + } + set_id(id); + } + + PosixErrorOr<struct itimerspec> Set( + int flags, const struct itimerspec& new_value) const { + struct itimerspec old_value = {}; + if (syscall(SYS_timer_settime, id_, flags, &new_value, &old_value) < 0) { + return PosixError(errno, "timer_settime"); + } + MaybeSave(); + return old_value; + } + + PosixErrorOr<struct itimerspec> Get() const { + struct itimerspec curr_value = {}; + if (syscall(SYS_timer_gettime, id_, &curr_value) < 0) { + return PosixError(errno, "timer_gettime"); + } + MaybeSave(); + return curr_value; + } + + PosixErrorOr<int> Overruns() const { + int rv = syscall(SYS_timer_getoverrun, id_); + if (rv < 0) { + return PosixError(errno, "timer_getoverrun"); + } + MaybeSave(); + return rv; + } + + private: + void set_id(int id) { id_ = std::max(id, -1); } + + // Kernel timer_t is int; glibc timer_t is void*. + int id_ = -1; +}; + +// A wrapper around timer_create(2). +PosixErrorOr<IntervalTimer> TimerCreate(clockid_t clockid, + const struct sigevent& sev); + +#endif // __linux__ } // namespace testing } // namespace gvisor |