summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/README.md4
-rw-r--r--test/benchmarks/README.md157
-rw-r--r--test/benchmarks/database/BUILD29
-rw-r--r--test/benchmarks/database/database.go31
-rw-r--r--test/benchmarks/database/redis_test.go123
-rw-r--r--test/benchmarks/fs/BUILD31
-rw-r--r--test/benchmarks/fs/bazel_test.go119
-rw-r--r--test/benchmarks/fs/fio_test.go170
-rw-r--r--test/benchmarks/fs/fs.go31
-rw-r--r--test/benchmarks/harness/BUILD18
-rw-r--r--test/benchmarks/harness/harness.go38
-rw-r--r--test/benchmarks/harness/machine.go81
-rw-r--r--test/benchmarks/harness/util.go46
-rw-r--r--test/benchmarks/media/BUILD21
-rw-r--r--test/benchmarks/media/ffmpeg_test.go53
-rw-r--r--test/benchmarks/media/media.go31
-rw-r--r--test/benchmarks/ml/BUILD21
-rw-r--r--test/benchmarks/ml/ml.go31
-rw-r--r--test/benchmarks/ml/tensorflow_test.go69
-rw-r--r--test/benchmarks/network/BUILD33
-rw-r--r--test/benchmarks/network/httpd_test.go181
-rw-r--r--test/benchmarks/network/iperf_test.go113
-rw-r--r--test/benchmarks/network/network.go31
-rw-r--r--test/benchmarks/network/nginx_test.go104
-rw-r--r--test/benchmarks/network/node_test.go131
-rw-r--r--test/benchmarks/tools/BUILD29
-rw-r--r--test/benchmarks/tools/ab.go94
-rw-r--r--test/benchmarks/tools/ab_test.go90
-rw-r--r--test/benchmarks/tools/fio.go124
-rw-r--r--test/benchmarks/tools/fio_test.go122
-rw-r--r--test/benchmarks/tools/hey.go75
-rw-r--r--test/benchmarks/tools/hey_test.go81
-rw-r--r--test/benchmarks/tools/iperf.go56
-rw-r--r--test/benchmarks/tools/iperf_test.go34
-rw-r--r--test/benchmarks/tools/redis.go64
-rw-r--r--test/benchmarks/tools/redis_test.go87
-rw-r--r--test/benchmarks/tools/tools.go17
-rw-r--r--test/e2e/BUILD1
-rw-r--r--test/e2e/exec_test.go136
-rw-r--r--test/e2e/integration_test.go249
-rw-r--r--test/e2e/regression_test.go8
-rw-r--r--test/image/image_test.go107
-rw-r--r--test/iptables/BUILD2
-rw-r--r--test/iptables/filter_input.go217
-rw-r--r--test/iptables/filter_output.go122
-rw-r--r--test/iptables/iptables.go4
-rw-r--r--test/iptables/iptables_test.go95
-rw-r--r--test/iptables/iptables_unsafe.go63
-rw-r--r--test/iptables/iptables_util.go169
-rw-r--r--test/iptables/nat.go404
-rw-r--r--test/iptables/runner/main.go9
-rw-r--r--test/packetdrill/defs.bzl12
-rw-r--r--test/packetimpact/README.md21
-rw-r--r--test/packetimpact/dut/posix_server.cc45
-rw-r--r--test/packetimpact/netdevs/BUILD23
-rw-r--r--test/packetimpact/netdevs/netdevs.go115
-rw-r--r--test/packetimpact/netdevs/netdevs_test.go227
-rw-r--r--test/packetimpact/proto/posix_server.proto15
-rw-r--r--test/packetimpact/runner/BUILD21
-rw-r--r--test/packetimpact/runner/defs.bzl (renamed from test/packetimpact/tests/defs.bzl)34
-rw-r--r--test/packetimpact/runner/packetimpact_test.go389
-rw-r--r--test/packetimpact/testbench/BUILD5
-rw-r--r--test/packetimpact/testbench/connections.go612
-rw-r--r--test/packetimpact/testbench/dut.go390
-rw-r--r--test/packetimpact/testbench/layers.go329
-rw-r--r--test/packetimpact/testbench/layers_test.go221
-rw-r--r--test/packetimpact/testbench/rawsockets.go47
-rw-r--r--test/packetimpact/testbench/testbench.go81
-rw-r--r--test/packetimpact/tests/BUILD152
-rw-r--r--test/packetimpact/tests/fin_wait2_timeout_test.go34
-rw-r--r--test/packetimpact/tests/icmpv6_param_problem_test.go38
-rw-r--r--test/packetimpact/tests/ipv4_id_uniqueness_test.go122
-rw-r--r--test/packetimpact/tests/ipv6_fragment_reassembly_test.go168
-rw-r--r--test/packetimpact/tests/ipv6_unknown_options_action_test.go187
-rw-r--r--test/packetimpact/tests/tcp_close_wait_ack_test.go75
-rw-r--r--test/packetimpact/tests/tcp_cork_mss_test.go84
-rw-r--r--test/packetimpact/tests/tcp_handshake_window_size_test.go66
-rw-r--r--test/packetimpact/tests/tcp_network_unreachable_test.go139
-rw-r--r--test/packetimpact/tests/tcp_noaccept_close_rst_test.go18
-rw-r--r--test/packetimpact/tests/tcp_outside_the_window_test.go42
-rw-r--r--test/packetimpact/tests/tcp_paws_mechanism_test.go32
-rw-r--r--test/packetimpact/tests/tcp_queue_receive_in_syn_sent_test.go132
-rw-r--r--test/packetimpact/tests/tcp_reordering_test.go174
-rw-r--r--test/packetimpact/tests/tcp_retransmits_test.go46
-rw-r--r--test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go105
-rw-r--r--test/packetimpact/tests/tcp_should_piggyback_test.go64
-rw-r--r--test/packetimpact/tests/tcp_synrcvd_reset_test.go52
-rw-r--r--test/packetimpact/tests/tcp_synsent_reset_test.go90
-rw-r--r--test/packetimpact/tests/tcp_user_timeout_test.go49
-rw-r--r--test/packetimpact/tests/tcp_window_shrink_test.go54
-rw-r--r--test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go60
-rw-r--r--test/packetimpact/tests/tcp_zero_window_probe_test.go66
-rw-r--r--test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go56
-rwxr-xr-xtest/packetimpact/tests/test_runner.sh325
-rw-r--r--test/packetimpact/tests/udp_any_addr_recv_unicast_test.go51
-rw-r--r--test/packetimpact/tests/udp_discard_mcast_source_addr_test.go94
-rw-r--r--test/packetimpact/tests/udp_icmp_error_propagation_test.go173
-rw-r--r--test/packetimpact/tests/udp_recv_mcast_bcast_test.go110
-rw-r--r--test/packetimpact/tests/udp_recv_multicast_test.go42
-rw-r--r--test/packetimpact/tests/udp_send_recv_dgram_test.go125
-rw-r--r--test/perf/linux/futex_benchmark.cc4
-rw-r--r--test/root/BUILD8
-rw-r--r--test/root/cgroup_test.go193
-rw-r--r--test/root/chroot_test.go21
-rw-r--r--test/root/crictl_test.go513
-rw-r--r--test/root/oom_score_adj_test.go30
-rw-r--r--test/runner/BUILD3
-rw-r--r--test/runner/defs.bzl72
-rw-r--r--test/runner/runner.go53
-rw-r--r--test/runtimes/BUILD16
-rw-r--r--test/runtimes/blacklist_go1.12.csv16
-rw-r--r--test/runtimes/blacklist_java11.csv126
-rw-r--r--test/runtimes/defs.bzl31
-rw-r--r--test/runtimes/exclude_go1.12.csv13
-rw-r--r--test/runtimes/exclude_java11.csv195
-rw-r--r--test/runtimes/exclude_nodejs12.4.0.csv (renamed from test/runtimes/blacklist_nodejs12.4.0.csv)4
-rw-r--r--test/runtimes/exclude_php7.3.6.csv (renamed from test/runtimes/blacklist_php7.3.6.csv)8
-rw-r--r--test/runtimes/exclude_python3.7.3.csv (renamed from test/runtimes/blacklist_python3.7.3.csv)0
-rw-r--r--test/runtimes/proctor/BUILD2
-rw-r--r--test/runtimes/proctor/go.go29
-rw-r--r--test/runtimes/proctor/java.go21
-rw-r--r--test/runtimes/proctor/nodejs.go8
-rw-r--r--test/runtimes/proctor/php.go9
-rw-r--r--test/runtimes/proctor/proctor.go28
-rw-r--r--test/runtimes/proctor/python.go8
-rw-r--r--test/runtimes/runner/BUILD5
-rw-r--r--test/runtimes/runner/exclude_test.go (renamed from test/runtimes/runner/blacklist_test.go)12
-rw-r--r--test/runtimes/runner/main.go90
-rw-r--r--test/syscalls/BUILD561
-rw-r--r--test/syscalls/linux/BUILD85
-rw-r--r--test/syscalls/linux/accept_bind.cc42
-rw-r--r--test/syscalls/linux/dev.cc21
-rw-r--r--test/syscalls/linux/epoll.cc22
-rw-r--r--test/syscalls/linux/eventfd.cc23
-rw-r--r--test/syscalls/linux/exec.cc32
-rw-r--r--test/syscalls/linux/exec_binary.cc5
-rw-r--r--test/syscalls/linux/fallocate.cc45
-rw-r--r--test/syscalls/linux/fcntl.cc317
-rw-r--r--test/syscalls/linux/flock.cc75
-rw-r--r--test/syscalls/linux/fpsig_fork.cc6
-rw-r--r--test/syscalls/linux/fpsig_nested.cc49
-rw-r--r--test/syscalls/linux/futex.cc92
-rw-r--r--test/syscalls/linux/getdents.cc3
-rw-r--r--test/syscalls/linux/inotify.cc846
-rw-r--r--test/syscalls/linux/link.cc15
-rw-r--r--test/syscalls/linux/mknod.cc26
-rw-r--r--test/syscalls/linux/mount.cc36
-rw-r--r--test/syscalls/linux/open.cc33
-rw-r--r--test/syscalls/linux/packet_socket.cc134
-rw-r--r--test/syscalls/linux/packet_socket_raw.cc370
-rw-r--r--test/syscalls/linux/ping_socket.cc91
-rw-r--r--test/syscalls/linux/poll.cc6
-rw-r--r--test/syscalls/linux/proc.cc80
-rw-r--r--test/syscalls/linux/proc_net.cc73
-rw-r--r--test/syscalls/linux/pty.cc11
-rw-r--r--test/syscalls/linux/pty_root.cc22
-rw-r--r--test/syscalls/linux/pwritev2.cc59
-rw-r--r--test/syscalls/linux/raw_socket.cc869
-rw-r--r--test/syscalls/linux/raw_socket_hdrincl.cc101
-rw-r--r--test/syscalls/linux/raw_socket_ipv4.cc392
-rw-r--r--test/syscalls/linux/socket.cc37
-rw-r--r--test/syscalls/linux/socket_bind_to_device_sequence.cc25
-rw-r--r--test/syscalls/linux/socket_capability.cc61
-rw-r--r--test/syscalls/linux/socket_inet_loopback.cc219
-rw-r--r--test/syscalls/linux/socket_inet_loopback_nogotsan.cc174
-rw-r--r--test/syscalls/linux/socket_ip_tcp_generic.cc97
-rw-r--r--test/syscalls/linux/socket_ip_udp_generic.cc6
-rw-r--r--test/syscalls/linux/socket_ip_unbound.cc61
-rw-r--r--test/syscalls/linux/socket_ipv4_udp_unbound.cc272
-rw-r--r--test/syscalls/linux/socket_netdevice.cc23
-rw-r--r--test/syscalls/linux/socket_netlink_route.cc7
-rw-r--r--test/syscalls/linux/socket_unix.cc7
-rw-r--r--test/syscalls/linux/socket_unix_seqpacket.cc18
-rw-r--r--test/syscalls/linux/socket_unix_stream.cc14
-rw-r--r--test/syscalls/linux/splice.cc49
-rw-r--r--test/syscalls/linux/sticky.cc68
-rw-r--r--test/syscalls/linux/tcp_socket.cc60
-rw-r--r--test/syscalls/linux/timerfd.cc29
-rw-r--r--test/syscalls/linux/tuntap.cc20
-rw-r--r--test/syscalls/linux/udp_socket_errqueue_test_case.cc2
-rw-r--r--test/syscalls/linux/udp_socket_test_cases.cc1464
-rw-r--r--test/syscalls/linux/udp_socket_test_cases.h60
-rw-r--r--test/syscalls/linux/xattr.cc5
-rw-r--r--test/util/fs_util.cc4
-rw-r--r--test/util/fs_util.h7
-rw-r--r--test/util/temp_path.cc2
-rw-r--r--test/util/test_util.cc17
-rw-r--r--test/util/test_util.h24
188 files changed, 14810 insertions, 3850 deletions
diff --git a/test/README.md b/test/README.md
index 97fe7ea04..02bbf42ff 100644
--- a/test/README.md
+++ b/test/README.md
@@ -24,11 +24,11 @@ also used to run these tests in `kokoro`.
To run image and integration tests, run:
-`./scripts/docker_test.sh`
+`./scripts/docker_tests.sh`
To run root tests, run:
-`./scripts/root_test.sh`
+`./scripts/root_tests.sh`
There are a few other interesting variations for image and integration tests:
diff --git a/test/benchmarks/README.md b/test/benchmarks/README.md
new file mode 100644
index 000000000..d1bbabf6f
--- /dev/null
+++ b/test/benchmarks/README.md
@@ -0,0 +1,157 @@
+# Benchmark tools
+
+This package and subpackages are for running macro benchmarks on `runsc`. They
+are meant to replace the previous //benchmarks benchmark-tools written in
+python.
+
+Benchmarks are meant to look like regular golang benchmarks using the testing.B
+library.
+
+## Setup
+
+To run benchmarks you will need:
+
+* Docker installed (17.09.0 or greater).
+
+The easiest way to setup runsc for running benchmarks is to use the make file.
+From the root directory:
+
+* Download images: `make load-all-images`
+* Install runsc suitable for benchmarking, which should probably not have
+ strace or debug logs enabled. For example:`make configure RUNTIME=myrunsc
+ ARGS=--platform=kvm`.
+* Restart docker: `sudo service docker restart`
+
+You should now have a runtime with the following options configured in
+`/etc/docker/daemon.json`
+
+```
+"myrunsc": {
+ "path": "/tmp/myrunsc/runsc",
+ "runtimeArgs": [
+ "--debug-log",
+ "/tmp/bench/logs/runsc.log.%TEST%.%TIMESTAMP%.%COMMAND%",
+ "--platform=kvm"
+ ]
+ },
+
+```
+
+This runtime has been configured with a debugging off and strace logs off and is
+using kvm for demonstration.
+
+## Running benchmarks
+
+Given the runtime above runtime `myrunsc`, run benchmarks with the following:
+
+```
+make sudo TARGETS=//path/to:target ARGS="--runtime=myrunsc -test.v \
+ -test.bench=." OPTIONS="-c opt
+```
+
+For example, to run only the Iperf tests:
+
+```
+make sudo TARGETS=//test/benchmarks/network:network_test \
+ ARGS="--runtime=myrunsc -test.v -test.bench=Iperf" OPTIONS="-c opt"
+```
+
+Benchmarks are run with root as some benchmarks require root privileges to do
+things like drop caches.
+
+## Writing benchmarks
+
+Benchmarks consist of docker images as Dockerfiles and golang testing.B
+benchmarks.
+
+### Dockerfiles:
+
+* Are stored at //images.
+* New Dockerfiles go in an appropriately named directory at
+ `//images/benchmarks/my-cool-dockerfile`.
+* Dockerfiles for benchmarks should:
+ * Use explicitly versioned packages.
+ * Not use ENV and CMD statements...it is easy to add these in the API.
+* Note: A common pattern for getting access to a tmpfs mount is to copy files
+ there after container start. See: //test/benchmarks/build/bazel_test.go. You
+ can also make your own with `RunOpts.Mounts`.
+
+### testing.B packages
+
+In general, benchmarks should look like this:
+
+```golang
+
+var h harness.Harness
+
+func BenchmarkMyCoolOne(b *testing.B) {
+ machine, err := h.GetMachine()
+ // check err
+ defer machine.CleanUp()
+
+ ctx := context.Background()
+ container := machine.GetContainer(ctx, b)
+ defer container.CleanUp(ctx)
+
+ b.ResetTimer()
+
+ //Respect b.N.
+ for i := 0; i < b.N; i++ {
+ out, err := container.Run(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/my-cool-image",
+ Env: []string{"MY_VAR=awesome"},
+ other options...see dockerutil
+ }, "sh", "-c", "echo MY_VAR")
+ //check err
+ b.StopTimer()
+
+ // Do parsing and reporting outside of the timer.
+ number := parseMyMetric(out)
+ b.ReportMetric(number, "my-cool-custom-metric")
+
+ b.StartTimer()
+ }
+}
+
+func TestMain(m *testing.M) {
+ h.Init()
+ os.Exit(m.Run())
+}
+```
+
+Some notes on the above:
+
+* The harness is initiated in the TestMain method and made global to test
+ module. The harness will handle any presetup that needs to happen with
+ flags, remote virtual machines (eventually), and other services.
+* Respect `b.N` in that users of the benchmark may want to "run for an hour"
+ or something of the sort.
+* Use the `b.ReportMetric()` method to report custom metrics.
+* Set the timer if time is useful for reporting. There isn't a way to turn off
+ default metrics in testing.B (B/op, allocs/op, ns/op).
+* Take a look at dockerutil at //pkg/test/dockerutil to see all methods
+ available from containers. The API is based on the "official"
+ [docker API for golang](https://pkg.go.dev/mod/github.com/docker/docker).
+* `harness.GetMachine()` marks how many machines this tests needs. If you have
+ a client and server and to mark them as multiple machines, call
+ `harness.GetMachine()` twice.
+
+## Profiling
+
+For profiling, the runtime is required to have the `--profile` flag enabled.
+This flag loosens seccomp filters so that the runtime can write profile data to
+disk. This configuration is not recommended for production.
+
+* Install runsc with the `--profile` flag: `make configure RUNTIME=myrunsc
+ ARGS="--profile --platform=kvm --vfs2"`. The kvm and vfs2 flags are not
+ required, but are included for demonstration.
+* Restart docker: `sudo service docker restart`
+
+To run and generate CPU profiles fs_test test run:
+
+```
+make sudo TARGETS=//test/benchmarks/fs:fs_test \
+ ARGS="--runtime=myrunsc -test.v -test.bench=. --pprof-cpu" OPTIONS="-c opt"
+```
+
+Profiles would be at: `/tmp/profile/myrunsc/CONTAINERNAME/cpu.pprof`
diff --git a/test/benchmarks/database/BUILD b/test/benchmarks/database/BUILD
new file mode 100644
index 000000000..572db665f
--- /dev/null
+++ b/test/benchmarks/database/BUILD
@@ -0,0 +1,29 @@
+load("//tools:defs.bzl", "go_library", "go_test")
+
+package(licenses = ["notice"])
+
+go_library(
+ name = "database",
+ testonly = 1,
+ srcs = ["database.go"],
+ deps = ["//test/benchmarks/harness"],
+)
+
+go_test(
+ name = "database_test",
+ size = "enormous",
+ srcs = [
+ "redis_test.go",
+ ],
+ library = ":database",
+ tags = [
+ # Requires docker and runsc to be configured before test runs.
+ "manual",
+ "local",
+ ],
+ deps = [
+ "//pkg/test/dockerutil",
+ "//test/benchmarks/harness",
+ "//test/benchmarks/tools",
+ ],
+)
diff --git a/test/benchmarks/database/database.go b/test/benchmarks/database/database.go
new file mode 100644
index 000000000..9eeb59f9a
--- /dev/null
+++ b/test/benchmarks/database/database.go
@@ -0,0 +1,31 @@
+// Copyright 2020 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 database holds benchmarks around database applications.
+package database
+
+import (
+ "os"
+ "testing"
+
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+)
+
+var h harness.Harness
+
+// TestMain is the main method for package database.
+func TestMain(m *testing.M) {
+ h.Init()
+ os.Exit(m.Run())
+}
diff --git a/test/benchmarks/database/redis_test.go b/test/benchmarks/database/redis_test.go
new file mode 100644
index 000000000..394fce820
--- /dev/null
+++ b/test/benchmarks/database/redis_test.go
@@ -0,0 +1,123 @@
+// Copyright 2020 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 database
+
+import (
+ "context"
+ "testing"
+ "time"
+
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+ "gvisor.dev/gvisor/test/benchmarks/tools"
+)
+
+// All possible operations from redis. Note: "ping" will
+// run both PING_INLINE and PING_BUILD.
+var operations []string = []string{
+ "PING_INLINE",
+ "PING_BULK",
+ "SET",
+ "GET",
+ "INCR",
+ "LPUSH",
+ "RPUSH",
+ "LPOP",
+ "RPOP",
+ "SADD",
+ "HSET",
+ "SPOP",
+ "LRANGE_100",
+ "LRANGE_300",
+ "LRANGE_500",
+ "LRANGE_600",
+ "MSET",
+}
+
+// BenchmarkRedis runs redis-benchmark against a redis instance and reports
+// data in queries per second. Each is reported by named operation (e.g. LPUSH).
+func BenchmarkRedis(b *testing.B) {
+ clientMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get machine: %v", err)
+ }
+ defer clientMachine.CleanUp()
+
+ serverMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get machine: %v", err)
+ }
+ defer serverMachine.CleanUp()
+
+ // Redis runs on port 6379 by default.
+ port := 6379
+ ctx := context.Background()
+
+ for _, operation := range operations {
+ b.Run(operation, func(b *testing.B) {
+ server := serverMachine.GetContainer(ctx, b)
+ defer server.CleanUp(ctx)
+
+ // The redis docker container takes no arguments to run a redis server.
+ if err := server.Spawn(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/redis",
+ Ports: []int{port},
+ }); err != nil {
+ b.Fatalf("failed to start redis server with: %v", err)
+ }
+
+ if out, err := server.WaitForOutput(ctx, "Ready to accept connections", 3*time.Second); err != nil {
+ b.Fatalf("failed to start redis server: %v %s", err, out)
+ }
+
+ ip, err := serverMachine.IPAddress()
+ if err != nil {
+ b.Fatal("failed to get IP from server: %v", err)
+ }
+
+ serverPort, err := server.FindPort(ctx, port)
+ if err != nil {
+ b.Fatal("failed to get IP from server: %v", err)
+ }
+
+ if err = harness.WaitUntilServing(ctx, clientMachine, ip, serverPort); err != nil {
+ b.Fatalf("failed to start redis with: %v", err)
+ }
+
+ redis := tools.Redis{
+ Operation: operation,
+ }
+
+ // Reset profiles and timer to begin the measurement.
+ server.RestartProfiles()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ client := clientMachine.GetNativeContainer(ctx, b)
+ defer client.CleanUp(ctx)
+ out, err := client.Run(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/redis",
+ }, redis.MakeCmd(ip, serverPort)...)
+ if err != nil {
+ b.Fatalf("redis-benchmark failed with: %v", err)
+ }
+
+ // Stop time while we parse results.
+ b.StopTimer()
+ redis.Report(b, out)
+ b.StartTimer()
+ }
+ })
+ }
+}
diff --git a/test/benchmarks/fs/BUILD b/test/benchmarks/fs/BUILD
new file mode 100644
index 000000000..20654d88f
--- /dev/null
+++ b/test/benchmarks/fs/BUILD
@@ -0,0 +1,31 @@
+load("//tools:defs.bzl", "go_library", "go_test")
+
+package(licenses = ["notice"])
+
+go_library(
+ name = "fs",
+ testonly = 1,
+ srcs = ["fs.go"],
+ deps = ["//test/benchmarks/harness"],
+)
+
+go_test(
+ name = "fs_test",
+ size = "large",
+ srcs = [
+ "bazel_test.go",
+ "fio_test.go",
+ ],
+ library = ":fs",
+ tags = [
+ # Requires docker and runsc to be configured before test runs.
+ "local",
+ "manual",
+ ],
+ deps = [
+ "//pkg/test/dockerutil",
+ "//test/benchmarks/harness",
+ "//test/benchmarks/tools",
+ "@com_github_docker_docker//api/types/mount:go_default_library",
+ ],
+)
diff --git a/test/benchmarks/fs/bazel_test.go b/test/benchmarks/fs/bazel_test.go
new file mode 100644
index 000000000..f4236ba37
--- /dev/null
+++ b/test/benchmarks/fs/bazel_test.go
@@ -0,0 +1,119 @@
+// Copyright 2020 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 fs
+
+import (
+ "context"
+ "fmt"
+ "strings"
+ "testing"
+
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+)
+
+// Note: CleanCache versions of this test require running with root permissions.
+func BenchmarkBuildABSL(b *testing.B) {
+ runBuildBenchmark(b, "benchmarks/absl", "/abseil-cpp", "absl/base/...")
+}
+
+// Note: CleanCache versions of this test require running with root permissions.
+// Note: This test takes on the order of 10m per permutation for runsc on kvm.
+func BenchmarkBuildRunsc(b *testing.B) {
+ runBuildBenchmark(b, "benchmarks/runsc", "/gvisor", "runsc:runsc")
+}
+
+func runBuildBenchmark(b *testing.B, image, workdir, target string) {
+ b.Helper()
+ // Get a machine from the Harness on which to run.
+ machine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get machine: %v", err)
+ }
+ defer machine.CleanUp()
+
+ // Dimensions here are clean/dirty cache (do or don't drop caches)
+ // and if the mount on which we are compiling is a tmpfs/bind mount.
+ benchmarks := []struct {
+ name string
+ clearCache bool // clearCache drops caches before running.
+ tmpfs bool // tmpfs will run compilation on a tmpfs.
+ }{
+ {name: "CleanCache", clearCache: true, tmpfs: false},
+ {name: "DirtyCache", clearCache: false, tmpfs: false},
+ {name: "CleanCacheTmpfs", clearCache: true, tmpfs: true},
+ {name: "DirtyCacheTmpfs", clearCache: false, tmpfs: true},
+ }
+ for _, bm := range benchmarks {
+ b.Run(bm.name, func(b *testing.B) {
+ // Grab a container.
+ ctx := context.Background()
+ container := machine.GetContainer(ctx, b)
+ defer container.CleanUp(ctx)
+
+ // Start a container and sleep by an order of b.N.
+ if err := container.Spawn(ctx, dockerutil.RunOpts{
+ Image: image,
+ }, "sleep", fmt.Sprintf("%d", 1000000)); err != nil {
+ b.Fatalf("run failed with: %v", err)
+ }
+
+ // If we are running on a tmpfs, copy to /tmp which is a tmpfs.
+ if bm.tmpfs {
+ if out, err := container.Exec(ctx, dockerutil.ExecOpts{},
+ "cp", "-r", workdir, "/tmp/."); err != nil {
+ b.Fatal("failed to copy directory: %v %s", err, out)
+ }
+ workdir = "/tmp" + workdir
+ }
+
+ // Restart profiles after the copy.
+ container.RestartProfiles()
+ b.ResetTimer()
+ // Drop Caches and bazel clean should happen inside the loop as we may use
+ // time options with b.N. (e.g. Run for an hour.)
+ for i := 0; i < b.N; i++ {
+ b.StopTimer()
+ // Drop Caches for clear cache runs.
+ if bm.clearCache {
+ if err := harness.DropCaches(machine); err != nil {
+ b.Skipf("failed to drop caches: %v. You probably need root.", err)
+ }
+ }
+ b.StartTimer()
+
+ got, err := container.Exec(ctx, dockerutil.ExecOpts{
+ WorkDir: workdir,
+ }, "bazel", "build", "-c", "opt", target)
+ if err != nil {
+ b.Fatalf("build failed with: %v", err)
+ }
+ b.StopTimer()
+
+ want := "Build completed successfully"
+ if !strings.Contains(got, want) {
+ b.Fatalf("string %s not in: %s", want, got)
+ }
+ // Clean bazel in case we use b.N.
+ _, err = container.Exec(ctx, dockerutil.ExecOpts{
+ WorkDir: workdir,
+ }, "bazel", "clean")
+ if err != nil {
+ b.Fatalf("build failed with: %v", err)
+ }
+ b.StartTimer()
+ }
+ })
+ }
+}
diff --git a/test/benchmarks/fs/fio_test.go b/test/benchmarks/fs/fio_test.go
new file mode 100644
index 000000000..65874ed8b
--- /dev/null
+++ b/test/benchmarks/fs/fio_test.go
@@ -0,0 +1,170 @@
+// Copyright 2020 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 fs
+
+import (
+ "context"
+ "fmt"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "github.com/docker/docker/api/types/mount"
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+ "gvisor.dev/gvisor/test/benchmarks/tools"
+)
+
+// BenchmarkFio runs fio on the runtime under test. There are 4 basic test
+// cases each run on a tmpfs mount and a bind mount. Fio requires root so that
+// caches can be dropped.
+func BenchmarkFio(b *testing.B) {
+ testCases := []tools.Fio{
+ tools.Fio{
+ Test: "write",
+ Size: "5G",
+ Blocksize: "1M",
+ Iodepth: 4,
+ },
+ tools.Fio{
+ Test: "read",
+ Size: "5G",
+ Blocksize: "1M",
+ Iodepth: 4,
+ },
+ tools.Fio{
+ Test: "randwrite",
+ Size: "5G",
+ Blocksize: "4K",
+ Iodepth: 4,
+ Time: 30,
+ },
+ tools.Fio{
+ Test: "randread",
+ Size: "5G",
+ Blocksize: "4K",
+ Iodepth: 4,
+ Time: 30,
+ },
+ }
+
+ machine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get machine with: %v", err)
+ }
+ defer machine.CleanUp()
+
+ for _, fsType := range []mount.Type{mount.TypeBind, mount.TypeTmpfs} {
+ for _, tc := range testCases {
+ testName := strings.Title(tc.Test) + strings.Title(string(fsType))
+ b.Run(testName, func(b *testing.B) {
+ ctx := context.Background()
+ container := machine.GetContainer(ctx, b)
+ defer container.CleanUp(ctx)
+
+ // Directory and filename inside container where fio will read/write.
+ outdir := "/data"
+ outfile := filepath.Join(outdir, "test.txt")
+
+ // Make the required mount and grab a cleanup for bind mounts
+ // as they are backed by a temp directory (mktemp).
+ mnt, mountCleanup, err := makeMount(machine, fsType, outdir)
+ if err != nil {
+ b.Fatalf("failed to make mount: %v", err)
+ }
+ defer mountCleanup()
+
+ // Start the container with the mount.
+ if err := container.Spawn(
+ ctx,
+ dockerutil.RunOpts{
+ Image: "benchmarks/fio",
+ Mounts: []mount.Mount{
+ mnt,
+ },
+ },
+ // Sleep on the order of b.N.
+ "sleep", fmt.Sprintf("%d", 1000*b.N),
+ ); err != nil {
+ b.Fatalf("failed to start fio container with: %v", err)
+ }
+
+ // For reads, we need a file to read so make one inside the container.
+ if strings.Contains(tc.Test, "read") {
+ fallocateCmd := fmt.Sprintf("fallocate -l %s %s", tc.Size, outfile)
+ if out, err := container.Exec(ctx, dockerutil.ExecOpts{},
+ strings.Split(fallocateCmd, " ")...); err != nil {
+ b.Fatalf("failed to create readable file on mount: %v, %s", err, out)
+ }
+ }
+
+ // Drop caches just before running.
+ if err := harness.DropCaches(machine); err != nil {
+ b.Skipf("failed to drop caches with %v. You probably need root.", err)
+ }
+ cmd := tc.MakeCmd(outfile)
+ container.RestartProfiles()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ // Run fio.
+ data, err := container.Exec(ctx, dockerutil.ExecOpts{}, cmd...)
+ if err != nil {
+ b.Fatalf("failed to run cmd %v: %v", cmd, err)
+ }
+ b.StopTimer()
+ tc.Report(b, data)
+ // If b.N is used (i.e. we run for an hour), we should drop caches
+ // after each run.
+ if err := harness.DropCaches(machine); err != nil {
+ b.Fatalf("failed to drop caches: %v", err)
+ }
+ b.StartTimer()
+ }
+ })
+ }
+ }
+}
+
+// makeMount makes a mount and cleanup based on the requested type. Bind
+// and volume mounts are backed by a temp directory made with mktemp.
+// tmpfs mounts require no such backing and are just made.
+// It is up to the caller to call the returned cleanup.
+func makeMount(machine harness.Machine, mountType mount.Type, target string) (mount.Mount, func(), error) {
+ switch mountType {
+ case mount.TypeVolume, mount.TypeBind:
+ dir, err := machine.RunCommand("mktemp", "-d")
+ if err != nil {
+ return mount.Mount{}, func() {}, fmt.Errorf("failed to create tempdir: %v", err)
+ }
+ dir = strings.TrimSuffix(dir, "\n")
+
+ out, err := machine.RunCommand("chmod", "777", dir)
+ if err != nil {
+ machine.RunCommand("rm", "-rf", dir)
+ return mount.Mount{}, func() {}, fmt.Errorf("failed modify directory: %v %s", err, out)
+ }
+ return mount.Mount{
+ Target: target,
+ Source: dir,
+ Type: mount.TypeBind,
+ }, func() { machine.RunCommand("rm", "-rf", dir) }, nil
+ case mount.TypeTmpfs:
+ return mount.Mount{
+ Target: target,
+ Type: mount.TypeTmpfs,
+ }, func() {}, nil
+ default:
+ return mount.Mount{}, func() {}, fmt.Errorf("illegal mount time not supported: %v", mountType)
+ }
+}
diff --git a/test/benchmarks/fs/fs.go b/test/benchmarks/fs/fs.go
new file mode 100644
index 000000000..e5ca28c3b
--- /dev/null
+++ b/test/benchmarks/fs/fs.go
@@ -0,0 +1,31 @@
+// Copyright 2020 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 fs holds benchmarks around filesystem performance.
+package fs
+
+import (
+ "os"
+ "testing"
+
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+)
+
+var h harness.Harness
+
+// TestMain is the main method for package fs.
+func TestMain(m *testing.M) {
+ h.Init()
+ os.Exit(m.Run())
+}
diff --git a/test/benchmarks/harness/BUILD b/test/benchmarks/harness/BUILD
new file mode 100644
index 000000000..c2e316709
--- /dev/null
+++ b/test/benchmarks/harness/BUILD
@@ -0,0 +1,18 @@
+load("//tools:defs.bzl", "go_library")
+
+package(licenses = ["notice"])
+
+go_library(
+ name = "harness",
+ testonly = 1,
+ srcs = [
+ "harness.go",
+ "machine.go",
+ "util.go",
+ ],
+ visibility = ["//:sandbox"],
+ deps = [
+ "//pkg/test/dockerutil",
+ "//pkg/test/testutil",
+ ],
+)
diff --git a/test/benchmarks/harness/harness.go b/test/benchmarks/harness/harness.go
new file mode 100644
index 000000000..68bd7b4cf
--- /dev/null
+++ b/test/benchmarks/harness/harness.go
@@ -0,0 +1,38 @@
+// Copyright 2020 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 harness holds utility code for running benchmarks on Docker.
+package harness
+
+import (
+ "flag"
+
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+)
+
+// Harness is a handle for managing state in benchmark runs.
+type Harness struct {
+}
+
+// Init performs any harness initilialization before runs.
+func (h *Harness) Init() error {
+ flag.Parse()
+ dockerutil.EnsureSupportedDockerVersion()
+ return nil
+}
+
+// GetMachine returns this run's implementation of machine.
+func (h *Harness) GetMachine() (Machine, error) {
+ return &localMachine{}, nil
+}
diff --git a/test/benchmarks/harness/machine.go b/test/benchmarks/harness/machine.go
new file mode 100644
index 000000000..88e5e841b
--- /dev/null
+++ b/test/benchmarks/harness/machine.go
@@ -0,0 +1,81 @@
+// Copyright 2020 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 harness
+
+import (
+ "context"
+ "net"
+ "os/exec"
+
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/pkg/test/testutil"
+)
+
+// Machine describes a real machine for use in benchmarks.
+type Machine interface {
+ // GetContainer gets a container from the machine. The container uses the
+ // runtime under test and is profiled if requested by flags.
+ GetContainer(ctx context.Context, log testutil.Logger) *dockerutil.Container
+
+ // GetNativeContainer gets a native container from the machine. Native containers
+ // use runc by default and are not profiled.
+ GetNativeContainer(ctx context.Context, log testutil.Logger) *dockerutil.Container
+
+ // RunCommand runs cmd on this machine.
+ RunCommand(cmd string, args ...string) (string, error)
+
+ // Returns IP Address for the machine.
+ IPAddress() (net.IP, error)
+
+ // CleanUp cleans up this machine.
+ CleanUp()
+}
+
+// localMachine describes this machine.
+type localMachine struct {
+}
+
+// GetContainer implements Machine.GetContainer for localMachine.
+func (l *localMachine) GetContainer(ctx context.Context, logger testutil.Logger) *dockerutil.Container {
+ return dockerutil.MakeContainer(ctx, logger)
+}
+
+// GetContainer implements Machine.GetContainer for localMachine.
+func (l *localMachine) GetNativeContainer(ctx context.Context, logger testutil.Logger) *dockerutil.Container {
+ return dockerutil.MakeNativeContainer(ctx, logger)
+}
+
+// RunCommand implements Machine.RunCommand for localMachine.
+func (l *localMachine) RunCommand(cmd string, args ...string) (string, error) {
+ c := exec.Command(cmd, args...)
+ out, err := c.CombinedOutput()
+ return string(out), err
+}
+
+// IPAddress implements Machine.IPAddress.
+func (l *localMachine) IPAddress() (net.IP, error) {
+ conn, err := net.Dial("udp", "8.8.8.8:80")
+ if err != nil {
+ return nil, err
+ }
+ defer conn.Close()
+
+ addr := conn.LocalAddr().(*net.UDPAddr)
+ return addr.IP, nil
+}
+
+// CleanUp implements Machine.CleanUp and does nothing for localMachine.
+func (*localMachine) CleanUp() {
+}
diff --git a/test/benchmarks/harness/util.go b/test/benchmarks/harness/util.go
new file mode 100644
index 000000000..bc551c582
--- /dev/null
+++ b/test/benchmarks/harness/util.go
@@ -0,0 +1,46 @@
+// Copyright 2020 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 harness
+
+import (
+ "context"
+ "fmt"
+ "net"
+
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/pkg/test/testutil"
+)
+
+// WaitUntilServing grabs a container from `machine` and waits for a server at
+// IP:port.
+func WaitUntilServing(ctx context.Context, machine Machine, server net.IP, port int) error {
+ var logger testutil.DefaultLogger = "netcat"
+ netcat := machine.GetNativeContainer(ctx, logger)
+ defer netcat.CleanUp(ctx)
+
+ cmd := fmt.Sprintf("while ! nc -zv %s %d; do true; done", server, port)
+ _, err := netcat.Run(ctx, dockerutil.RunOpts{
+ Image: "packetdrill",
+ }, "sh", "-c", cmd)
+ return err
+}
+
+// DropCaches drops caches on the provided machine. Requires root.
+func DropCaches(machine Machine) error {
+ if out, err := machine.RunCommand("/bin/sh", "-c", "sync | sysctl vm.drop_caches=3"); err != nil {
+ return fmt.Errorf("failed to drop caches: %v logs: %s", err, out)
+ }
+ return nil
+}
diff --git a/test/benchmarks/media/BUILD b/test/benchmarks/media/BUILD
new file mode 100644
index 000000000..6c41fc4f6
--- /dev/null
+++ b/test/benchmarks/media/BUILD
@@ -0,0 +1,21 @@
+load("//tools:defs.bzl", "go_library", "go_test")
+
+package(licenses = ["notice"])
+
+go_library(
+ name = "media",
+ testonly = 1,
+ srcs = ["media.go"],
+ deps = ["//test/benchmarks/harness"],
+)
+
+go_test(
+ name = "media_test",
+ size = "large",
+ srcs = ["ffmpeg_test.go"],
+ library = ":media",
+ deps = [
+ "//pkg/test/dockerutil",
+ "//test/benchmarks/harness",
+ ],
+)
diff --git a/test/benchmarks/media/ffmpeg_test.go b/test/benchmarks/media/ffmpeg_test.go
new file mode 100644
index 000000000..7822dfad7
--- /dev/null
+++ b/test/benchmarks/media/ffmpeg_test.go
@@ -0,0 +1,53 @@
+// Copyright 2020 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 media
+
+import (
+ "context"
+ "strings"
+ "testing"
+
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+)
+
+// BenchmarkFfmpeg runs ffmpeg in a container and records runtime.
+// BenchmarkFfmpeg should run as root to drop caches.
+func BenchmarkFfmpeg(b *testing.B) {
+ machine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get machine: %v", err)
+ }
+ defer machine.CleanUp()
+
+ ctx := context.Background()
+ container := machine.GetContainer(ctx, b)
+ defer container.CleanUp(ctx)
+ cmd := strings.Split("ffmpeg -i video.mp4 -c:v libx264 -preset veryslow output.mp4", " ")
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ b.StopTimer()
+ if err := harness.DropCaches(machine); err != nil {
+ b.Skipf("failed to drop caches: %v. You probably need root.", err)
+ }
+ b.StartTimer()
+
+ if _, err := container.Run(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/ffmpeg",
+ }, cmd...); err != nil {
+ b.Fatalf("failed to run container: %v", err)
+ }
+ }
+}
diff --git a/test/benchmarks/media/media.go b/test/benchmarks/media/media.go
new file mode 100644
index 000000000..c7b35b758
--- /dev/null
+++ b/test/benchmarks/media/media.go
@@ -0,0 +1,31 @@
+// Copyright 2020 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 media holds benchmarks around media processing applications.
+package media
+
+import (
+ "os"
+ "testing"
+
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+)
+
+var h harness.Harness
+
+// TestMain is the main method for package media.
+func TestMain(m *testing.M) {
+ h.Init()
+ os.Exit(m.Run())
+}
diff --git a/test/benchmarks/ml/BUILD b/test/benchmarks/ml/BUILD
new file mode 100644
index 000000000..2430b60a7
--- /dev/null
+++ b/test/benchmarks/ml/BUILD
@@ -0,0 +1,21 @@
+load("//tools:defs.bzl", "go_library", "go_test")
+
+package(licenses = ["notice"])
+
+go_library(
+ name = "ml",
+ testonly = 1,
+ srcs = ["ml.go"],
+ deps = ["//test/benchmarks/harness"],
+)
+
+go_test(
+ name = "ml_test",
+ size = "large",
+ srcs = ["tensorflow_test.go"],
+ library = ":ml",
+ deps = [
+ "//pkg/test/dockerutil",
+ "//test/benchmarks/harness",
+ ],
+)
diff --git a/test/benchmarks/ml/ml.go b/test/benchmarks/ml/ml.go
new file mode 100644
index 000000000..13282d7bb
--- /dev/null
+++ b/test/benchmarks/ml/ml.go
@@ -0,0 +1,31 @@
+// Copyright 2020 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 ml holds benchmarks around machine learning performance.
+package ml
+
+import (
+ "os"
+ "testing"
+
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+)
+
+var h harness.Harness
+
+// TestMain is the main method for package ml.
+func TestMain(m *testing.M) {
+ h.Init()
+ os.Exit(m.Run())
+}
diff --git a/test/benchmarks/ml/tensorflow_test.go b/test/benchmarks/ml/tensorflow_test.go
new file mode 100644
index 000000000..f7746897d
--- /dev/null
+++ b/test/benchmarks/ml/tensorflow_test.go
@@ -0,0 +1,69 @@
+// Copyright 2020 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 ml
+
+import (
+ "context"
+ "testing"
+
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+)
+
+// BenchmarkTensorflow runs workloads from a TensorFlow tutorial.
+// See: https://github.com/aymericdamien/TensorFlow-Examples
+func BenchmarkTensorflow(b *testing.B) {
+ workloads := map[string]string{
+ "GradientDecisionTree": "2_BasicModels/gradient_boosted_decision_tree.py",
+ "Kmeans": "2_BasicModels/kmeans.py",
+ "LogisticRegression": "2_BasicModels/logistic_regression.py",
+ "NearestNeighbor": "2_BasicModels/nearest_neighbor.py",
+ "RandomForest": "2_BasicModels/random_forest.py",
+ "ConvolutionalNetwork": "3_NeuralNetworks/convolutional_network.py",
+ "MultilayerPerceptron": "3_NeuralNetworks/multilayer_perceptron.py",
+ "NeuralNetwork": "3_NeuralNetworks/neural_network.py",
+ }
+
+ machine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get machine: %v", err)
+ }
+ defer machine.CleanUp()
+
+ for name, workload := range workloads {
+ b.Run(name, func(b *testing.B) {
+ ctx := context.Background()
+ container := machine.GetContainer(ctx, b)
+ defer container.CleanUp(ctx)
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ b.StopTimer()
+ if err := harness.DropCaches(machine); err != nil {
+ b.Skipf("failed to drop caches: %v. You probably need root.", err)
+ }
+ b.StartTimer()
+
+ if out, err := container.Run(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/tensorflow",
+ Env: []string{"PYTHONPATH=$PYTHONPATH:/TensorFlow-Examples/examples"},
+ WorkDir: "/TensorFlow-Examples/examples",
+ }, "python", workload); err != nil {
+ b.Fatalf("failed to run container: %v logs: %s", err, out)
+ }
+ }
+ })
+ }
+
+}
diff --git a/test/benchmarks/network/BUILD b/test/benchmarks/network/BUILD
new file mode 100644
index 000000000..d15cd55ee
--- /dev/null
+++ b/test/benchmarks/network/BUILD
@@ -0,0 +1,33 @@
+load("//tools:defs.bzl", "go_library", "go_test")
+
+package(licenses = ["notice"])
+
+go_library(
+ name = "network",
+ testonly = 1,
+ srcs = ["network.go"],
+ deps = ["//test/benchmarks/harness"],
+)
+
+go_test(
+ name = "network_test",
+ size = "large",
+ srcs = [
+ "httpd_test.go",
+ "iperf_test.go",
+ "nginx_test.go",
+ "node_test.go",
+ ],
+ library = ":network",
+ tags = [
+ # Requires docker and runsc to be configured before test runs.
+ "manual",
+ "local",
+ ],
+ deps = [
+ "//pkg/test/dockerutil",
+ "//pkg/test/testutil",
+ "//test/benchmarks/harness",
+ "//test/benchmarks/tools",
+ ],
+)
diff --git a/test/benchmarks/network/httpd_test.go b/test/benchmarks/network/httpd_test.go
new file mode 100644
index 000000000..21cb0b804
--- /dev/null
+++ b/test/benchmarks/network/httpd_test.go
@@ -0,0 +1,181 @@
+// Copyright 2020 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 network
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+ "gvisor.dev/gvisor/test/benchmarks/tools"
+)
+
+// see Dockerfile '//images/benchmarks/httpd'.
+var docs = map[string]string{
+ "notfound": "notfound",
+ "1Kb": "latin1k.txt",
+ "10Kb": "latin10k.txt",
+ "100Kb": "latin100k.txt",
+ "1000Kb": "latin1000k.txt",
+ "1Mb": "latin1024k.txt",
+ "10Mb": "latin10240k.txt",
+}
+
+// BenchmarkHttpdConcurrency iterates the concurrency argument and tests
+// how well the runtime under test handles requests in parallel.
+func BenchmarkHttpdConcurrency(b *testing.B) {
+ // Grab a machine for the client and server.
+ clientMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get client: %v", err)
+ }
+ defer clientMachine.CleanUp()
+
+ serverMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get server: %v", err)
+ }
+ defer serverMachine.CleanUp()
+
+ // The test iterates over client concurrency, so set other parameters.
+ concurrency := []int{1, 25, 50, 100, 1000}
+
+ for _, c := range concurrency {
+ b.Run(fmt.Sprintf("%d", c), func(b *testing.B) {
+ hey := &tools.Hey{
+ Requests: 10000,
+ Concurrency: c,
+ Doc: docs["10Kb"],
+ }
+ runHttpd(b, clientMachine, serverMachine, hey, false /* reverse */)
+ })
+ }
+}
+
+// BenchmarkHttpdDocSize iterates over different sized payloads, testing how
+// well the runtime handles sending different payload sizes.
+func BenchmarkHttpdDocSize(b *testing.B) {
+ benchmarkHttpDocSize(b, false /* reverse */)
+}
+
+// BenchmarkReverseHttpdDocSize iterates over different sized payloads, testing
+// how well the runtime handles receiving different payload sizes.
+func BenchmarkReverseHttpdDocSize(b *testing.B) {
+ benchmarkHttpDocSize(b, true /* reverse */)
+}
+
+func benchmarkHttpdDocSize(b *testing.B, reverse bool) {
+ b.Helper()
+
+ clientMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get machine: %v", err)
+ }
+ defer clientMachine.CleanUp()
+
+ serverMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get machine: %v", err)
+ }
+ defer serverMachine.CleanUp()
+
+ for name, filename := range docs {
+ concurrency := []int{1, 25, 50, 100, 1000}
+ for _, c := range concurrency {
+ b.Run(fmt.Sprintf("%s_%d", name, c), func(b *testing.B) {
+ hey := &tools.Hey{
+ Requests: 10000,
+ Concurrency: c,
+ Doc: filename,
+ }
+ runHttpd(b, clientMachine, serverMachine, hey, reverse)
+ })
+ }
+ }
+}
+
+// runHttpd runs a single test run.
+func runHttpd(b *testing.B, clientMachine, serverMachine harness.Machine, hey *tools.Hey, reverse bool) {
+ b.Helper()
+
+ // Grab a container from the server.
+ ctx := context.Background()
+ var server *dockerutil.Container
+ if reverse {
+ server = serverMachine.GetNativeContainer(ctx, b)
+ } else {
+ server = serverMachine.GetContainer(ctx, b)
+ }
+
+ defer server.CleanUp(ctx)
+
+ // Copy the docs to /tmp and serve from there.
+ cmd := "mkdir -p /tmp/html; cp -r /local/* /tmp/html/.; apache2 -X"
+ port := 80
+
+ // Start the server.
+ if err := server.Spawn(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/httpd",
+ Ports: []int{port},
+ Env: []string{
+ // Standard environmental variables for httpd.
+ "APACHE_RUN_DIR=/tmp",
+ "APACHE_RUN_USER=nobody",
+ "APACHE_RUN_GROUP=nogroup",
+ "APACHE_LOG_DIR=/tmp",
+ "APACHE_PID_FILE=/tmp/apache.pid",
+ },
+ }, "sh", "-c", cmd); err != nil {
+ b.Fatalf("failed to start server: %v", err)
+ }
+
+ ip, err := serverMachine.IPAddress()
+ if err != nil {
+ b.Fatalf("failed to find server ip: %v", err)
+ }
+
+ servingPort, err := server.FindPort(ctx, port)
+ if err != nil {
+ b.Fatalf("failed to find server port %d: %v", port, err)
+ }
+
+ // Check the server is serving.
+ harness.WaitUntilServing(ctx, clientMachine, ip, servingPort)
+
+ var client *dockerutil.Container
+ // Grab a client.
+ if reverse {
+ client = clientMachine.GetContainer(ctx, b)
+ } else {
+ client = clientMachine.GetNativeContainer(ctx, b)
+ }
+ defer client.CleanUp(ctx)
+
+ b.ResetTimer()
+ server.RestartProfiles()
+ for i := 0; i < b.N; i++ {
+ out, err := client.Run(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/hey",
+ }, hey.MakeCmd(ip, servingPort)...)
+ if err != nil {
+ b.Fatalf("run failed with: %v", err)
+ }
+
+ b.StopTimer()
+ hey.Report(b, out)
+ b.StartTimer()
+ }
+}
diff --git a/test/benchmarks/network/iperf_test.go b/test/benchmarks/network/iperf_test.go
new file mode 100644
index 000000000..b8ab7dfb8
--- /dev/null
+++ b/test/benchmarks/network/iperf_test.go
@@ -0,0 +1,113 @@
+// Copyright 2020 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 network
+
+import (
+ "context"
+ "testing"
+
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/pkg/test/testutil"
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+ "gvisor.dev/gvisor/test/benchmarks/tools"
+)
+
+func BenchmarkIperf(b *testing.B) {
+ iperf := tools.Iperf{
+ Time: 10, // time in seconds to run client.
+ }
+
+ clientMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get machine: %v", err)
+ }
+ defer clientMachine.CleanUp()
+
+ serverMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get machine: %v", err)
+ }
+ defer serverMachine.CleanUp()
+ ctx := context.Background()
+ for _, bm := range []struct {
+ name string
+ clientFunc func(context.Context, testutil.Logger) *dockerutil.Container
+ serverFunc func(context.Context, testutil.Logger) *dockerutil.Container
+ }{
+ // We are either measuring the server or the client. The other should be
+ // runc. e.g. Upload sees how fast the runtime under test uploads to a native
+ // server.
+ {
+ name: "Upload",
+ clientFunc: clientMachine.GetContainer,
+ serverFunc: serverMachine.GetNativeContainer,
+ },
+ {
+ name: "Download",
+ clientFunc: clientMachine.GetNativeContainer,
+ serverFunc: serverMachine.GetContainer,
+ },
+ } {
+ b.Run(bm.name, func(b *testing.B) {
+ // Set up the containers.
+ server := bm.serverFunc(ctx, b)
+ defer server.CleanUp(ctx)
+ client := bm.clientFunc(ctx, b)
+ defer client.CleanUp(ctx)
+
+ // iperf serves on port 5001 by default.
+ port := 5001
+
+ // Start the server.
+ if err := server.Spawn(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/iperf",
+ Ports: []int{port},
+ }, "iperf", "-s"); err != nil {
+ b.Fatalf("failed to start server with: %v", err)
+ }
+
+ ip, err := serverMachine.IPAddress()
+ if err != nil {
+ b.Fatalf("failed to find server ip: %v", err)
+ }
+
+ servingPort, err := server.FindPort(ctx, port)
+ if err != nil {
+ b.Fatalf("failed to find port %d: %v", port, err)
+ }
+
+ // Make sure the server is up and serving before we run.
+ if err := harness.WaitUntilServing(ctx, clientMachine, ip, servingPort); err != nil {
+ b.Fatalf("failed to wait for server: %v", err)
+ }
+ // Run the client.
+ b.ResetTimer()
+
+ // Restart the server profiles. If the server isn't being profiled
+ // this does nothing.
+ server.RestartProfiles()
+ for i := 0; i < b.N; i++ {
+ out, err := client.Run(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/iperf",
+ }, iperf.MakeCmd(ip, servingPort)...)
+ if err != nil {
+ b.Fatalf("failed to run client: %v", err)
+ }
+ b.StopTimer()
+ iperf.Report(b, out)
+ b.StartTimer()
+ }
+ })
+ }
+}
diff --git a/test/benchmarks/network/network.go b/test/benchmarks/network/network.go
new file mode 100644
index 000000000..ce17ddb94
--- /dev/null
+++ b/test/benchmarks/network/network.go
@@ -0,0 +1,31 @@
+// Copyright 2020 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 network holds benchmarks around raw network performance.
+package network
+
+import (
+ "os"
+ "testing"
+
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+)
+
+var h harness.Harness
+
+// TestMain is the main method for package network.
+func TestMain(m *testing.M) {
+ h.Init()
+ os.Exit(m.Run())
+}
diff --git a/test/benchmarks/network/nginx_test.go b/test/benchmarks/network/nginx_test.go
new file mode 100644
index 000000000..5965652a5
--- /dev/null
+++ b/test/benchmarks/network/nginx_test.go
@@ -0,0 +1,104 @@
+// Copyright 2020 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 network
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+ "gvisor.dev/gvisor/test/benchmarks/tools"
+)
+
+// BenchmarkNginxConcurrency iterates the concurrency argument and tests
+// how well the runtime under test handles requests in parallel.
+// TODO(zkoopmans): Update with different doc sizes like Httpd.
+func BenchmarkNginxConcurrency(b *testing.B) {
+ // Grab a machine for the client and server.
+ clientMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get client: %v", err)
+ }
+ defer clientMachine.CleanUp()
+
+ serverMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get server: %v", err)
+ }
+ defer serverMachine.CleanUp()
+
+ concurrency := []int{1, 5, 10, 25}
+ for _, c := range concurrency {
+ b.Run(fmt.Sprintf("%d", c), func(b *testing.B) {
+ hey := &tools.Hey{
+ Requests: 10000,
+ Concurrency: c,
+ }
+ runNginx(b, clientMachine, serverMachine, hey)
+ })
+ }
+}
+
+// runHttpd runs a single test run.
+func runNginx(b *testing.B, clientMachine, serverMachine harness.Machine, hey *tools.Hey) {
+ b.Helper()
+
+ // Grab a container from the server.
+ ctx := context.Background()
+ server := serverMachine.GetContainer(ctx, b)
+ defer server.CleanUp(ctx)
+
+ port := 80
+ // Start the server.
+ if err := server.Spawn(ctx,
+ dockerutil.RunOpts{
+ Image: "benchmarks/nginx",
+ Ports: []int{port},
+ }); err != nil {
+ b.Fatalf("server failed to start: %v", err)
+ }
+
+ ip, err := serverMachine.IPAddress()
+ if err != nil {
+ b.Fatalf("failed to find server ip: %v", err)
+ }
+
+ servingPort, err := server.FindPort(ctx, port)
+ if err != nil {
+ b.Fatalf("failed to find server port %d: %v", port, err)
+ }
+
+ // Check the server is serving.
+ harness.WaitUntilServing(ctx, clientMachine, ip, servingPort)
+
+ // Grab a client.
+ client := clientMachine.GetNativeContainer(ctx, b)
+ defer client.CleanUp(ctx)
+
+ b.ResetTimer()
+ server.RestartProfiles()
+ for i := 0; i < b.N; i++ {
+ out, err := client.Run(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/hey",
+ }, hey.MakeCmd(ip, servingPort)...)
+ if err != nil {
+ b.Fatalf("run failed with: %v", err)
+ }
+ b.StopTimer()
+ hey.Report(b, out)
+ b.StartTimer()
+ }
+}
diff --git a/test/benchmarks/network/node_test.go b/test/benchmarks/network/node_test.go
new file mode 100644
index 000000000..5b568cfe5
--- /dev/null
+++ b/test/benchmarks/network/node_test.go
@@ -0,0 +1,131 @@
+// Copyright 2020 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 network
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+ "gvisor.dev/gvisor/test/benchmarks/tools"
+)
+
+// BenchmarkNode runs 10K requests using 'hey' against a Node server run on
+// 'runtime'. The server responds to requests by grabbing some data in a
+// redis instance and returns the data in its reponse. The test loops through
+// increasing amounts of concurency for requests.
+func BenchmarkNode(b *testing.B) {
+ requests := 10000
+ concurrency := []int{1, 5, 10, 25}
+
+ for _, c := range concurrency {
+ b.Run(fmt.Sprintf("Concurrency%d", c), func(b *testing.B) {
+ hey := &tools.Hey{
+ Requests: requests,
+ Concurrency: c,
+ }
+ runNode(b, hey)
+ })
+ }
+}
+
+// runNode runs the test for a given # of requests and concurrency.
+func runNode(b *testing.B, hey *tools.Hey) {
+ b.Helper()
+
+ // The machine to hold Redis and the Node Server.
+ serverMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatal("failed to get machine with: %v", err)
+ }
+ defer serverMachine.CleanUp()
+
+ // The machine to run 'hey'.
+ clientMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatal("failed to get machine with: %v", err)
+ }
+ defer clientMachine.CleanUp()
+
+ ctx := context.Background()
+
+ // Spawn a redis instance for the app to use.
+ redis := serverMachine.GetNativeContainer(ctx, b)
+ if err := redis.Spawn(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/redis",
+ }); err != nil {
+ b.Fatalf("failed to spwan redis instance: %v", err)
+ }
+ defer redis.CleanUp(ctx)
+
+ if out, err := redis.WaitForOutput(ctx, "Ready to accept connections", 3*time.Second); err != nil {
+ b.Fatalf("failed to start redis server: %v %s", err, out)
+ }
+ redisIP, err := redis.FindIP(ctx, false)
+ if err != nil {
+ b.Fatalf("failed to get IP from redis instance: %v", err)
+ }
+
+ // Node runs on port 8080.
+ port := 8080
+
+ // Start-up the Node server.
+ nodeApp := serverMachine.GetContainer(ctx, b)
+ if err := nodeApp.Spawn(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/node",
+ WorkDir: "/usr/src/app",
+ Links: []string{redis.MakeLink("redis")},
+ Ports: []int{port},
+ }, "node", "index.js", redisIP.String()); err != nil {
+ b.Fatalf("failed to spawn node instance: %v", err)
+ }
+ defer nodeApp.CleanUp(ctx)
+
+ servingIP, err := serverMachine.IPAddress()
+ if err != nil {
+ b.Fatalf("failed to get ip from server: %v", err)
+ }
+
+ servingPort, err := nodeApp.FindPort(ctx, port)
+ if err != nil {
+ b.Fatalf("failed to port from node instance: %v", err)
+ }
+
+ // Wait until the Client sees the server as up.
+ harness.WaitUntilServing(ctx, clientMachine, servingIP, servingPort)
+
+ heyCmd := hey.MakeCmd(servingIP, servingPort)
+
+ nodeApp.RestartProfiles()
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ // the client should run on Native.
+ client := clientMachine.GetNativeContainer(ctx, b)
+ out, err := client.Run(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/hey",
+ }, heyCmd...)
+ if err != nil {
+ b.Fatalf("hey container failed: %v logs: %s", err, out)
+ }
+
+ // Stop the timer to parse the data and report stats.
+ b.StopTimer()
+ hey.Report(b, out)
+ b.StartTimer()
+ }
+}
diff --git a/test/benchmarks/tools/BUILD b/test/benchmarks/tools/BUILD
new file mode 100644
index 000000000..4358551bc
--- /dev/null
+++ b/test/benchmarks/tools/BUILD
@@ -0,0 +1,29 @@
+load("//tools:defs.bzl", "go_library", "go_test")
+
+package(licenses = ["notice"])
+
+go_library(
+ name = "tools",
+ srcs = [
+ "ab.go",
+ "fio.go",
+ "hey.go",
+ "iperf.go",
+ "redis.go",
+ "tools.go",
+ ],
+ visibility = ["//:sandbox"],
+)
+
+go_test(
+ name = "tools_test",
+ size = "small",
+ srcs = [
+ "ab_test.go",
+ "fio_test.go",
+ "hey_test.go",
+ "iperf_test.go",
+ "redis_test.go",
+ ],
+ library = ":tools",
+)
diff --git a/test/benchmarks/tools/ab.go b/test/benchmarks/tools/ab.go
new file mode 100644
index 000000000..4cc9c3bce
--- /dev/null
+++ b/test/benchmarks/tools/ab.go
@@ -0,0 +1,94 @@
+// Copyright 2020 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 tools
+
+import (
+ "fmt"
+ "net"
+ "regexp"
+ "strconv"
+ "testing"
+)
+
+// ApacheBench is for the client application ApacheBench.
+type ApacheBench struct {
+ Requests int
+ Concurrency int
+ Doc string
+ // TODO(zkoopmans): support KeepAlive and pass option to enable.
+}
+
+// MakeCmd makes an ApacheBench command.
+func (a *ApacheBench) MakeCmd(ip net.IP, port int) []string {
+ path := fmt.Sprintf("http://%s:%d/%s", ip, port, a.Doc)
+ // See apachebench (ab) for flags.
+ cmd := fmt.Sprintf("ab -n %d -c %d %s", a.Requests, a.Concurrency, path)
+ return []string{"sh", "-c", cmd}
+}
+
+// Report parses and reports metrics from ApacheBench output.
+func (a *ApacheBench) Report(b *testing.B, output string) {
+ // Parse and report custom metrics.
+ transferRate, err := a.parseTransferRate(output)
+ if err != nil {
+ b.Logf("failed to parse transferrate: %v", err)
+ }
+ b.ReportMetric(transferRate*1024, "transfer_rate_b/s") // Convert from Kb/s to b/s.
+
+ latency, err := a.parseLatency(output)
+ if err != nil {
+ b.Logf("failed to parse latency: %v", err)
+ }
+ b.ReportMetric(latency/1000, "mean_latency_secs") // Convert from ms to s.
+
+ reqPerSecond, err := a.parseRequestsPerSecond(output)
+ if err != nil {
+ b.Logf("failed to parse requests per second: %v", err)
+ }
+ b.ReportMetric(reqPerSecond, "requests_per_second")
+}
+
+var transferRateRE = regexp.MustCompile(`Transfer rate:\s+(\d+\.?\d+?)\s+\[Kbytes/sec\]\s+received`)
+
+// parseTransferRate parses transfer rate from ApacheBench output.
+func (a *ApacheBench) parseTransferRate(data string) (float64, error) {
+ match := transferRateRE.FindStringSubmatch(data)
+ if len(match) < 2 {
+ return 0, fmt.Errorf("failed get bandwidth: %s", data)
+ }
+ return strconv.ParseFloat(match[1], 64)
+}
+
+var latencyRE = regexp.MustCompile(`Total:\s+\d+\s+(\d+)\s+(\d+\.?\d+?)\s+\d+\s+\d+\s`)
+
+// parseLatency parses latency from ApacheBench output.
+func (a *ApacheBench) parseLatency(data string) (float64, error) {
+ match := latencyRE.FindStringSubmatch(data)
+ if len(match) < 2 {
+ return 0, fmt.Errorf("failed get bandwidth: %s", data)
+ }
+ return strconv.ParseFloat(match[1], 64)
+}
+
+var requestsPerSecondRE = regexp.MustCompile(`Requests per second:\s+(\d+\.?\d+?)\s+`)
+
+// parseRequestsPerSecond parses requests per second from ApacheBench output.
+func (a *ApacheBench) parseRequestsPerSecond(data string) (float64, error) {
+ match := requestsPerSecondRE.FindStringSubmatch(data)
+ if len(match) < 2 {
+ return 0, fmt.Errorf("failed get bandwidth: %s", data)
+ }
+ return strconv.ParseFloat(match[1], 64)
+}
diff --git a/test/benchmarks/tools/ab_test.go b/test/benchmarks/tools/ab_test.go
new file mode 100644
index 000000000..28ee66ec1
--- /dev/null
+++ b/test/benchmarks/tools/ab_test.go
@@ -0,0 +1,90 @@
+// Copyright 2020 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 tools
+
+import "testing"
+
+// TestApacheBench checks the ApacheBench parsers on sample output.
+func TestApacheBench(t *testing.T) {
+ // Sample output from apachebench.
+ sampleData := `This is ApacheBench, Version 2.3 <$Revision: 1826891 $>
+Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
+Licensed to The Apache Software Foundation, http://www.apache.org/
+
+Benchmarking 10.10.10.10 (be patient).....done
+
+
+Server Software: Apache/2.4.38
+Server Hostname: 10.10.10.10
+Server Port: 80
+
+Document Path: /latin10k.txt
+Document Length: 210 bytes
+
+Concurrency Level: 1
+Time taken for tests: 0.180 seconds
+Complete requests: 100
+Failed requests: 0
+Non-2xx responses: 100
+Total transferred: 38800 bytes
+HTML transferred: 21000 bytes
+Requests per second: 556.44 [#/sec] (mean)
+Time per request: 1.797 [ms] (mean)
+Time per request: 1.797 [ms] (mean, across all concurrent requests)
+Transfer rate: 210.84 [Kbytes/sec] received
+
+Connection Times (ms)
+ min mean[+/-sd] median max
+Connect: 0 0 0.2 0 2
+Processing: 1 2 1.0 1 8
+Waiting: 1 1 1.0 1 7
+Total: 1 2 1.2 1 10
+
+Percentage of the requests served within a certain time (ms)
+ 50% 1
+ 66% 2
+ 75% 2
+ 80% 2
+ 90% 2
+ 95% 3
+ 98% 7
+ 99% 10
+ 100% 10 (longest request)`
+
+ ab := ApacheBench{}
+ want := 210.84
+ got, err := ab.parseTransferRate(sampleData)
+ if err != nil {
+ t.Fatalf("failed to parse transfer rate with error: %v", err)
+ } else if got != want {
+ t.Fatalf("parseTransferRate got: %f, want: %f", got, want)
+ }
+
+ want = 2.0
+ got, err = ab.parseLatency(sampleData)
+ if err != nil {
+ t.Fatalf("failed to parse transfer rate with error: %v", err)
+ } else if got != want {
+ t.Fatalf("parseLatency got: %f, want: %f", got, want)
+ }
+
+ want = 556.44
+ got, err = ab.parseRequestsPerSecond(sampleData)
+ if err != nil {
+ t.Fatalf("failed to parse transfer rate with error: %v", err)
+ } else if got != want {
+ t.Fatalf("parseRequestsPerSecond got: %f, want: %f", got, want)
+ }
+}
diff --git a/test/benchmarks/tools/fio.go b/test/benchmarks/tools/fio.go
new file mode 100644
index 000000000..20000db16
--- /dev/null
+++ b/test/benchmarks/tools/fio.go
@@ -0,0 +1,124 @@
+// Copyright 2020 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 tools
+
+import (
+ "encoding/json"
+ "fmt"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+// Fio makes 'fio' commands and parses their output.
+type Fio struct {
+ Test string // test to run: read, write, randread, randwrite.
+ Size string // total size to be read/written of format N[GMK] (e.g. 5G).
+ Blocksize string // blocksize to be read/write of format N[GMK] (e.g. 4K).
+ Iodepth int // iodepth for reads/writes.
+ Time int // time to run the test in seconds, usually for rand(read/write).
+}
+
+// MakeCmd makes a 'fio' command.
+func (f *Fio) MakeCmd(filename string) []string {
+ cmd := []string{"fio", "--output-format=json", "--ioengine=sync"}
+ cmd = append(cmd, fmt.Sprintf("--name=%s", f.Test))
+ cmd = append(cmd, fmt.Sprintf("--size=%s", f.Size))
+ cmd = append(cmd, fmt.Sprintf("--blocksize=%s", f.Blocksize))
+ cmd = append(cmd, fmt.Sprintf("--filename=%s", filename))
+ cmd = append(cmd, fmt.Sprintf("--iodepth=%d", f.Iodepth))
+ cmd = append(cmd, fmt.Sprintf("--rw=%s", f.Test))
+ if f.Time != 0 {
+ cmd = append(cmd, "--time_based")
+ cmd = append(cmd, fmt.Sprintf("--runtime=%d", f.Time))
+ }
+ return cmd
+}
+
+// Report reports metrics based on output from an 'fio' command.
+func (f *Fio) Report(b *testing.B, output string) {
+ b.Helper()
+ // Parse the output and report the metrics.
+ isRead := strings.Contains(f.Test, "read")
+ bw, err := f.parseBandwidth(output, isRead)
+ if err != nil {
+ b.Fatalf("failed to parse bandwidth from %s with: %v", output, err)
+ }
+ b.ReportMetric(bw, "bandwidth_b/s") // in b/s.
+
+ iops, err := f.parseIOps(output, isRead)
+ if err != nil {
+ b.Fatalf("failed to parse iops from %s with: %v", output, err)
+ }
+ b.ReportMetric(iops, "iops")
+}
+
+// parseBandwidth reports the bandwidth in b/s.
+func (f *Fio) parseBandwidth(data string, isRead bool) (float64, error) {
+ if isRead {
+ result, err := f.parseFioJSON(data, "read", "bw")
+ if err != nil {
+ return 0, err
+ }
+ return 1024 * result, nil
+ }
+ result, err := f.parseFioJSON(data, "write", "bw")
+ if err != nil {
+ return 0, err
+ }
+ return 1024 * result, nil
+}
+
+// parseIOps reports the write IO per second metric.
+func (f *Fio) parseIOps(data string, isRead bool) (float64, error) {
+ if isRead {
+ return f.parseFioJSON(data, "read", "iops")
+ }
+ return f.parseFioJSON(data, "write", "iops")
+}
+
+// fioResult is for parsing FioJSON.
+type fioResult struct {
+ Jobs []fioJob
+}
+
+// fioJob is for parsing FioJSON.
+type fioJob map[string]json.RawMessage
+
+// fioMetrics is for parsing FioJSON.
+type fioMetrics map[string]json.RawMessage
+
+// parseFioJSON parses data and grabs "op" (read or write) and "metric"
+// (bw or iops) from the JSON.
+func (f *Fio) parseFioJSON(data, op, metric string) (float64, error) {
+ var result fioResult
+ if err := json.Unmarshal([]byte(data), &result); err != nil {
+ return 0, fmt.Errorf("could not unmarshal data: %v", err)
+ }
+
+ if len(result.Jobs) < 1 {
+ return 0, fmt.Errorf("no jobs present to parse")
+ }
+
+ var metrics fioMetrics
+ if err := json.Unmarshal(result.Jobs[0][op], &metrics); err != nil {
+ return 0, fmt.Errorf("could not unmarshal jobs: %v", err)
+ }
+
+ if _, ok := metrics[metric]; !ok {
+ return 0, fmt.Errorf("no metric found for op: %s", op)
+ }
+ return strconv.ParseFloat(string(metrics[metric]), 64)
+}
diff --git a/test/benchmarks/tools/fio_test.go b/test/benchmarks/tools/fio_test.go
new file mode 100644
index 000000000..a98277150
--- /dev/null
+++ b/test/benchmarks/tools/fio_test.go
@@ -0,0 +1,122 @@
+// Copyright 2020 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 tools
+
+import "testing"
+
+// TestFio checks the Fio parsers on sample output.
+func TestFio(t *testing.T) {
+ sampleData := `
+{
+ "fio version" : "fio-3.1",
+ "timestamp" : 1554837456,
+ "timestamp_ms" : 1554837456621,
+ "time" : "Tue Apr 9 19:17:36 2019",
+ "jobs" : [
+ {
+ "jobname" : "test",
+ "groupid" : 0,
+ "error" : 0,
+ "eta" : 2147483647,
+ "elapsed" : 1,
+ "job options" : {
+ "name" : "test",
+ "ioengine" : "sync",
+ "size" : "1073741824",
+ "filename" : "/disk/file.dat",
+ "iodepth" : "4",
+ "bs" : "4096",
+ "rw" : "write"
+ },
+ "read" : {
+ "io_bytes" : 0,
+ "io_kbytes" : 0,
+ "bw" : 123456,
+ "iops" : 1234.5678,
+ "runtime" : 0,
+ "total_ios" : 0,
+ "short_ios" : 0,
+ "bw_min" : 0,
+ "bw_max" : 0,
+ "bw_agg" : 0.000000,
+ "bw_mean" : 0.000000,
+ "bw_dev" : 0.000000,
+ "bw_samples" : 0,
+ "iops_min" : 0,
+ "iops_max" : 0,
+ "iops_mean" : 0.000000,
+ "iops_stddev" : 0.000000,
+ "iops_samples" : 0
+ },
+ "write" : {
+ "io_bytes" : 1073741824,
+ "io_kbytes" : 1048576,
+ "bw" : 1753471,
+ "iops" : 438367.892977,
+ "runtime" : 598,
+ "total_ios" : 262144,
+ "bw_min" : 1731120,
+ "bw_max" : 1731120,
+ "bw_agg" : 98.725328,
+ "bw_mean" : 1731120.000000,
+ "bw_dev" : 0.000000,
+ "bw_samples" : 1,
+ "iops_min" : 432780,
+ "iops_max" : 432780,
+ "iops_mean" : 432780.000000,
+ "iops_stddev" : 0.000000,
+ "iops_samples" : 1
+ }
+ }
+ ]
+}
+`
+ fio := Fio{}
+ // WriteBandwidth.
+ got, err := fio.parseBandwidth(sampleData, false)
+ var want float64 = 1753471.0 * 1024
+ if err != nil {
+ t.Fatalf("parse failed with err: %v", err)
+ } else if got != want {
+ t.Fatalf("got: %f, want: %f", got, want)
+ }
+
+ // ReadBandwidth.
+ got, err = fio.parseBandwidth(sampleData, true)
+ want = 123456 * 1024
+ if err != nil {
+ t.Fatalf("parse failed with err: %v", err)
+ } else if got != want {
+ t.Fatalf("got: %f, want: %f", got, want)
+ }
+
+ // WriteIOps.
+ got, err = fio.parseIOps(sampleData, false)
+ want = 438367.892977
+ if err != nil {
+ t.Fatalf("parse failed with err: %v", err)
+ } else if got != want {
+ t.Fatalf("got: %f, want: %f", got, want)
+ }
+
+ // ReadIOps.
+ got, err = fio.parseIOps(sampleData, true)
+ want = 1234.5678
+ if err != nil {
+ t.Fatalf("parse failed with err: %v", err)
+ } else if got != want {
+ t.Fatalf("got: %f, want: %f", got, want)
+ }
+}
diff --git a/test/benchmarks/tools/hey.go b/test/benchmarks/tools/hey.go
new file mode 100644
index 000000000..699497c64
--- /dev/null
+++ b/test/benchmarks/tools/hey.go
@@ -0,0 +1,75 @@
+// Copyright 2020 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 tools
+
+import (
+ "fmt"
+ "net"
+ "regexp"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+// Hey is for the client application 'hey'.
+type Hey struct {
+ Requests int
+ Concurrency int
+ Doc string
+}
+
+// MakeCmd returns a 'hey' command.
+func (h *Hey) MakeCmd(ip net.IP, port int) []string {
+ return strings.Split(fmt.Sprintf("hey -n %d -c %d http://%s:%d/%s",
+ h.Requests, h.Concurrency, ip, port, h.Doc), " ")
+}
+
+// Report parses output from 'hey' and reports metrics.
+func (h *Hey) Report(b *testing.B, output string) {
+ b.Helper()
+ requests, err := h.parseRequestsPerSecond(output)
+ if err != nil {
+ b.Fatalf("failed to parse requests per second: %v", err)
+ }
+ b.ReportMetric(requests, "requests_per_second")
+
+ ave, err := h.parseAverageLatency(output)
+ if err != nil {
+ b.Fatalf("failed to parse average latency: %v", err)
+ }
+ b.ReportMetric(ave, "average_latency_secs")
+}
+
+var heyReqPerSecondRE = regexp.MustCompile(`Requests/sec:\s*(\d+\.?\d+?)\s+`)
+
+// parseRequestsPerSecond finds requests per second from 'hey' output.
+func (h *Hey) parseRequestsPerSecond(data string) (float64, error) {
+ match := heyReqPerSecondRE.FindStringSubmatch(data)
+ if len(match) < 2 {
+ return 0, fmt.Errorf("failed get bandwidth: %s", data)
+ }
+ return strconv.ParseFloat(match[1], 64)
+}
+
+var heyAverageLatencyRE = regexp.MustCompile(`Average:\s*(\d+\.?\d+?)\s+secs`)
+
+// parseHeyAverageLatency finds Average Latency in seconds form 'hey' output.
+func (h *Hey) parseAverageLatency(data string) (float64, error) {
+ match := heyAverageLatencyRE.FindStringSubmatch(data)
+ if len(match) < 2 {
+ return 0, fmt.Errorf("failed get average latency match%d : %s", len(match), data)
+ }
+ return strconv.ParseFloat(match[1], 64)
+}
diff --git a/test/benchmarks/tools/hey_test.go b/test/benchmarks/tools/hey_test.go
new file mode 100644
index 000000000..e0cab1f52
--- /dev/null
+++ b/test/benchmarks/tools/hey_test.go
@@ -0,0 +1,81 @@
+// Copyright 2020 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 tools
+
+import "testing"
+
+// TestHey checks the Hey parsers on sample output.
+func TestHey(t *testing.T) {
+ sampleData := `
+ Summary:
+ Total: 2.2391 secs
+ Slowest: 1.6292 secs
+ Fastest: 0.0066 secs
+ Average: 0.5351 secs
+ Requests/sec: 89.3202
+
+ Total data: 841200 bytes
+ Size/request: 4206 bytes
+
+ Response time histogram:
+ 0.007 [1] |
+ 0.169 [0] |
+ 0.331 [149] |â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– â– 
+ 0.493 [0] |
+ 0.656 [0] |
+ 0.818 [0] |
+ 0.980 [0] |
+ 1.142 [0] |
+ 1.305 [0] |
+ 1.467 [49] |â– â– â– â– â– â– â– â– â– â– â– â– â– 
+ 1.629 [1] |
+
+
+ Latency distribution:
+ 10% in 0.2149 secs
+ 25% in 0.2449 secs
+ 50% in 0.2703 secs
+ 75% in 1.3315 secs
+ 90% in 1.4045 secs
+ 95% in 1.4232 secs
+ 99% in 1.4362 secs
+
+ Details (average, fastest, slowest):
+ DNS+dialup: 0.0002 secs, 0.0066 secs, 1.6292 secs
+ DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0000 secs
+ req write: 0.0000 secs, 0.0000 secs, 0.0012 secs
+ resp wait: 0.5225 secs, 0.0064 secs, 1.4346 secs
+ resp read: 0.0122 secs, 0.0001 secs, 0.2006 secs
+
+ Status code distribution:
+ [200] 200 responses
+ `
+ hey := Hey{}
+ want := 89.3202
+ got, err := hey.parseRequestsPerSecond(sampleData)
+ if err != nil {
+ t.Fatalf("failed to parse request per second with: %v", err)
+ } else if got != want {
+ t.Fatalf("got: %f, want: %f", got, want)
+ }
+
+ want = 0.5351
+ got, err = hey.parseAverageLatency(sampleData)
+ if err != nil {
+ t.Fatalf("failed to parse average latency with: %v", err)
+ } else if got != want {
+ t.Fatalf("got: %f, want: %f", got, want)
+ }
+}
diff --git a/test/benchmarks/tools/iperf.go b/test/benchmarks/tools/iperf.go
new file mode 100644
index 000000000..df3d9349b
--- /dev/null
+++ b/test/benchmarks/tools/iperf.go
@@ -0,0 +1,56 @@
+// Copyright 2020 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 tools
+
+import (
+ "fmt"
+ "net"
+ "regexp"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+// Iperf is for the client side of `iperf`.
+type Iperf struct {
+ Time int
+}
+
+// MakeCmd returns a iperf client command.
+func (i *Iperf) MakeCmd(ip net.IP, port int) []string {
+ // iperf report in Kb realtime
+ return strings.Split(fmt.Sprintf("iperf -f K --realtime --time %d -c %s -p %d", i.Time, ip, port), " ")
+}
+
+// Report parses output from iperf client and reports metrics.
+func (i *Iperf) Report(b *testing.B, output string) {
+ b.Helper()
+ // Parse bandwidth and report it.
+ bW, err := i.bandwidth(output)
+ if err != nil {
+ b.Fatalf("failed to parse bandwitdth from %s: %v", output, err)
+ }
+ b.ReportMetric(bW*1024, "bandwidth_b/s") // Convert from Kb/s to b/s.
+}
+
+// bandwidth parses the Bandwidth number from an iperf report. A sample is below.
+func (i *Iperf) bandwidth(data string) (float64, error) {
+ re := regexp.MustCompile(`\[\s*\d+\][^\n]+\s+(\d+\.?\d*)\s+KBytes/sec`)
+ match := re.FindStringSubmatch(data)
+ if len(match) < 1 {
+ return 0, fmt.Errorf("failed get bandwidth: %s", data)
+ }
+ return strconv.ParseFloat(match[1], 64)
+}
diff --git a/test/benchmarks/tools/iperf_test.go b/test/benchmarks/tools/iperf_test.go
new file mode 100644
index 000000000..03bb30d05
--- /dev/null
+++ b/test/benchmarks/tools/iperf_test.go
@@ -0,0 +1,34 @@
+// Copyright 2020 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 tools
+
+import "testing"
+
+// TestIperf checks the Iperf parsers on sample output.
+func TestIperf(t *testing.T) {
+ sampleData := `
+------------------------------------------------------------
+Client connecting to 10.138.15.215, TCP port 32779
+TCP window size: 45.0 KByte (default)
+------------------------------------------------------------
+[ 3] local 10.138.15.216 port 32866 connected with 10.138.15.215 port 32779
+[ ID] Interval Transfer Bandwidth
+[ 3] 0.0-10.0 sec 459520 KBytes 45900 KBytes/sec
+`
+ i := Iperf{}
+ bandwidth, err := i.bandwidth(sampleData)
+ if err != nil || bandwidth != 45900 {
+ t.Fatalf("failed with: %v and %f", err, bandwidth)
+ }
+}
diff --git a/test/benchmarks/tools/redis.go b/test/benchmarks/tools/redis.go
new file mode 100644
index 000000000..db32460ec
--- /dev/null
+++ b/test/benchmarks/tools/redis.go
@@ -0,0 +1,64 @@
+// Copyright 2020 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 tools
+
+import (
+ "fmt"
+ "net"
+ "regexp"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+// Redis is for the client 'redis-benchmark'.
+type Redis struct {
+ Operation string
+}
+
+// MakeCmd returns a redis-benchmark client command.
+func (r *Redis) MakeCmd(ip net.IP, port int) []string {
+ // There is no -t PING_BULK for redis-benchmark, so adjust the command in that case.
+ // Note that "ping" will run both PING_INLINE and PING_BULK.
+ if r.Operation == "PING_BULK" {
+ return strings.Split(
+ fmt.Sprintf("redis-benchmark --csv -t ping -h %s -p %d", ip, port), " ")
+ }
+
+ // runs redis-benchmark -t operation for 100K requests against server.
+ return strings.Split(
+ fmt.Sprintf("redis-benchmark --csv -t %s -h %s -p %d", r.Operation, ip, port), " ")
+}
+
+// Report parses output from redis-benchmark client and reports metrics.
+func (r *Redis) Report(b *testing.B, output string) {
+ b.Helper()
+ result, err := r.parseOperation(output)
+ if err != nil {
+ b.Fatalf("parsing result %s failed with err: %v", output, err)
+ }
+ b.ReportMetric(result, r.Operation) // operations per second
+}
+
+// parseOperation grabs the metric operations per second from redis-benchmark output.
+func (r *Redis) parseOperation(data string) (float64, error) {
+ re := regexp.MustCompile(fmt.Sprintf(`"%s( .*)?","(\d*\.\d*)"`, r.Operation))
+ match := re.FindStringSubmatch(data)
+ // If no match, simply don't add it to the result map.
+ if len(match) < 3 {
+ return 0.0, fmt.Errorf("could not find %s in %s", r.Operation, data)
+ }
+ return strconv.ParseFloat(match[2], 64)
+}
diff --git a/test/benchmarks/tools/redis_test.go b/test/benchmarks/tools/redis_test.go
new file mode 100644
index 000000000..4bafda66f
--- /dev/null
+++ b/test/benchmarks/tools/redis_test.go
@@ -0,0 +1,87 @@
+// Copyright 2020 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 tools
+
+import (
+ "testing"
+)
+
+// TestRedis checks the Redis parsers on sample output.
+func TestRedis(t *testing.T) {
+ sampleData := `
+ "PING_INLINE","48661.80"
+ "PING_BULK","50301.81"
+ "SET","48923.68"
+ "GET","49382.71"
+ "INCR","49975.02"
+ "LPUSH","49875.31"
+ "RPUSH","50276.52"
+ "LPOP","50327.12"
+ "RPOP","50556.12"
+ "SADD","49504.95"
+ "HSET","49504.95"
+ "SPOP","50025.02"
+ "LPUSH (needed to benchmark LRANGE)","48875.86"
+ "LRANGE_100 (first 100 elements)","33955.86"
+ "LRANGE_300 (first 300 elements)","16550.81"// Copyright 2020 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 tools
+
+ "LRANGE_500 (first 450 elements)","13653.74"
+ "LRANGE_600 (first 600 elements)","11219.57"
+ "MSET (10 keys)","44682.75"
+ `
+ wants := map[string]float64{
+ "PING_INLINE": 48661.80,
+ "PING_BULK": 50301.81,
+ "SET": 48923.68,
+ "GET": 49382.71,
+ "INCR": 49975.02,
+ "LPUSH": 49875.31,
+ "RPUSH": 50276.52,
+ "LPOP": 50327.12,
+ "RPOP": 50556.12,
+ "SADD": 49504.95,
+ "HSET": 49504.95,
+ "SPOP": 50025.02,
+ "LRANGE_100": 33955.86,
+ "LRANGE_300": 16550.81,
+ "LRANGE_500": 13653.74,
+ "LRANGE_600": 11219.57,
+ "MSET": 44682.75,
+ }
+ for op, want := range wants {
+ redis := Redis{
+ Operation: op,
+ }
+ if got, err := redis.parseOperation(sampleData); err != nil {
+ t.Fatalf("failed to parse %s: %v", op, err)
+ } else if want != got {
+ t.Fatalf("wanted %f for op %s, got %f", want, op, got)
+ }
+ }
+}
diff --git a/test/benchmarks/tools/tools.go b/test/benchmarks/tools/tools.go
new file mode 100644
index 000000000..eb61c0136
--- /dev/null
+++ b/test/benchmarks/tools/tools.go
@@ -0,0 +1,17 @@
+// Copyright 2020 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 tools holds tooling to couple command formatting and output parsers
+// together.
+package tools
diff --git a/test/e2e/BUILD b/test/e2e/BUILD
index 44cce0e3b..29a84f184 100644
--- a/test/e2e/BUILD
+++ b/test/e2e/BUILD
@@ -23,6 +23,7 @@ go_test(
"//pkg/test/dockerutil",
"//pkg/test/testutil",
"//runsc/specutils",
+ "@com_github_docker_docker//api/types/mount:go_default_library",
],
)
diff --git a/test/e2e/exec_test.go b/test/e2e/exec_test.go
index 6a63b1232..b47df447c 100644
--- a/test/e2e/exec_test.go
+++ b/test/e2e/exec_test.go
@@ -22,12 +22,10 @@
package integration
import (
+ "context"
"fmt"
- "os"
- "os/exec"
"strconv"
"strings"
- "syscall"
"testing"
"time"
@@ -39,18 +37,19 @@ import (
// Test that exec uses the exact same capability set as the container.
func TestExecCapabilities(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// Start the container.
- if err := d.Spawn(dockerutil.RunOpts{
+ if err := d.Spawn(ctx, dockerutil.RunOpts{
Image: "basic/alpine",
}, "sh", "-c", "cat /proc/self/status; sleep 100"); err != nil {
t.Fatalf("docker run failed: %v", err)
}
// Check that capability.
- matches, err := d.WaitForOutputSubmatch("CapEff:\t([0-9a-f]+)\n", 5*time.Second)
+ matches, err := d.WaitForOutputSubmatch(ctx, "CapEff:\t([0-9a-f]+)\n", 5*time.Second)
if err != nil {
t.Fatalf("WaitForOutputSubmatch() timeout: %v", err)
}
@@ -61,7 +60,7 @@ func TestExecCapabilities(t *testing.T) {
t.Log("Root capabilities:", want)
// Now check that exec'd process capabilities match the root.
- got, err := d.Exec(dockerutil.RunOpts{}, "grep", "CapEff:", "/proc/self/status")
+ got, err := d.Exec(ctx, dockerutil.ExecOpts{}, "grep", "CapEff:", "/proc/self/status")
if err != nil {
t.Fatalf("docker exec failed: %v", err)
}
@@ -74,11 +73,12 @@ func TestExecCapabilities(t *testing.T) {
// Test that 'exec --privileged' adds all capabilities, except for CAP_NET_RAW
// which is removed from the container when --net-raw=false.
func TestExecPrivileged(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// Start the container with all capabilities dropped.
- if err := d.Spawn(dockerutil.RunOpts{
+ if err := d.Spawn(ctx, dockerutil.RunOpts{
Image: "basic/alpine",
CapDrop: []string{"all"},
}, "sh", "-c", "cat /proc/self/status; sleep 100"); err != nil {
@@ -86,7 +86,7 @@ func TestExecPrivileged(t *testing.T) {
}
// Check that all capabilities where dropped from container.
- matches, err := d.WaitForOutputSubmatch("CapEff:\t([0-9a-f]+)\n", 5*time.Second)
+ matches, err := d.WaitForOutputSubmatch(ctx, "CapEff:\t([0-9a-f]+)\n", 5*time.Second)
if err != nil {
t.Fatalf("WaitForOutputSubmatch() timeout: %v", err)
}
@@ -104,7 +104,7 @@ func TestExecPrivileged(t *testing.T) {
// Check that 'exec --privileged' adds all capabilities, except for
// CAP_NET_RAW.
- got, err := d.Exec(dockerutil.RunOpts{
+ got, err := d.Exec(ctx, dockerutil.ExecOpts{
Privileged: true,
}, "grep", "CapEff:", "/proc/self/status")
if err != nil {
@@ -118,76 +118,59 @@ func TestExecPrivileged(t *testing.T) {
}
func TestExecJobControl(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// Start the container.
- if err := d.Spawn(dockerutil.RunOpts{
+ if err := d.Spawn(ctx, dockerutil.RunOpts{
Image: "basic/alpine",
}, "sleep", "1000"); err != nil {
t.Fatalf("docker run failed: %v", err)
}
- // Exec 'sh' with an attached pty.
- if _, err := d.Exec(dockerutil.RunOpts{
- Pty: func(cmd *exec.Cmd, ptmx *os.File) {
- // Call "sleep 100 | cat" in the shell. We pipe to cat
- // so that there will be two processes in the
- // foreground process group.
- if _, err := ptmx.Write([]byte("sleep 100 | cat\n")); err != nil {
- t.Fatalf("error writing to pty: %v", err)
- }
-
- // Give shell a few seconds to start executing the sleep.
- time.Sleep(2 * time.Second)
-
- // Send a ^C to the pty, which should kill sleep and
- // cat, but not the shell. \x03 is ASCII "end of
- // text", which is the same as ^C.
- if _, err := ptmx.Write([]byte{'\x03'}); err != nil {
- t.Fatalf("error writing to pty: %v", err)
- }
-
- // The shell should still be alive at this point. Sleep
- // should have exited with code 2+128=130. We'll exit
- // with 10 plus that number, so that we can be sure
- // that the shell did not get signalled.
- if _, err := ptmx.Write([]byte("exit $(expr $? + 10)\n")); err != nil {
- t.Fatalf("error writing to pty: %v", err)
- }
-
- // Exec process should exit with code 10+130=140.
- ps, err := cmd.Process.Wait()
- if err != nil {
- t.Fatalf("error waiting for exec process: %v", err)
- }
- ws := ps.Sys().(syscall.WaitStatus)
- if !ws.Exited() {
- t.Errorf("ws.Exited got false, want true")
- }
- if got, want := ws.ExitStatus(), 140; got != want {
- t.Errorf("ws.ExitedStatus got %d, want %d", got, want)
- }
- },
- }, "sh"); err != nil {
+ p, err := d.ExecProcess(ctx, dockerutil.ExecOpts{UseTTY: true}, "/bin/sh")
+ if err != nil {
t.Fatalf("docker exec failed: %v", err)
}
+
+ if _, err = p.Write(time.Second, []byte("sleep 100 | cat\n")); err != nil {
+ t.Fatalf("error exit: %v", err)
+ }
+ time.Sleep(time.Second)
+
+ if _, err = p.Write(time.Second, []byte{0x03}); err != nil {
+ t.Fatalf("error exit: %v", err)
+ }
+
+ if _, err = p.Write(time.Second, []byte("exit $(expr $? + 10)\n")); err != nil {
+ t.Fatalf("error exit: %v", err)
+ }
+
+ want := 140
+ got, err := p.WaitExitStatus(ctx)
+ if err != nil {
+ t.Fatalf("wait for exit failed with: %v", err)
+ } else if got != want {
+ t.Fatalf("wait for exit returned: %d want: %d", got, want)
+ }
}
// Test that failure to exec returns proper error message.
func TestExecError(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// Start the container.
- if err := d.Spawn(dockerutil.RunOpts{
+ if err := d.Spawn(ctx, dockerutil.RunOpts{
Image: "basic/alpine",
}, "sleep", "1000"); err != nil {
t.Fatalf("docker run failed: %v", err)
}
// Attempt to exec a binary that doesn't exist.
- out, err := d.Exec(dockerutil.RunOpts{}, "no_can_find")
+ out, err := d.Exec(ctx, dockerutil.ExecOpts{}, "no_can_find")
if err == nil {
t.Fatalf("docker exec didn't fail")
}
@@ -198,11 +181,12 @@ func TestExecError(t *testing.T) {
// Test that exec inherits environment from run.
func TestExecEnv(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// Start the container with env FOO=BAR.
- if err := d.Spawn(dockerutil.RunOpts{
+ if err := d.Spawn(ctx, dockerutil.RunOpts{
Image: "basic/alpine",
Env: []string{"FOO=BAR"},
}, "sleep", "1000"); err != nil {
@@ -210,7 +194,7 @@ func TestExecEnv(t *testing.T) {
}
// Exec "echo $FOO".
- got, err := d.Exec(dockerutil.RunOpts{}, "/bin/sh", "-c", "echo $FOO")
+ got, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "echo $FOO")
if err != nil {
t.Fatalf("docker exec failed: %v", err)
}
@@ -222,11 +206,12 @@ func TestExecEnv(t *testing.T) {
// TestRunEnvHasHome tests that run always has HOME environment set.
func TestRunEnvHasHome(t *testing.T) {
// Base alpine image does not have any environment variables set.
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// Exec "echo $HOME". The 'bin' user's home dir is '/bin'.
- got, err := d.Run(dockerutil.RunOpts{
+ got, err := d.Run(ctx, dockerutil.RunOpts{
Image: "basic/alpine",
User: "bin",
}, "/bin/sh", "-c", "echo $HOME")
@@ -243,17 +228,18 @@ func TestRunEnvHasHome(t *testing.T) {
// Test that exec always has HOME environment set, even when not set in run.
func TestExecEnvHasHome(t *testing.T) {
// Base alpine image does not have any environment variables set.
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
- if err := d.Spawn(dockerutil.RunOpts{
+ if err := d.Spawn(ctx, dockerutil.RunOpts{
Image: "basic/alpine",
}, "sleep", "1000"); err != nil {
t.Fatalf("docker run failed: %v", err)
}
// Exec "echo $HOME", and expect to see "/root".
- got, err := d.Exec(dockerutil.RunOpts{}, "/bin/sh", "-c", "echo $HOME")
+ got, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "echo $HOME")
if err != nil {
t.Fatalf("docker exec failed: %v", err)
}
@@ -265,12 +251,12 @@ func TestExecEnvHasHome(t *testing.T) {
newUID := 1234
newHome := "/foo/bar"
cmd := fmt.Sprintf("mkdir -p -m 777 %q && adduser foo -D -u %d -h %q", newHome, newUID, newHome)
- if _, err := d.Exec(dockerutil.RunOpts{}, "/bin/sh", "-c", cmd); err != nil {
+ if _, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", cmd); err != nil {
t.Fatalf("docker exec failed: %v", err)
}
// Execute the same as the new user and expect newHome.
- got, err = d.Exec(dockerutil.RunOpts{
+ got, err = d.Exec(ctx, dockerutil.ExecOpts{
User: strconv.Itoa(newUID),
}, "/bin/sh", "-c", "echo $HOME")
if err != nil {
diff --git a/test/e2e/integration_test.go b/test/e2e/integration_test.go
index 9cbb2ed5b..6fe6d304f 100644
--- a/test/e2e/integration_test.go
+++ b/test/e2e/integration_test.go
@@ -22,22 +22,27 @@
package integration
import (
+ "context"
"flag"
"fmt"
+ "io/ioutil"
"net"
"net/http"
"os"
- "os/exec"
+ "path/filepath"
"strconv"
"strings"
- "syscall"
"testing"
"time"
+ "github.com/docker/docker/api/types/mount"
"gvisor.dev/gvisor/pkg/test/dockerutil"
"gvisor.dev/gvisor/pkg/test/testutil"
)
+// defaultWait is the default wait time used for tests.
+const defaultWait = time.Minute
+
// httpRequestSucceeds sends a request to a given url and checks that the status is OK.
func httpRequestSucceeds(client http.Client, server string, port int) error {
url := fmt.Sprintf("http://%s:%d", server, port)
@@ -54,37 +59,38 @@ func httpRequestSucceeds(client http.Client, server string, port int) error {
// TestLifeCycle tests a basic Create/Start/Stop docker container life cycle.
func TestLifeCycle(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// Start the container.
- if err := d.Create(dockerutil.RunOpts{
+ if err := d.Create(ctx, dockerutil.RunOpts{
Image: "basic/nginx",
Ports: []int{80},
}); err != nil {
t.Fatalf("docker create failed: %v", err)
}
- if err := d.Start(); err != nil {
+ if err := d.Start(ctx); err != nil {
t.Fatalf("docker start failed: %v", err)
}
// Test that container is working.
- port, err := d.FindPort(80)
+ port, err := d.FindPort(ctx, 80)
if err != nil {
t.Fatalf("docker.FindPort(80) failed: %v", err)
}
- if err := testutil.WaitForHTTP(port, 30*time.Second); err != nil {
+ if err := testutil.WaitForHTTP(port, defaultWait); err != nil {
t.Fatalf("WaitForHTTP() timeout: %v", err)
}
- client := http.Client{Timeout: time.Duration(2 * time.Second)}
+ client := http.Client{Timeout: defaultWait}
if err := httpRequestSucceeds(client, "localhost", port); err != nil {
t.Errorf("http request failed: %v", err)
}
- if err := d.Stop(); err != nil {
+ if err := d.Stop(ctx); err != nil {
t.Fatalf("docker stop failed: %v", err)
}
- if err := d.Remove(); err != nil {
+ if err := d.Remove(ctx); err != nil {
t.Fatalf("docker rm failed: %v", err)
}
}
@@ -94,11 +100,12 @@ func TestPauseResume(t *testing.T) {
t.Skip("Checkpoint is not supported.")
}
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// Start the container.
- if err := d.Spawn(dockerutil.RunOpts{
+ if err := d.Spawn(ctx, dockerutil.RunOpts{
Image: "basic/python",
Ports: []int{8080}, // See Dockerfile.
}); err != nil {
@@ -106,27 +113,28 @@ func TestPauseResume(t *testing.T) {
}
// Find where port 8080 is mapped to.
- port, err := d.FindPort(8080)
+ port, err := d.FindPort(ctx, 8080)
if err != nil {
t.Fatalf("docker.FindPort(8080) failed: %v", err)
}
// Wait until it's up and running.
- if err := testutil.WaitForHTTP(port, 30*time.Second); err != nil {
+ if err := testutil.WaitForHTTP(port, defaultWait); err != nil {
t.Fatalf("WaitForHTTP() timeout: %v", err)
}
// Check that container is working.
- client := http.Client{Timeout: time.Duration(2 * time.Second)}
+ client := http.Client{Timeout: defaultWait}
if err := httpRequestSucceeds(client, "localhost", port); err != nil {
t.Error("http request failed:", err)
}
- if err := d.Pause(); err != nil {
+ if err := d.Pause(ctx); err != nil {
t.Fatalf("docker pause failed: %v", err)
}
// Check if container is paused.
+ client = http.Client{Timeout: 10 * time.Millisecond} // Don't wait a minute.
switch _, err := client.Get(fmt.Sprintf("http://localhost:%d", port)); v := err.(type) {
case nil:
t.Errorf("http req expected to fail but it succeeded")
@@ -138,16 +146,17 @@ func TestPauseResume(t *testing.T) {
t.Errorf("http req got unexpected error %v", v)
}
- if err := d.Unpause(); err != nil {
+ if err := d.Unpause(ctx); err != nil {
t.Fatalf("docker unpause failed: %v", err)
}
// Wait until it's up and running.
- if err := testutil.WaitForHTTP(port, 30*time.Second); err != nil {
+ if err := testutil.WaitForHTTP(port, defaultWait); err != nil {
t.Fatalf("WaitForHTTP() timeout: %v", err)
}
// Check if container is working again.
+ client = http.Client{Timeout: defaultWait}
if err := httpRequestSucceeds(client, "localhost", port); err != nil {
t.Error("http request failed:", err)
}
@@ -158,11 +167,12 @@ func TestCheckpointRestore(t *testing.T) {
t.Skip("Pause/resume is not supported.")
}
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// Start the container.
- if err := d.Spawn(dockerutil.RunOpts{
+ if err := d.Spawn(ctx, dockerutil.RunOpts{
Image: "basic/python",
Ports: []int{8080}, // See Dockerfile.
}); err != nil {
@@ -170,31 +180,31 @@ func TestCheckpointRestore(t *testing.T) {
}
// Create a snapshot.
- if err := d.Checkpoint("test"); err != nil {
+ if err := d.Checkpoint(ctx, "test"); err != nil {
t.Fatalf("docker checkpoint failed: %v", err)
}
- if _, err := d.Wait(30 * time.Second); err != nil {
+ if err := d.WaitTimeout(ctx, defaultWait); err != nil {
t.Fatalf("wait failed: %v", err)
}
// TODO(b/143498576): Remove Poll after github.com/moby/moby/issues/38963 is fixed.
- if err := testutil.Poll(func() error { return d.Restore("test") }, 15*time.Second); err != nil {
+ if err := testutil.Poll(func() error { return d.Restore(ctx, "test") }, defaultWait); err != nil {
t.Fatalf("docker restore failed: %v", err)
}
// Find where port 8080 is mapped to.
- port, err := d.FindPort(8080)
+ port, err := d.FindPort(ctx, 8080)
if err != nil {
t.Fatalf("docker.FindPort(8080) failed: %v", err)
}
// Wait until it's up and running.
- if err := testutil.WaitForHTTP(port, 30*time.Second); err != nil {
+ if err := testutil.WaitForHTTP(port, defaultWait); err != nil {
t.Fatalf("WaitForHTTP() timeout: %v", err)
}
// Check if container is working again.
- client := http.Client{Timeout: time.Duration(2 * time.Second)}
+ client := http.Client{Timeout: defaultWait}
if err := httpRequestSucceeds(client, "localhost", port); err != nil {
t.Error("http request failed:", err)
}
@@ -202,26 +212,27 @@ func TestCheckpointRestore(t *testing.T) {
// Create client and server that talk to each other using the local IP.
func TestConnectToSelf(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// Creates server that replies "server" and exists. Sleeps at the end because
// 'docker exec' gets killed if the init process exists before it can finish.
- if err := d.Spawn(dockerutil.RunOpts{
+ if err := d.Spawn(ctx, dockerutil.RunOpts{
Image: "basic/ubuntu",
}, "/bin/sh", "-c", "echo server | nc -l -p 8080 && sleep 1"); err != nil {
t.Fatalf("docker run failed: %v", err)
}
// Finds IP address for host.
- ip, err := d.Exec(dockerutil.RunOpts{}, "/bin/sh", "-c", "cat /etc/hosts | grep ${HOSTNAME} | awk '{print $1}'")
+ ip, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "cat /etc/hosts | grep ${HOSTNAME} | awk '{print $1}'")
if err != nil {
t.Fatalf("docker exec failed: %v", err)
}
ip = strings.TrimRight(ip, "\n")
// Runs client that sends "client" to the server and exits.
- reply, err := d.Exec(dockerutil.RunOpts{}, "/bin/sh", "-c", fmt.Sprintf("echo client | nc %s 8080", ip))
+ reply, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", fmt.Sprintf("echo client | nc %s 8080", ip))
if err != nil {
t.Fatalf("docker exec failed: %v", err)
}
@@ -230,19 +241,22 @@ func TestConnectToSelf(t *testing.T) {
if want := "server\n"; reply != want {
t.Errorf("Error on server, want: %q, got: %q", want, reply)
}
- if _, err := d.WaitForOutput("^client\n$", 1*time.Second); err != nil {
+ if _, err := d.WaitForOutput(ctx, "^client\n$", defaultWait); err != nil {
t.Fatalf("docker.WaitForOutput(client) timeout: %v", err)
}
}
func TestMemLimit(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
-
- allocMemory := 500 * 1024
- out, err := d.Run(dockerutil.RunOpts{
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
+
+ // N.B. Because the size of the memory file may grow in large chunks,
+ // there is a minimum threshold of 1GB for the MemTotal figure.
+ allocMemory := 1024 * 1024 // In kb.
+ out, err := d.Run(ctx, dockerutil.RunOpts{
Image: "basic/alpine",
- Memory: allocMemory, // In kB.
+ Memory: allocMemory * 1024, // In bytes.
}, "sh", "-c", "cat /proc/meminfo | grep MemTotal: | awk '{print $2}'")
if err != nil {
t.Fatalf("docker run failed: %v", err)
@@ -268,13 +282,14 @@ func TestMemLimit(t *testing.T) {
}
func TestNumCPU(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// Read how many cores are in the container.
- out, err := d.Run(dockerutil.RunOpts{
- Image: "basic/alpine",
- Extra: []string{"--cpuset-cpus=0"},
+ out, err := d.Run(ctx, dockerutil.RunOpts{
+ Image: "basic/alpine",
+ CpusetCpus: "0",
}, "sh", "-c", "cat /proc/cpuinfo | grep 'processor.*:' | wc -l")
if err != nil {
t.Fatalf("docker run failed: %v", err)
@@ -292,48 +307,34 @@ func TestNumCPU(t *testing.T) {
// TestJobControl tests that job control characters are handled properly.
func TestJobControl(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// Start the container with an attached PTY.
- if _, err := d.Run(dockerutil.RunOpts{
+ p, err := d.SpawnProcess(ctx, dockerutil.RunOpts{
Image: "basic/alpine",
- Pty: func(_ *exec.Cmd, ptmx *os.File) {
- // Call "sleep 100" in the shell.
- if _, err := ptmx.Write([]byte("sleep 100\n")); err != nil {
- t.Fatalf("error writing to pty: %v", err)
- }
-
- // Give shell a few seconds to start executing the sleep.
- time.Sleep(2 * time.Second)
+ }, "sh", "-c", "sleep 100 | cat")
+ if err != nil {
+ t.Fatalf("docker run failed: %v", err)
+ }
+ // Give shell a few seconds to start executing the sleep.
+ time.Sleep(2 * time.Second)
- // Send a ^C to the pty, which should kill sleep, but
- // not the shell. \x03 is ASCII "end of text", which
- // is the same as ^C.
- if _, err := ptmx.Write([]byte{'\x03'}); err != nil {
- t.Fatalf("error writing to pty: %v", err)
- }
+ if _, err := p.Write(time.Second, []byte{0x03}); err != nil {
+ t.Fatalf("error exit: %v", err)
+ }
- // The shell should still be alive at this point. Sleep
- // should have exited with code 2+128=130. We'll exit
- // with 10 plus that number, so that we can be sure
- // that the shell did not get signalled.
- if _, err := ptmx.Write([]byte("exit $(expr $? + 10)\n")); err != nil {
- t.Fatalf("error writing to pty: %v", err)
- }
- },
- }, "sh"); err != nil {
- t.Fatalf("docker run failed: %v", err)
+ if err := d.WaitTimeout(ctx, 3*time.Second); err != nil {
+ t.Fatalf("WaitTimeout failed: %v", err)
}
- // Wait for the container to exit.
- got, err := d.Wait(5 * time.Second)
+ want := 130
+ got, err := p.WaitExitStatus(ctx)
if err != nil {
- t.Fatalf("error getting exit code: %v", err)
- }
- // Container should exit with code 10+130=140.
- if want := syscall.WaitStatus(140); got != want {
- t.Errorf("container exited with code %d want %d", got, want)
+ t.Fatalf("wait for exit failed with: %v", err)
+ } else if got != want {
+ t.Fatalf("got: %d want: %d", got, want)
}
}
@@ -352,15 +353,16 @@ func TestWorkingDirCreation(t *testing.T) {
name += "-readonly"
}
t.Run(name, func(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
opts := dockerutil.RunOpts{
Image: "basic/alpine",
WorkDir: tc.workingDir,
ReadOnly: readonly,
}
- got, err := d.Run(opts, "sh", "-c", "echo ${PWD}")
+ got, err := d.Run(ctx, opts, "sh", "-c", "echo ${PWD}")
if err != nil {
t.Fatalf("docker run failed: %v", err)
}
@@ -374,11 +376,12 @@ func TestWorkingDirCreation(t *testing.T) {
// TestTmpFile checks that files inside '/tmp' are not overridden.
func TestTmpFile(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
- opts := dockerutil.RunOpts{Image: "tmpfile"}
- got, err := d.Run(opts, "cat", "/tmp/foo/file.txt")
+ opts := dockerutil.RunOpts{Image: "basic/tmpfile"}
+ got, err := d.Run(ctx, opts, "cat", "/tmp/foo/file.txt")
if err != nil {
t.Fatalf("docker run failed: %v", err)
}
@@ -387,18 +390,80 @@ func TestTmpFile(t *testing.T) {
}
}
+// TestTmpMount checks that mounts inside '/tmp' are not overridden.
+func TestTmpMount(t *testing.T) {
+ ctx := context.Background()
+ dir, err := ioutil.TempDir(testutil.TmpDir(), "tmp-mount")
+ if err != nil {
+ t.Fatalf("TempDir(): %v", err)
+ }
+ want := "123"
+ if err := ioutil.WriteFile(filepath.Join(dir, "file.txt"), []byte("123"), 0666); err != nil {
+ t.Fatalf("WriteFile(): %v", err)
+ }
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
+
+ opts := dockerutil.RunOpts{
+ Image: "basic/alpine",
+ Mounts: []mount.Mount{
+ {
+ Type: mount.TypeBind,
+ Source: dir,
+ Target: "/tmp/foo",
+ },
+ },
+ }
+ got, err := d.Run(ctx, opts, "cat", "/tmp/foo/file.txt")
+ if err != nil {
+ t.Fatalf("docker run failed: %v", err)
+ }
+ if want != got {
+ t.Errorf("invalid file content, want: %q, got: %q", want, got)
+ }
+}
+
// 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) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
+
+ if got, err := d.Run(ctx, dockerutil.RunOpts{
+ Image: "basic/hostoverlaytest",
+ WorkDir: "/root",
+ }, "./test_copy_up"); err != nil {
+ t.Fatalf("docker run failed: %v", err)
+ } else if got != "" {
+ t.Errorf("test failed:\n%s", got)
+ }
+}
- if _, err := d.Run(dockerutil.RunOpts{
- Image: "hostoverlaytest",
+// TestHostOverlayfsRewindDir tests that rewinddir() "causes the directory
+// stream to refer to the current state of the corresponding directory, as a
+// call to opendir() would have done" as required by POSIX, when the directory
+// in question is host overlayfs.
+//
+// This test specifically targets host overlayfs because, per POSIX, "if a file
+// is removed from or added to the directory after the most recent call to
+// opendir() or rewinddir(), whether a subsequent call to readdir() returns an
+// entry for that file is unspecified"; the host filesystems used by other
+// 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) {
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
+
+ if got, err := d.Run(ctx, dockerutil.RunOpts{
+ Image: "basic/hostoverlaytest",
WorkDir: "/root",
- }, "./test"); err != nil {
+ }, "./test_rewinddir"); err != nil {
t.Fatalf("docker run failed: %v", err)
+ } else if got != "" {
+ t.Errorf("test failed:\n%s", got)
}
}
diff --git a/test/e2e/regression_test.go b/test/e2e/regression_test.go
index 327a2174c..70bbe5121 100644
--- a/test/e2e/regression_test.go
+++ b/test/e2e/regression_test.go
@@ -15,6 +15,7 @@
package integration
import (
+ "context"
"strings"
"testing"
@@ -27,11 +28,12 @@ import (
// Prerequisite: the directory where the socket file is created must not have
// been open for write before bind(2) is called.
func TestBindOverlay(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// Run the container.
- got, err := d.Run(dockerutil.RunOpts{
+ got, err := d.Run(ctx, dockerutil.RunOpts{
Image: "basic/ubuntu",
}, "bash", "-c", "nc -l -U /var/run/sock & p=$! && sleep 1 && echo foobar-asdf | nc -U /var/run/sock && wait $p")
if err != nil {
diff --git a/test/image/image_test.go b/test/image/image_test.go
index 2e3543109..ac6186688 100644
--- a/test/image/image_test.go
+++ b/test/image/image_test.go
@@ -22,6 +22,7 @@
package image
import (
+ "context"
"flag"
"fmt"
"io/ioutil"
@@ -36,12 +37,20 @@ import (
"gvisor.dev/gvisor/pkg/test/testutil"
)
+// defaultWait defines how long to wait for progress.
+//
+// See BUILD: This is at least a "large" test, so allow up to 1 minute for any
+// given "wait" step. Note that all tests are run in parallel, which may cause
+// individual slow-downs (but a huge speed-up in aggregate).
+const defaultWait = time.Minute
+
func TestHelloWorld(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// Run the basic container.
- out, err := d.Run(dockerutil.RunOpts{
+ out, err := d.Run(ctx, dockerutil.RunOpts{
Image: "basic/alpine",
}, "echo", "Hello world!")
if err != nil {
@@ -107,26 +116,28 @@ func testHTTPServer(t *testing.T, port int) {
}
func TestHttpd(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// Start the container.
- d.CopyFiles("/usr/local/apache2/htdocs", "test/image/latin10k.txt")
- if err := d.Spawn(dockerutil.RunOpts{
+ opts := dockerutil.RunOpts{
Image: "basic/httpd",
Ports: []int{80},
- }); err != nil {
+ }
+ d.CopyFiles(&opts, "/usr/local/apache2/htdocs", "test/image/latin10k.txt")
+ if err := d.Spawn(ctx, opts); err != nil {
t.Fatalf("docker run failed: %v", err)
}
// Find where port 80 is mapped to.
- port, err := d.FindPort(80)
+ port, err := d.FindPort(ctx, 80)
if err != nil {
t.Fatalf("FindPort(80) failed: %v", err)
}
// Wait until it's up and running.
- if err := testutil.WaitForHTTP(port, 30*time.Second); err != nil {
+ if err := testutil.WaitForHTTP(port, defaultWait); err != nil {
t.Errorf("WaitForHTTP() timeout: %v", err)
}
@@ -134,26 +145,28 @@ func TestHttpd(t *testing.T) {
}
func TestNginx(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// Start the container.
- d.CopyFiles("/usr/share/nginx/html", "test/image/latin10k.txt")
- if err := d.Spawn(dockerutil.RunOpts{
+ opts := dockerutil.RunOpts{
Image: "basic/nginx",
Ports: []int{80},
- }); err != nil {
+ }
+ d.CopyFiles(&opts, "/usr/share/nginx/html", "test/image/latin10k.txt")
+ if err := d.Spawn(ctx, opts); err != nil {
t.Fatalf("docker run failed: %v", err)
}
// Find where port 80 is mapped to.
- port, err := d.FindPort(80)
+ port, err := d.FindPort(ctx, 80)
if err != nil {
t.Fatalf("FindPort(80) failed: %v", err)
}
// Wait until it's up and running.
- if err := testutil.WaitForHTTP(port, 30*time.Second); err != nil {
+ if err := testutil.WaitForHTTP(port, defaultWait); err != nil {
t.Errorf("WaitForHTTP() timeout: %v", err)
}
@@ -161,11 +174,12 @@ func TestNginx(t *testing.T) {
}
func TestMysql(t *testing.T) {
- server := dockerutil.MakeDocker(t)
- defer server.CleanUp()
+ ctx := context.Background()
+ server := dockerutil.MakeContainer(ctx, t)
+ defer server.CleanUp(ctx)
// Start the container.
- if err := server.Spawn(dockerutil.RunOpts{
+ if err := server.Spawn(ctx, dockerutil.RunOpts{
Image: "basic/mysql",
Env: []string{"MYSQL_ROOT_PASSWORD=foobar123"},
}); err != nil {
@@ -173,36 +187,38 @@ func TestMysql(t *testing.T) {
}
// Wait until it's up and running.
- if _, err := server.WaitForOutput("port: 3306 MySQL Community Server", 3*time.Minute); err != nil {
+ if _, err := server.WaitForOutput(ctx, "port: 3306 MySQL Community Server", defaultWait); err != nil {
t.Fatalf("WaitForOutput() timeout: %v", err)
}
// Generate the client and copy in the SQL payload.
- client := dockerutil.MakeDocker(t)
- defer client.CleanUp()
+ client := dockerutil.MakeContainer(ctx, t)
+ defer client.CleanUp(ctx)
// Tell mysql client to connect to the server and execute the file in
// verbose mode to verify the output.
- client.CopyFiles("/sql", "test/image/mysql.sql")
- client.Link("mysql", server)
- if _, err := client.Run(dockerutil.RunOpts{
+ opts := dockerutil.RunOpts{
Image: "basic/mysql",
- }, "mysql", "-hmysql", "-uroot", "-pfoobar123", "-v", "-e", "source /sql/mysql.sql"); err != nil {
+ Links: []string{server.MakeLink("mysql")},
+ }
+ client.CopyFiles(&opts, "/sql", "test/image/mysql.sql")
+ if _, err := client.Run(ctx, opts, "mysql", "-hmysql", "-uroot", "-pfoobar123", "-v", "-e", "source /sql/mysql.sql"); err != nil {
t.Fatalf("docker run failed: %v", err)
}
// Ensure file executed to the end and shutdown mysql.
- if _, err := server.WaitForOutput("mysqld: Shutdown complete", 30*time.Second); err != nil {
+ if _, err := server.WaitForOutput(ctx, "mysqld: Shutdown complete", defaultWait); err != nil {
t.Fatalf("WaitForOutput() timeout: %v", err)
}
}
func TestTomcat(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// Start the server.
- if err := d.Spawn(dockerutil.RunOpts{
+ if err := d.Spawn(ctx, dockerutil.RunOpts{
Image: "basic/tomcat",
Ports: []int{8080},
}); err != nil {
@@ -210,13 +226,13 @@ func TestTomcat(t *testing.T) {
}
// Find where port 8080 is mapped to.
- port, err := d.FindPort(8080)
+ port, err := d.FindPort(ctx, 8080)
if err != nil {
t.Fatalf("FindPort(8080) failed: %v", err)
}
// Wait until it's up and running.
- if err := testutil.WaitForHTTP(port, 30*time.Second); err != nil {
+ if err := testutil.WaitForHTTP(port, defaultWait); err != nil {
t.Fatalf("WaitForHTTP() timeout: %v", err)
}
@@ -232,26 +248,28 @@ func TestTomcat(t *testing.T) {
}
func TestRuby(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// Execute the ruby workload.
- d.CopyFiles("/src", "test/image/ruby.rb", "test/image/ruby.sh")
- if err := d.Spawn(dockerutil.RunOpts{
+ opts := dockerutil.RunOpts{
Image: "basic/ruby",
Ports: []int{8080},
- }, "/src/ruby.sh"); err != nil {
+ }
+ d.CopyFiles(&opts, "/src", "test/image/ruby.rb", "test/image/ruby.sh")
+ if err := d.Spawn(ctx, opts, "/src/ruby.sh"); err != nil {
t.Fatalf("docker run failed: %v", err)
}
// Find where port 8080 is mapped to.
- port, err := d.FindPort(8080)
+ port, err := d.FindPort(ctx, 8080)
if err != nil {
t.Fatalf("FindPort(8080) failed: %v", err)
}
// Wait until it's up and running, 'gem install' can take some time.
- if err := testutil.WaitForHTTP(port, 1*time.Minute); err != nil {
+ if err := testutil.WaitForHTTP(port, time.Minute); err != nil {
t.Fatalf("WaitForHTTP() timeout: %v", err)
}
@@ -274,20 +292,21 @@ func TestRuby(t *testing.T) {
}
func TestStdio(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
wantStdout := "hello stdout"
wantStderr := "bonjour stderr"
cmd := fmt.Sprintf("echo %q; echo %q 1>&2;", wantStdout, wantStderr)
- if err := d.Spawn(dockerutil.RunOpts{
+ if err := d.Spawn(ctx, dockerutil.RunOpts{
Image: "basic/alpine",
}, "/bin/sh", "-c", cmd); err != nil {
t.Fatalf("docker run failed: %v", err)
}
for _, want := range []string{wantStdout, wantStderr} {
- if _, err := d.WaitForOutput(want, 5*time.Second); err != nil {
+ if _, err := d.WaitForOutput(ctx, want, defaultWait); err != nil {
t.Fatalf("docker didn't get output %q : %v", want, err)
}
}
diff --git a/test/iptables/BUILD b/test/iptables/BUILD
index 3e29ca90d..66453772a 100644
--- a/test/iptables/BUILD
+++ b/test/iptables/BUILD
@@ -9,6 +9,7 @@ go_library(
"filter_input.go",
"filter_output.go",
"iptables.go",
+ "iptables_unsafe.go",
"iptables_util.go",
"nat.go",
],
@@ -20,6 +21,7 @@ go_library(
go_test(
name = "iptables_test",
+ size = "large",
srcs = [
"iptables_test.go",
],
diff --git a/test/iptables/filter_input.go b/test/iptables/filter_input.go
index 41e0cfa8d..5737ee317 100644
--- a/test/iptables/filter_input.go
+++ b/test/iptables/filter_input.go
@@ -25,7 +25,6 @@ const (
dropPort = 2401
acceptPort = 2402
sendloopDuration = 2 * time.Second
- network = "udp4"
chainName = "foochain"
)
@@ -49,6 +48,8 @@ func init() {
RegisterTestCase(FilterInputJumpTwice{})
RegisterTestCase(FilterInputDestination{})
RegisterTestCase(FilterInputInvertDestination{})
+ RegisterTestCase(FilterInputSource{})
+ RegisterTestCase(FilterInputInvertSource{})
}
// FilterInputDropUDP tests that we can drop UDP traffic.
@@ -60,8 +61,8 @@ func (FilterInputDropUDP) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterInputDropUDP) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "INPUT", "-p", "udp", "-j", "DROP"); err != nil {
+func (FilterInputDropUDP) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-j", "DROP"); err != nil {
return err
}
@@ -78,8 +79,8 @@ func (FilterInputDropUDP) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterInputDropUDP) LocalAction(ip net.IP) error {
- return sendUDPLoop(ip, dropPort, sendloopDuration)
+func (FilterInputDropUDP) LocalAction(ip net.IP, ipv6 bool) error {
+ return spawnUDPLoop(ip, dropPort, sendloopDuration)
}
// FilterInputDropOnlyUDP tests that "-p udp -j DROP" only affects UDP traffic.
@@ -91,8 +92,8 @@ func (FilterInputDropOnlyUDP) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterInputDropOnlyUDP) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "INPUT", "-p", "udp", "-j", "DROP"); err != nil {
+func (FilterInputDropOnlyUDP) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-j", "DROP"); err != nil {
return err
}
@@ -105,7 +106,7 @@ func (FilterInputDropOnlyUDP) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterInputDropOnlyUDP) LocalAction(ip net.IP) error {
+func (FilterInputDropOnlyUDP) LocalAction(ip net.IP, ipv6 bool) error {
// Try to establish a TCP connection with the container, which should
// succeed.
return connectTCP(ip, acceptPort, sendloopDuration)
@@ -120,8 +121,8 @@ func (FilterInputDropUDPPort) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterInputDropUDPPort) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil {
+func (FilterInputDropUDPPort) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil {
return err
}
@@ -138,8 +139,8 @@ func (FilterInputDropUDPPort) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterInputDropUDPPort) LocalAction(ip net.IP) error {
- return sendUDPLoop(ip, dropPort, sendloopDuration)
+func (FilterInputDropUDPPort) LocalAction(ip net.IP, ipv6 bool) error {
+ return spawnUDPLoop(ip, dropPort, sendloopDuration)
}
// FilterInputDropDifferentUDPPort tests that dropping traffic for a single UDP port
@@ -152,8 +153,8 @@ func (FilterInputDropDifferentUDPPort) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterInputDropDifferentUDPPort) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil {
+func (FilterInputDropDifferentUDPPort) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil {
return err
}
@@ -166,8 +167,8 @@ func (FilterInputDropDifferentUDPPort) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterInputDropDifferentUDPPort) LocalAction(ip net.IP) error {
- return sendUDPLoop(ip, acceptPort, sendloopDuration)
+func (FilterInputDropDifferentUDPPort) LocalAction(ip net.IP, ipv6 bool) error {
+ return spawnUDPLoop(ip, acceptPort, sendloopDuration)
}
// FilterInputDropTCPDestPort tests that connections are not accepted on specified source ports.
@@ -179,8 +180,8 @@ func (FilterInputDropTCPDestPort) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterInputDropTCPDestPort) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "INPUT", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil {
+func (FilterInputDropTCPDestPort) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "INPUT", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil {
return err
}
@@ -193,7 +194,7 @@ func (FilterInputDropTCPDestPort) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterInputDropTCPDestPort) LocalAction(ip net.IP) error {
+func (FilterInputDropTCPDestPort) LocalAction(ip net.IP, ipv6 bool) error {
// Ensure we cannot connect to the container.
for start := time.Now(); time.Since(start) < sendloopDuration; {
if err := connectTCP(ip, dropPort, sendloopDuration-time.Since(start)); err == nil {
@@ -213,9 +214,9 @@ func (FilterInputDropTCPSrcPort) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterInputDropTCPSrcPort) ContainerAction(ip net.IP) error {
+func (FilterInputDropTCPSrcPort) ContainerAction(ip net.IP, ipv6 bool) error {
// Drop anything from an ephemeral port.
- if err := filterTable("-A", "INPUT", "-p", "tcp", "-m", "tcp", "--sport", "1024:65535", "-j", "DROP"); err != nil {
+ if err := filterTable(ipv6, "-A", "INPUT", "-p", "tcp", "-m", "tcp", "--sport", "1024:65535", "-j", "DROP"); err != nil {
return err
}
@@ -228,7 +229,7 @@ func (FilterInputDropTCPSrcPort) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterInputDropTCPSrcPort) LocalAction(ip net.IP) error {
+func (FilterInputDropTCPSrcPort) LocalAction(ip net.IP, ipv6 bool) error {
// Ensure we cannot connect to the container.
for start := time.Now(); time.Since(start) < sendloopDuration; {
if err := connectTCP(ip, acceptPort, sendloopDuration-time.Since(start)); err == nil {
@@ -248,8 +249,8 @@ func (FilterInputDropAll) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterInputDropAll) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "INPUT", "-j", "DROP"); err != nil {
+func (FilterInputDropAll) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "INPUT", "-j", "DROP"); err != nil {
return err
}
@@ -266,8 +267,8 @@ func (FilterInputDropAll) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterInputDropAll) LocalAction(ip net.IP) error {
- return sendUDPLoop(ip, dropPort, sendloopDuration)
+func (FilterInputDropAll) LocalAction(ip net.IP, ipv6 bool) error {
+ return spawnUDPLoop(ip, dropPort, sendloopDuration)
}
// FilterInputMultiUDPRules verifies that multiple UDP rules are applied
@@ -282,17 +283,17 @@ func (FilterInputMultiUDPRules) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterInputMultiUDPRules) ContainerAction(ip net.IP) error {
+func (FilterInputMultiUDPRules) ContainerAction(ip net.IP, ipv6 bool) error {
rules := [][]string{
{"-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", dropPort), "-j", "DROP"},
{"-A", "INPUT", "-p", "udp", "-m", "udp", "--destination-port", fmt.Sprintf("%d", acceptPort), "-j", "ACCEPT"},
{"-L"},
}
- return filterTableRules(rules)
+ return filterTableRules(ipv6, rules)
}
// LocalAction implements TestCase.LocalAction.
-func (FilterInputMultiUDPRules) LocalAction(ip net.IP) error {
+func (FilterInputMultiUDPRules) LocalAction(ip net.IP, ipv6 bool) error {
// No-op.
return nil
}
@@ -307,14 +308,14 @@ func (FilterInputRequireProtocolUDP) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterInputRequireProtocolUDP) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "INPUT", "-m", "udp", "--destination-port", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err == nil {
+func (FilterInputRequireProtocolUDP) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "INPUT", "-m", "udp", "--destination-port", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err == nil {
return errors.New("expected iptables to fail with out \"-p udp\", but succeeded")
}
return nil
}
-func (FilterInputRequireProtocolUDP) LocalAction(ip net.IP) error {
+func (FilterInputRequireProtocolUDP) LocalAction(ip net.IP, ipv6 bool) error {
// No-op.
return nil
}
@@ -328,18 +329,18 @@ func (FilterInputCreateUserChain) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterInputCreateUserChain) ContainerAction(ip net.IP) error {
+func (FilterInputCreateUserChain) ContainerAction(ip net.IP, ipv6 bool) error {
rules := [][]string{
// Create a chain.
{"-N", chainName},
// Add a simple rule to the chain.
{"-A", chainName, "-j", "DROP"},
}
- return filterTableRules(rules)
+ return filterTableRules(ipv6, rules)
}
// LocalAction implements TestCase.LocalAction.
-func (FilterInputCreateUserChain) LocalAction(ip net.IP) error {
+func (FilterInputCreateUserChain) LocalAction(ip net.IP, ipv6 bool) error {
// No-op.
return nil
}
@@ -353,17 +354,17 @@ func (FilterInputDefaultPolicyAccept) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterInputDefaultPolicyAccept) ContainerAction(ip net.IP) error {
+func (FilterInputDefaultPolicyAccept) ContainerAction(ip net.IP, ipv6 bool) error {
// Set the default policy to accept, then receive a packet.
- if err := filterTable("-P", "INPUT", "ACCEPT"); err != nil {
+ if err := filterTable(ipv6, "-P", "INPUT", "ACCEPT"); err != nil {
return err
}
return listenUDP(acceptPort, sendloopDuration)
}
// LocalAction implements TestCase.LocalAction.
-func (FilterInputDefaultPolicyAccept) LocalAction(ip net.IP) error {
- return sendUDPLoop(ip, acceptPort, sendloopDuration)
+func (FilterInputDefaultPolicyAccept) LocalAction(ip net.IP, ipv6 bool) error {
+ return spawnUDPLoop(ip, acceptPort, sendloopDuration)
}
// FilterInputDefaultPolicyDrop tests the default DROP policy.
@@ -375,8 +376,8 @@ func (FilterInputDefaultPolicyDrop) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterInputDefaultPolicyDrop) ContainerAction(ip net.IP) error {
- if err := filterTable("-P", "INPUT", "DROP"); err != nil {
+func (FilterInputDefaultPolicyDrop) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-P", "INPUT", "DROP"); err != nil {
return err
}
@@ -393,8 +394,8 @@ func (FilterInputDefaultPolicyDrop) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterInputDefaultPolicyDrop) LocalAction(ip net.IP) error {
- return sendUDPLoop(ip, acceptPort, sendloopDuration)
+func (FilterInputDefaultPolicyDrop) LocalAction(ip net.IP, ipv6 bool) error {
+ return spawnUDPLoop(ip, acceptPort, sendloopDuration)
}
// FilterInputReturnUnderflow tests that -j RETURN in a built-in chain causes
@@ -407,7 +408,7 @@ func (FilterInputReturnUnderflow) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterInputReturnUnderflow) ContainerAction(ip net.IP) error {
+func (FilterInputReturnUnderflow) ContainerAction(ip net.IP, ipv6 bool) error {
// Add a RETURN rule followed by an unconditional accept, and set the
// default policy to DROP.
rules := [][]string{
@@ -415,7 +416,7 @@ func (FilterInputReturnUnderflow) ContainerAction(ip net.IP) error {
{"-A", "INPUT", "-j", "DROP"},
{"-P", "INPUT", "ACCEPT"},
}
- if err := filterTableRules(rules); err != nil {
+ if err := filterTableRules(ipv6, rules); err != nil {
return err
}
@@ -425,8 +426,8 @@ func (FilterInputReturnUnderflow) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterInputReturnUnderflow) LocalAction(ip net.IP) error {
- return sendUDPLoop(ip, acceptPort, sendloopDuration)
+func (FilterInputReturnUnderflow) LocalAction(ip net.IP, ipv6 bool) error {
+ return spawnUDPLoop(ip, acceptPort, sendloopDuration)
}
// FilterInputSerializeJump verifies that we can serialize jumps.
@@ -438,18 +439,18 @@ func (FilterInputSerializeJump) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterInputSerializeJump) ContainerAction(ip net.IP) error {
+func (FilterInputSerializeJump) ContainerAction(ip net.IP, ipv6 bool) error {
// Write a JUMP rule, the serialize it with `-L`.
rules := [][]string{
{"-N", chainName},
{"-A", "INPUT", "-j", chainName},
{"-L"},
}
- return filterTableRules(rules)
+ return filterTableRules(ipv6, rules)
}
// LocalAction implements TestCase.LocalAction.
-func (FilterInputSerializeJump) LocalAction(ip net.IP) error {
+func (FilterInputSerializeJump) LocalAction(ip net.IP, ipv6 bool) error {
// No-op.
return nil
}
@@ -463,14 +464,14 @@ func (FilterInputJumpBasic) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterInputJumpBasic) ContainerAction(ip net.IP) error {
+func (FilterInputJumpBasic) ContainerAction(ip net.IP, ipv6 bool) error {
rules := [][]string{
{"-P", "INPUT", "DROP"},
{"-N", chainName},
{"-A", "INPUT", "-j", chainName},
{"-A", chainName, "-j", "ACCEPT"},
}
- if err := filterTableRules(rules); err != nil {
+ if err := filterTableRules(ipv6, rules); err != nil {
return err
}
@@ -479,8 +480,8 @@ func (FilterInputJumpBasic) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterInputJumpBasic) LocalAction(ip net.IP) error {
- return sendUDPLoop(ip, acceptPort, sendloopDuration)
+func (FilterInputJumpBasic) LocalAction(ip net.IP, ipv6 bool) error {
+ return spawnUDPLoop(ip, acceptPort, sendloopDuration)
}
// FilterInputJumpReturn jumps, returns, and executes a rule.
@@ -492,7 +493,7 @@ func (FilterInputJumpReturn) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterInputJumpReturn) ContainerAction(ip net.IP) error {
+func (FilterInputJumpReturn) ContainerAction(ip net.IP, ipv6 bool) error {
rules := [][]string{
{"-N", chainName},
{"-P", "INPUT", "ACCEPT"},
@@ -500,7 +501,7 @@ func (FilterInputJumpReturn) ContainerAction(ip net.IP) error {
{"-A", chainName, "-j", "RETURN"},
{"-A", chainName, "-j", "DROP"},
}
- if err := filterTableRules(rules); err != nil {
+ if err := filterTableRules(ipv6, rules); err != nil {
return err
}
@@ -509,8 +510,8 @@ func (FilterInputJumpReturn) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterInputJumpReturn) LocalAction(ip net.IP) error {
- return sendUDPLoop(ip, acceptPort, sendloopDuration)
+func (FilterInputJumpReturn) LocalAction(ip net.IP, ipv6 bool) error {
+ return spawnUDPLoop(ip, acceptPort, sendloopDuration)
}
// FilterInputJumpReturnDrop jumps to a chain, returns, and DROPs packets.
@@ -522,14 +523,14 @@ func (FilterInputJumpReturnDrop) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterInputJumpReturnDrop) ContainerAction(ip net.IP) error {
+func (FilterInputJumpReturnDrop) ContainerAction(ip net.IP, ipv6 bool) error {
rules := [][]string{
{"-N", chainName},
{"-A", "INPUT", "-j", chainName},
{"-A", "INPUT", "-j", "DROP"},
{"-A", chainName, "-j", "RETURN"},
}
- if err := filterTableRules(rules); err != nil {
+ if err := filterTableRules(ipv6, rules); err != nil {
return err
}
@@ -546,8 +547,8 @@ func (FilterInputJumpReturnDrop) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterInputJumpReturnDrop) LocalAction(ip net.IP) error {
- return sendUDPLoop(ip, dropPort, sendloopDuration)
+func (FilterInputJumpReturnDrop) LocalAction(ip net.IP, ipv6 bool) error {
+ return spawnUDPLoop(ip, dropPort, sendloopDuration)
}
// FilterInputJumpBuiltin verifies that jumping to a top-levl chain is illegal.
@@ -559,15 +560,15 @@ func (FilterInputJumpBuiltin) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterInputJumpBuiltin) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "INPUT", "-j", "OUTPUT"); err == nil {
+func (FilterInputJumpBuiltin) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "INPUT", "-j", "OUTPUT"); err == nil {
return fmt.Errorf("iptables should be unable to jump to a built-in chain")
}
return nil
}
// LocalAction implements TestCase.LocalAction.
-func (FilterInputJumpBuiltin) LocalAction(ip net.IP) error {
+func (FilterInputJumpBuiltin) LocalAction(ip net.IP, ipv6 bool) error {
// No-op.
return nil
}
@@ -581,7 +582,7 @@ func (FilterInputJumpTwice) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterInputJumpTwice) ContainerAction(ip net.IP) error {
+func (FilterInputJumpTwice) ContainerAction(ip net.IP, ipv6 bool) error {
const chainName2 = chainName + "2"
rules := [][]string{
{"-P", "INPUT", "DROP"},
@@ -591,7 +592,7 @@ func (FilterInputJumpTwice) ContainerAction(ip net.IP) error {
{"-A", chainName, "-j", chainName2},
{"-A", "INPUT", "-j", "ACCEPT"},
}
- if err := filterTableRules(rules); err != nil {
+ if err := filterTableRules(ipv6, rules); err != nil {
return err
}
@@ -601,8 +602,8 @@ func (FilterInputJumpTwice) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterInputJumpTwice) LocalAction(ip net.IP) error {
- return sendUDPLoop(ip, acceptPort, sendloopDuration)
+func (FilterInputJumpTwice) LocalAction(ip net.IP, ipv6 bool) error {
+ return spawnUDPLoop(ip, acceptPort, sendloopDuration)
}
// FilterInputDestination verifies that we can filter packets via `-d
@@ -615,8 +616,8 @@ func (FilterInputDestination) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterInputDestination) ContainerAction(ip net.IP) error {
- addrs, err := localAddrs()
+func (FilterInputDestination) ContainerAction(ip net.IP, ipv6 bool) error {
+ addrs, err := localAddrs(ipv6)
if err != nil {
return err
}
@@ -627,7 +628,7 @@ func (FilterInputDestination) ContainerAction(ip net.IP) error {
for _, addr := range addrs {
rules = append(rules, []string{"-A", "INPUT", "-d", addr, "-j", "ACCEPT"})
}
- if err := filterTableRules(rules); err != nil {
+ if err := filterTableRules(ipv6, rules); err != nil {
return err
}
@@ -635,8 +636,8 @@ func (FilterInputDestination) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterInputDestination) LocalAction(ip net.IP) error {
- return sendUDPLoop(ip, acceptPort, sendloopDuration)
+func (FilterInputDestination) LocalAction(ip net.IP, ipv6 bool) error {
+ return spawnUDPLoop(ip, acceptPort, sendloopDuration)
}
// FilterInputInvertDestination verifies that we can filter packets via `! -d
@@ -649,14 +650,14 @@ func (FilterInputInvertDestination) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterInputInvertDestination) ContainerAction(ip net.IP) error {
+func (FilterInputInvertDestination) ContainerAction(ip net.IP, ipv6 bool) error {
// Make INPUT's default action DROP, then ACCEPT all packets not bound
// for 127.0.0.1.
rules := [][]string{
{"-P", "INPUT", "DROP"},
- {"-A", "INPUT", "!", "-d", localIP, "-j", "ACCEPT"},
+ {"-A", "INPUT", "!", "-d", localIP(ipv6), "-j", "ACCEPT"},
}
- if err := filterTableRules(rules); err != nil {
+ if err := filterTableRules(ipv6, rules); err != nil {
return err
}
@@ -664,6 +665,64 @@ func (FilterInputInvertDestination) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterInputInvertDestination) LocalAction(ip net.IP) error {
- return sendUDPLoop(ip, acceptPort, sendloopDuration)
+func (FilterInputInvertDestination) LocalAction(ip net.IP, ipv6 bool) error {
+ return spawnUDPLoop(ip, acceptPort, sendloopDuration)
+}
+
+// FilterInputSource verifies that we can filter packets via `-s
+// <ipaddr>`.
+type FilterInputSource struct{}
+
+// Name implements TestCase.Name.
+func (FilterInputSource) Name() string {
+ return "FilterInputSource"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (FilterInputSource) ContainerAction(ip net.IP, ipv6 bool) error {
+ // Make INPUT's default action DROP, then ACCEPT all packets from this
+ // machine.
+ rules := [][]string{
+ {"-P", "INPUT", "DROP"},
+ {"-A", "INPUT", "-s", fmt.Sprintf("%v", ip), "-j", "ACCEPT"},
+ }
+ if err := filterTableRules(ipv6, rules); err != nil {
+ return err
+ }
+
+ return listenUDP(acceptPort, sendloopDuration)
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (FilterInputSource) LocalAction(ip net.IP, ipv6 bool) error {
+ return spawnUDPLoop(ip, acceptPort, sendloopDuration)
+}
+
+// FilterInputInvertSource verifies that we can filter packets via `! -s
+// <ipaddr>`.
+type FilterInputInvertSource struct{}
+
+// Name implements TestCase.Name.
+func (FilterInputInvertSource) Name() string {
+ return "FilterInputInvertSource"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (FilterInputInvertSource) ContainerAction(ip net.IP, ipv6 bool) error {
+ // Make INPUT's default action DROP, then ACCEPT all packets not bound
+ // for 127.0.0.1.
+ rules := [][]string{
+ {"-P", "INPUT", "DROP"},
+ {"-A", "INPUT", "!", "-s", localIP(ipv6), "-j", "ACCEPT"},
+ }
+ if err := filterTableRules(ipv6, rules); err != nil {
+ return err
+ }
+
+ return listenUDP(acceptPort, sendloopDuration)
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (FilterInputInvertSource) LocalAction(ip net.IP, ipv6 bool) error {
+ return spawnUDPLoop(ip, acceptPort, sendloopDuration)
}
diff --git a/test/iptables/filter_output.go b/test/iptables/filter_output.go
index ba0d6fc29..c1d83b471 100644
--- a/test/iptables/filter_output.go
+++ b/test/iptables/filter_output.go
@@ -52,8 +52,8 @@ func (FilterOutputDropTCPDestPort) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterOutputDropTCPDestPort) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "OUTPUT", "-p", "tcp", "-m", "tcp", "--dport", "1024:65535", "-j", "DROP"); err != nil {
+func (FilterOutputDropTCPDestPort) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "tcp", "--dport", "1024:65535", "-j", "DROP"); err != nil {
return err
}
@@ -66,7 +66,7 @@ func (FilterOutputDropTCPDestPort) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterOutputDropTCPDestPort) LocalAction(ip net.IP) error {
+func (FilterOutputDropTCPDestPort) LocalAction(ip net.IP, ipv6 bool) error {
if err := connectTCP(ip, acceptPort, sendloopDuration); err == nil {
return fmt.Errorf("connection on port %d should not be accepted, but got accepted", dropPort)
}
@@ -84,8 +84,8 @@ func (FilterOutputDropTCPSrcPort) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterOutputDropTCPSrcPort) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "OUTPUT", "-p", "tcp", "-m", "tcp", "--sport", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil {
+func (FilterOutputDropTCPSrcPort) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "tcp", "--sport", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil {
return err
}
@@ -98,7 +98,7 @@ func (FilterOutputDropTCPSrcPort) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterOutputDropTCPSrcPort) LocalAction(ip net.IP) error {
+func (FilterOutputDropTCPSrcPort) LocalAction(ip net.IP, ipv6 bool) error {
if err := connectTCP(ip, dropPort, sendloopDuration); err == nil {
return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", dropPort)
}
@@ -115,8 +115,8 @@ func (FilterOutputAcceptTCPOwner) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterOutputAcceptTCPOwner) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "OUTPUT", "-p", "tcp", "-m", "owner", "--uid-owner", "root", "-j", "ACCEPT"); err != nil {
+func (FilterOutputAcceptTCPOwner) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "owner", "--uid-owner", "root", "-j", "ACCEPT"); err != nil {
return err
}
@@ -125,7 +125,7 @@ func (FilterOutputAcceptTCPOwner) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterOutputAcceptTCPOwner) LocalAction(ip net.IP) error {
+func (FilterOutputAcceptTCPOwner) LocalAction(ip net.IP, ipv6 bool) error {
return connectTCP(ip, acceptPort, sendloopDuration)
}
@@ -138,8 +138,8 @@ func (FilterOutputDropTCPOwner) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterOutputDropTCPOwner) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "OUTPUT", "-p", "tcp", "-m", "owner", "--uid-owner", "root", "-j", "DROP"); err != nil {
+func (FilterOutputDropTCPOwner) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "owner", "--uid-owner", "root", "-j", "DROP"); err != nil {
return err
}
@@ -152,7 +152,7 @@ func (FilterOutputDropTCPOwner) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterOutputDropTCPOwner) LocalAction(ip net.IP) error {
+func (FilterOutputDropTCPOwner) LocalAction(ip net.IP, ipv6 bool) error {
if err := connectTCP(ip, acceptPort, sendloopDuration); err == nil {
return fmt.Errorf("connection destined to port %d should be dropped, but got accepted", acceptPort)
}
@@ -169,8 +169,8 @@ func (FilterOutputAcceptUDPOwner) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterOutputAcceptUDPOwner) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "OUTPUT", "-p", "udp", "-m", "owner", "--uid-owner", "root", "-j", "ACCEPT"); err != nil {
+func (FilterOutputAcceptUDPOwner) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-m", "owner", "--uid-owner", "root", "-j", "ACCEPT"); err != nil {
return err
}
@@ -179,7 +179,7 @@ func (FilterOutputAcceptUDPOwner) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterOutputAcceptUDPOwner) LocalAction(ip net.IP) error {
+func (FilterOutputAcceptUDPOwner) LocalAction(ip net.IP, ipv6 bool) error {
// Listen for UDP packets on acceptPort.
return listenUDP(acceptPort, sendloopDuration)
}
@@ -193,8 +193,8 @@ func (FilterOutputDropUDPOwner) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterOutputDropUDPOwner) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "OUTPUT", "-p", "udp", "-m", "owner", "--uid-owner", "root", "-j", "DROP"); err != nil {
+func (FilterOutputDropUDPOwner) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-m", "owner", "--uid-owner", "root", "-j", "DROP"); err != nil {
return err
}
@@ -203,7 +203,7 @@ func (FilterOutputDropUDPOwner) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterOutputDropUDPOwner) LocalAction(ip net.IP) error {
+func (FilterOutputDropUDPOwner) LocalAction(ip net.IP, ipv6 bool) error {
// Listen for UDP packets on dropPort.
if err := listenUDP(dropPort, sendloopDuration); err == nil {
return fmt.Errorf("packets should not be received")
@@ -222,8 +222,8 @@ func (FilterOutputOwnerFail) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterOutputOwnerFail) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "OUTPUT", "-p", "udp", "-m", "owner", "-j", "ACCEPT"); err == nil {
+func (FilterOutputOwnerFail) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-m", "owner", "-j", "ACCEPT"); err == nil {
return fmt.Errorf("Invalid argument")
}
@@ -231,7 +231,7 @@ func (FilterOutputOwnerFail) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterOutputOwnerFail) LocalAction(ip net.IP) error {
+func (FilterOutputOwnerFail) LocalAction(ip net.IP, ipv6 bool) error {
// no-op.
return nil
}
@@ -245,8 +245,8 @@ func (FilterOutputAcceptGIDOwner) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterOutputAcceptGIDOwner) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "OUTPUT", "-p", "tcp", "-m", "owner", "--gid-owner", "root", "-j", "ACCEPT"); err != nil {
+func (FilterOutputAcceptGIDOwner) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "owner", "--gid-owner", "root", "-j", "ACCEPT"); err != nil {
return err
}
@@ -255,7 +255,7 @@ func (FilterOutputAcceptGIDOwner) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterOutputAcceptGIDOwner) LocalAction(ip net.IP) error {
+func (FilterOutputAcceptGIDOwner) LocalAction(ip net.IP, ipv6 bool) error {
return connectTCP(ip, acceptPort, sendloopDuration)
}
@@ -268,8 +268,8 @@ func (FilterOutputDropGIDOwner) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterOutputDropGIDOwner) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "OUTPUT", "-p", "tcp", "-m", "owner", "--gid-owner", "root", "-j", "DROP"); err != nil {
+func (FilterOutputDropGIDOwner) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "owner", "--gid-owner", "root", "-j", "DROP"); err != nil {
return err
}
@@ -282,7 +282,7 @@ func (FilterOutputDropGIDOwner) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterOutputDropGIDOwner) LocalAction(ip net.IP) error {
+func (FilterOutputDropGIDOwner) LocalAction(ip net.IP, ipv6 bool) error {
if err := connectTCP(ip, acceptPort, sendloopDuration); err == nil {
return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", acceptPort)
}
@@ -299,12 +299,12 @@ func (FilterOutputInvertGIDOwner) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterOutputInvertGIDOwner) ContainerAction(ip net.IP) error {
+func (FilterOutputInvertGIDOwner) ContainerAction(ip net.IP, ipv6 bool) error {
rules := [][]string{
{"-A", "OUTPUT", "-p", "tcp", "-m", "owner", "!", "--gid-owner", "root", "-j", "ACCEPT"},
{"-A", "OUTPUT", "-p", "tcp", "-j", "DROP"},
}
- if err := filterTableRules(rules); err != nil {
+ if err := filterTableRules(ipv6, rules); err != nil {
return err
}
@@ -317,7 +317,7 @@ func (FilterOutputInvertGIDOwner) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterOutputInvertGIDOwner) LocalAction(ip net.IP) error {
+func (FilterOutputInvertGIDOwner) LocalAction(ip net.IP, ipv6 bool) error {
if err := connectTCP(ip, acceptPort, sendloopDuration); err == nil {
return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", acceptPort)
}
@@ -334,12 +334,12 @@ func (FilterOutputInvertUIDOwner) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterOutputInvertUIDOwner) ContainerAction(ip net.IP) error {
+func (FilterOutputInvertUIDOwner) ContainerAction(ip net.IP, ipv6 bool) error {
rules := [][]string{
{"-A", "OUTPUT", "-p", "tcp", "-m", "owner", "!", "--uid-owner", "root", "-j", "DROP"},
{"-A", "OUTPUT", "-p", "tcp", "-j", "ACCEPT"},
}
- if err := filterTableRules(rules); err != nil {
+ if err := filterTableRules(ipv6, rules); err != nil {
return err
}
@@ -348,7 +348,7 @@ func (FilterOutputInvertUIDOwner) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterOutputInvertUIDOwner) LocalAction(ip net.IP) error {
+func (FilterOutputInvertUIDOwner) LocalAction(ip net.IP, ipv6 bool) error {
return connectTCP(ip, acceptPort, sendloopDuration)
}
@@ -362,12 +362,12 @@ func (FilterOutputInvertUIDAndGIDOwner) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterOutputInvertUIDAndGIDOwner) ContainerAction(ip net.IP) error {
+func (FilterOutputInvertUIDAndGIDOwner) ContainerAction(ip net.IP, ipv6 bool) error {
rules := [][]string{
{"-A", "OUTPUT", "-p", "tcp", "-m", "owner", "!", "--uid-owner", "root", "!", "--gid-owner", "root", "-j", "ACCEPT"},
{"-A", "OUTPUT", "-p", "tcp", "-j", "DROP"},
}
- if err := filterTableRules(rules); err != nil {
+ if err := filterTableRules(ipv6, rules); err != nil {
return err
}
@@ -380,7 +380,7 @@ func (FilterOutputInvertUIDAndGIDOwner) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterOutputInvertUIDAndGIDOwner) LocalAction(ip net.IP) error {
+func (FilterOutputInvertUIDAndGIDOwner) LocalAction(ip net.IP, ipv6 bool) error {
if err := connectTCP(ip, acceptPort, sendloopDuration); err == nil {
return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", acceptPort)
}
@@ -398,12 +398,12 @@ func (FilterOutputDestination) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterOutputDestination) ContainerAction(ip net.IP) error {
+func (FilterOutputDestination) ContainerAction(ip net.IP, ipv6 bool) error {
rules := [][]string{
{"-A", "OUTPUT", "-d", ip.String(), "-j", "ACCEPT"},
{"-P", "OUTPUT", "DROP"},
}
- if err := filterTableRules(rules); err != nil {
+ if err := filterTableRules(ipv6, rules); err != nil {
return err
}
@@ -411,7 +411,7 @@ func (FilterOutputDestination) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterOutputDestination) LocalAction(ip net.IP) error {
+func (FilterOutputDestination) LocalAction(ip net.IP, ipv6 bool) error {
return listenUDP(acceptPort, sendloopDuration)
}
@@ -425,12 +425,12 @@ func (FilterOutputInvertDestination) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterOutputInvertDestination) ContainerAction(ip net.IP) error {
+func (FilterOutputInvertDestination) ContainerAction(ip net.IP, ipv6 bool) error {
rules := [][]string{
- {"-A", "OUTPUT", "!", "-d", localIP, "-j", "ACCEPT"},
+ {"-A", "OUTPUT", "!", "-d", localIP(ipv6), "-j", "ACCEPT"},
{"-P", "OUTPUT", "DROP"},
}
- if err := filterTableRules(rules); err != nil {
+ if err := filterTableRules(ipv6, rules); err != nil {
return err
}
@@ -438,7 +438,7 @@ func (FilterOutputInvertDestination) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterOutputInvertDestination) LocalAction(ip net.IP) error {
+func (FilterOutputInvertDestination) LocalAction(ip net.IP, ipv6 bool) error {
return listenUDP(acceptPort, sendloopDuration)
}
@@ -452,12 +452,12 @@ func (FilterOutputInterfaceAccept) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterOutputInterfaceAccept) ContainerAction(ip net.IP) error {
+func (FilterOutputInterfaceAccept) ContainerAction(ip net.IP, ipv6 bool) error {
ifname, ok := getInterfaceName()
if !ok {
return fmt.Errorf("no interface is present, except loopback")
}
- if err := filterTable("-A", "OUTPUT", "-p", "udp", "-o", ifname, "-j", "ACCEPT"); err != nil {
+ if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-o", ifname, "-j", "ACCEPT"); err != nil {
return err
}
@@ -465,7 +465,7 @@ func (FilterOutputInterfaceAccept) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterOutputInterfaceAccept) LocalAction(ip net.IP) error {
+func (FilterOutputInterfaceAccept) LocalAction(ip net.IP, ipv6 bool) error {
return listenUDP(acceptPort, sendloopDuration)
}
@@ -479,12 +479,12 @@ func (FilterOutputInterfaceDrop) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterOutputInterfaceDrop) ContainerAction(ip net.IP) error {
+func (FilterOutputInterfaceDrop) ContainerAction(ip net.IP, ipv6 bool) error {
ifname, ok := getInterfaceName()
if !ok {
return fmt.Errorf("no interface is present, except loopback")
}
- if err := filterTable("-A", "OUTPUT", "-p", "udp", "-o", ifname, "-j", "DROP"); err != nil {
+ if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-o", ifname, "-j", "DROP"); err != nil {
return err
}
@@ -492,7 +492,7 @@ func (FilterOutputInterfaceDrop) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterOutputInterfaceDrop) LocalAction(ip net.IP) error {
+func (FilterOutputInterfaceDrop) LocalAction(ip net.IP, ipv6 bool) error {
if err := listenUDP(acceptPort, sendloopDuration); err == nil {
return fmt.Errorf("packets should not be received on port %v, but are received", acceptPort)
}
@@ -510,8 +510,8 @@ func (FilterOutputInterface) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterOutputInterface) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "OUTPUT", "-p", "udp", "-o", "lo", "-j", "DROP"); err != nil {
+func (FilterOutputInterface) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-o", "lo", "-j", "DROP"); err != nil {
return err
}
@@ -519,7 +519,7 @@ func (FilterOutputInterface) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterOutputInterface) LocalAction(ip net.IP) error {
+func (FilterOutputInterface) LocalAction(ip net.IP, ipv6 bool) error {
return listenUDP(acceptPort, sendloopDuration)
}
@@ -533,8 +533,8 @@ func (FilterOutputInterfaceBeginsWith) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterOutputInterfaceBeginsWith) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "OUTPUT", "-p", "udp", "-o", "e+", "-j", "DROP"); err != nil {
+func (FilterOutputInterfaceBeginsWith) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-o", "e+", "-j", "DROP"); err != nil {
return err
}
@@ -542,7 +542,7 @@ func (FilterOutputInterfaceBeginsWith) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterOutputInterfaceBeginsWith) LocalAction(ip net.IP) error {
+func (FilterOutputInterfaceBeginsWith) LocalAction(ip net.IP, ipv6 bool) error {
if err := listenUDP(acceptPort, sendloopDuration); err == nil {
return fmt.Errorf("packets should not be received on port %v, but are received", acceptPort)
}
@@ -560,8 +560,8 @@ func (FilterOutputInterfaceInvertDrop) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterOutputInterfaceInvertDrop) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "OUTPUT", "-p", "tcp", "!", "-o", "lo", "-j", "DROP"); err != nil {
+func (FilterOutputInterfaceInvertDrop) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "!", "-o", "lo", "-j", "DROP"); err != nil {
return err
}
@@ -574,7 +574,7 @@ func (FilterOutputInterfaceInvertDrop) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterOutputInterfaceInvertDrop) LocalAction(ip net.IP) error {
+func (FilterOutputInterfaceInvertDrop) LocalAction(ip net.IP, ipv6 bool) error {
if err := connectTCP(ip, acceptPort, sendloopDuration); err == nil {
return fmt.Errorf("connection destined to port %d should not be accepted, but got accepted", acceptPort)
}
@@ -592,8 +592,8 @@ func (FilterOutputInterfaceInvertAccept) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (FilterOutputInterfaceInvertAccept) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "OUTPUT", "-p", "tcp", "!", "-o", "lo", "-j", "ACCEPT"); err != nil {
+func (FilterOutputInterfaceInvertAccept) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "!", "-o", "lo", "-j", "ACCEPT"); err != nil {
return err
}
@@ -602,6 +602,6 @@ func (FilterOutputInterfaceInvertAccept) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (FilterOutputInterfaceInvertAccept) LocalAction(ip net.IP) error {
+func (FilterOutputInterfaceInvertAccept) LocalAction(ip net.IP, ipv6 bool) error {
return connectTCP(ip, acceptPort, sendloopDuration)
}
diff --git a/test/iptables/iptables.go b/test/iptables/iptables.go
index 16cb4f4da..dfbd80cd1 100644
--- a/test/iptables/iptables.go
+++ b/test/iptables/iptables.go
@@ -40,10 +40,10 @@ type TestCase interface {
// ContainerAction runs inside the container. It receives the IP of the
// local process.
- ContainerAction(ip net.IP) error
+ ContainerAction(ip net.IP, ipv6 bool) error
// LocalAction runs locally. It receives the IP of the container.
- LocalAction(ip net.IP) error
+ LocalAction(ip net.IP, ipv6 bool) error
}
// Tests maps test names to TestCase.
diff --git a/test/iptables/iptables_test.go b/test/iptables/iptables_test.go
index 4fd2cb46a..fda5f694f 100644
--- a/test/iptables/iptables_test.go
+++ b/test/iptables/iptables_test.go
@@ -15,8 +15,10 @@
package iptables
import (
+ "context"
"fmt"
"net"
+ "reflect"
"testing"
"gvisor.dev/gvisor/pkg/test/dockerutil"
@@ -33,24 +35,47 @@ import (
// Container output is logged to $TEST_UNDECLARED_OUTPUTS_DIR if it exists, or
// to stderr.
func singleTest(t *testing.T, test TestCase) {
+ for _, tc := range []bool{false, true} {
+ subtest := "IPv4"
+ if tc {
+ subtest = "IPv6"
+ }
+ t.Run(subtest, func(t *testing.T) {
+ iptablesTest(t, test, tc)
+ })
+ }
+}
+
+func iptablesTest(t *testing.T, test TestCase, ipv6 bool) {
if _, ok := Tests[test.Name()]; !ok {
t.Fatalf("no test found with name %q. Has it been registered?", test.Name())
}
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
+
+ // TODO(gvisor.dev/issue/170): Skipping IPv6 gVisor tests.
+ if ipv6 && dockerutil.Runtime() != "runc" {
+ t.Skip("gVisor ip6tables not yet implemented")
+ }
// Create and start the container.
- d.CopyFiles("/runner", "test/iptables/runner/runner")
- if err := d.Spawn(dockerutil.RunOpts{
+ opts := dockerutil.RunOpts{
Image: "iptables",
CapAdd: []string{"NET_ADMIN"},
- }, "/runner/runner", "-name", test.Name()); err != nil {
+ }
+ d.CopyFiles(&opts, "/runner", "test/iptables/runner/runner")
+ args := []string{"/runner/runner", "-name", test.Name()}
+ if ipv6 {
+ args = append(args, "-ipv6")
+ }
+ if err := d.Spawn(ctx, opts, args...); err != nil {
t.Fatalf("docker run failed: %v", err)
}
// Get the container IP.
- ip, err := d.FindIP()
+ ip, err := d.FindIP(ctx, ipv6)
if err != nil {
t.Fatalf("failed to get container IP: %v", err)
}
@@ -61,14 +86,14 @@ func singleTest(t *testing.T, test TestCase) {
}
// Run our side of the test.
- if err := test.LocalAction(ip); err != nil {
+ if err := test.LocalAction(ip, ipv6); err != nil {
t.Fatalf("LocalAction failed: %v", err)
}
// Wait for the final statement. This structure has the side effect
// that all container logs will appear within the individual test
// context.
- if _, err := d.WaitForOutput(TerminalStatement, TestTimeout); err != nil {
+ if _, err := d.WaitForOutput(ctx, TerminalStatement, TestTimeout); err != nil {
t.Fatalf("test failed: %v", err)
}
}
@@ -82,7 +107,7 @@ func sendIP(ip net.IP) error {
// The container may not be listening when we first connect, so retry
// upon error.
cb := func() error {
- c, err := net.DialTCP("tcp4", nil, &contAddr)
+ c, err := net.DialTCP("tcp", nil, &contAddr)
conn = c
return err
}
@@ -259,6 +284,13 @@ func TestNATPreRedirectTCPPort(t *testing.T) {
singleTest(t, NATPreRedirectTCPPort{})
}
+func TestNATPreRedirectTCPOutgoing(t *testing.T) {
+ singleTest(t, NATPreRedirectTCPOutgoing{})
+}
+
+func TestNATOutRedirectTCPIncoming(t *testing.T) {
+ singleTest(t, NATOutRedirectTCPIncoming{})
+}
func TestNATOutRedirectUDPPort(t *testing.T) {
singleTest(t, NATOutRedirectUDPPort{})
}
@@ -302,3 +334,48 @@ func TestNATPreRedirectInvert(t *testing.T) {
func TestNATRedirectRequiresProtocol(t *testing.T) {
singleTest(t, NATRedirectRequiresProtocol{})
}
+
+func TestNATLoopbackSkipsPrerouting(t *testing.T) {
+ singleTest(t, NATLoopbackSkipsPrerouting{})
+}
+
+func TestInputSource(t *testing.T) {
+ singleTest(t, FilterInputSource{})
+}
+
+func TestInputInvertSource(t *testing.T) {
+ singleTest(t, FilterInputInvertSource{})
+}
+
+func TestFilterAddrs(t *testing.T) {
+ tcs := []struct {
+ ipv6 bool
+ addrs []string
+ want []string
+ }{
+ {
+ ipv6: false,
+ addrs: []string{"192.168.0.1", "192.168.0.2/24", "::1", "::2/128"},
+ want: []string{"192.168.0.1", "192.168.0.2"},
+ },
+ {
+ ipv6: true,
+ addrs: []string{"192.168.0.1", "192.168.0.2/24", "::1", "::2/128"},
+ want: []string{"::1", "::2"},
+ },
+ }
+
+ for _, tc := range tcs {
+ if got := filterAddrs(tc.addrs, tc.ipv6); !reflect.DeepEqual(got, tc.want) {
+ t.Errorf("%v with IPv6 %t: got %v, but wanted %v", tc.addrs, tc.ipv6, got, tc.want)
+ }
+ }
+}
+
+func TestNATPreOriginalDst(t *testing.T) {
+ singleTest(t, NATPreOriginalDst{})
+}
+
+func TestNATOutOriginalDst(t *testing.T) {
+ singleTest(t, NATOutOriginalDst{})
+}
diff --git a/test/iptables/iptables_unsafe.go b/test/iptables/iptables_unsafe.go
new file mode 100644
index 000000000..bd85a8fea
--- /dev/null
+++ b/test/iptables/iptables_unsafe.go
@@ -0,0 +1,63 @@
+// Copyright 2020 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 iptables
+
+import (
+ "fmt"
+ "syscall"
+ "unsafe"
+)
+
+type originalDstError struct {
+ errno syscall.Errno
+}
+
+func (e originalDstError) Error() string {
+ return fmt.Sprintf("errno (%d) when calling getsockopt(SO_ORIGINAL_DST): %v", int(e.errno), e.errno.Error())
+}
+
+// SO_ORIGINAL_DST gets the original destination of a redirected packet via
+// getsockopt.
+const SO_ORIGINAL_DST = 80
+
+func originalDestination4(connfd int) (syscall.RawSockaddrInet4, error) {
+ var addr syscall.RawSockaddrInet4
+ var addrLen uint32 = syscall.SizeofSockaddrInet4
+ if errno := originalDestination(connfd, syscall.SOL_IP, unsafe.Pointer(&addr), &addrLen); errno != 0 {
+ return syscall.RawSockaddrInet4{}, originalDstError{errno}
+ }
+ return addr, nil
+}
+
+func originalDestination6(connfd int) (syscall.RawSockaddrInet6, error) {
+ var addr syscall.RawSockaddrInet6
+ var addrLen uint32 = syscall.SizeofSockaddrInet6
+ if errno := originalDestination(connfd, syscall.SOL_IPV6, unsafe.Pointer(&addr), &addrLen); errno != 0 {
+ return syscall.RawSockaddrInet6{}, originalDstError{errno}
+ }
+ return addr, nil
+}
+
+func originalDestination(connfd int, level uintptr, optval unsafe.Pointer, optlen *uint32) syscall.Errno {
+ _, _, errno := syscall.Syscall6(
+ syscall.SYS_GETSOCKOPT,
+ uintptr(connfd),
+ level,
+ SO_ORIGINAL_DST,
+ uintptr(optval),
+ uintptr(unsafe.Pointer(optlen)),
+ 0)
+ return errno
+}
diff --git a/test/iptables/iptables_util.go b/test/iptables/iptables_util.go
index 7146edbb9..5125fe47b 100644
--- a/test/iptables/iptables_util.go
+++ b/test/iptables/iptables_util.go
@@ -15,30 +15,34 @@
package iptables
import (
+ "encoding/binary"
+ "errors"
"fmt"
"net"
"os/exec"
+ "strings"
"time"
"gvisor.dev/gvisor/pkg/test/testutil"
)
-const iptablesBinary = "iptables"
-const localIP = "127.0.0.1"
-
-// filterTable calls `iptables -t filter` with the given args.
-func filterTable(args ...string) error {
- return tableCmd("filter", args)
+// filterTable calls `ip{6}tables -t filter` with the given args.
+func filterTable(ipv6 bool, args ...string) error {
+ return tableCmd(ipv6, "filter", args)
}
-// natTable calls `iptables -t nat` with the given args.
-func natTable(args ...string) error {
- return tableCmd("nat", args)
+// natTable calls `ip{6}tables -t nat` with the given args.
+func natTable(ipv6 bool, args ...string) error {
+ return tableCmd(ipv6, "nat", args)
}
-func tableCmd(table string, args []string) error {
+func tableCmd(ipv6 bool, table string, args []string) error {
args = append([]string{"-t", table}, args...)
- cmd := exec.Command(iptablesBinary, args...)
+ binary := "iptables"
+ if ipv6 {
+ binary = "ip6tables"
+ }
+ cmd := exec.Command(binary, args...)
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("error running iptables with args %v\nerror: %v\noutput: %s", args, err, string(out))
}
@@ -46,18 +50,18 @@ func tableCmd(table string, args []string) error {
}
// filterTableRules is like filterTable, but runs multiple iptables commands.
-func filterTableRules(argsList [][]string) error {
- return tableRules("filter", argsList)
+func filterTableRules(ipv6 bool, argsList [][]string) error {
+ return tableRules(ipv6, "filter", argsList)
}
// natTableRules is like natTable, but runs multiple iptables commands.
-func natTableRules(argsList [][]string) error {
- return tableRules("nat", argsList)
+func natTableRules(ipv6 bool, argsList [][]string) error {
+ return tableRules(ipv6, "nat", argsList)
}
-func tableRules(table string, argsList [][]string) error {
+func tableRules(ipv6 bool, table string, argsList [][]string) error {
for _, args := range argsList {
- if err := tableCmd(table, args); err != nil {
+ if err := tableCmd(ipv6, table, args); err != nil {
return err
}
}
@@ -70,7 +74,7 @@ func listenUDP(port int, timeout time.Duration) error {
localAddr := net.UDPAddr{
Port: port,
}
- conn, err := net.ListenUDP(network, &localAddr)
+ conn, err := net.ListenUDP("udp", &localAddr)
if err != nil {
return err
}
@@ -83,17 +87,42 @@ func listenUDP(port int, timeout time.Duration) error {
// sendUDPLoop sends 1 byte UDP packets repeatedly to the IP and port specified
// over a duration.
func sendUDPLoop(ip net.IP, port int, duration time.Duration) error {
- // Send packets for a few seconds.
+ conn, err := connectUDP(ip, port)
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+ loopUDP(conn, duration)
+ return nil
+}
+
+// spawnUDPLoop works like sendUDPLoop, but returns immediately and sends
+// packets in another goroutine.
+func spawnUDPLoop(ip net.IP, port int, duration time.Duration) error {
+ conn, err := connectUDP(ip, port)
+ if err != nil {
+ return err
+ }
+ go func() {
+ defer conn.Close()
+ loopUDP(conn, duration)
+ }()
+ return nil
+}
+
+func connectUDP(ip net.IP, port int) (net.Conn, error) {
remote := net.UDPAddr{
IP: ip,
Port: port,
}
- conn, err := net.DialUDP(network, nil, &remote)
+ conn, err := net.DialUDP("udp", nil, &remote)
if err != nil {
- return err
+ return nil, err
}
- defer conn.Close()
+ return conn, nil
+}
+func loopUDP(conn net.Conn, duration time.Duration) {
to := time.After(duration)
for timedOut := false; !timedOut; {
// This may return an error (connection refused) if the remote
@@ -108,8 +137,6 @@ func sendUDPLoop(ip net.IP, port int, duration time.Duration) error {
time.Sleep(200 * time.Millisecond)
}
}
-
- return nil
}
// listenTCP listens for connections on a TCP port.
@@ -119,7 +146,7 @@ func listenTCP(port int, timeout time.Duration) error {
}
// Starts listening on port.
- lConn, err := net.ListenTCP("tcp4", &localAddr)
+ lConn, err := net.ListenTCP("tcp", &localAddr)
if err != nil {
return err
}
@@ -157,30 +184,106 @@ func connectTCP(ip net.IP, port int, timeout time.Duration) error {
return nil
}
-// localAddrs returns a list of local network interface addresses.
-func localAddrs() ([]string, error) {
+// localAddrs returns a list of local network interface addresses. When ipv6 is
+// true, only IPv6 addresses are returned. Otherwise only IPv4 addresses are
+// returned.
+func localAddrs(ipv6 bool) ([]string, error) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return nil, err
}
addrStrs := make([]string, 0, len(addrs))
for _, addr := range addrs {
- addrStrs = append(addrStrs, addr.String())
+ // Add only IPv4 or only IPv6 addresses.
+ parts := strings.Split(addr.String(), "/")
+ if len(parts) != 2 {
+ return nil, fmt.Errorf("bad interface address: %q", addr.String())
+ }
+ if isIPv6 := net.ParseIP(parts[0]).To4() == nil; isIPv6 == ipv6 {
+ addrStrs = append(addrStrs, addr.String())
+ }
+ }
+ return filterAddrs(addrStrs, ipv6), nil
+}
+
+func filterAddrs(addrs []string, ipv6 bool) []string {
+ addrStrs := make([]string, 0, len(addrs))
+ for _, addr := range addrs {
+ // Add only IPv4 or only IPv6 addresses.
+ parts := strings.Split(addr, "/")
+ if isIPv6 := net.ParseIP(parts[0]).To4() == nil; isIPv6 == ipv6 {
+ addrStrs = append(addrStrs, parts[0])
+ }
}
- return addrStrs, nil
+ return addrStrs
}
// getInterfaceName returns the name of the interface other than loopback.
func getInterfaceName() (string, bool) {
- var ifname string
+ iface, ok := getNonLoopbackInterface()
+ if !ok {
+ return "", false
+ }
+ return iface.Name, true
+}
+
+func getInterfaceAddrs(ipv6 bool) ([]net.IP, error) {
+ iface, ok := getNonLoopbackInterface()
+ if !ok {
+ return nil, errors.New("no non-loopback interface found")
+ }
+ addrs, err := iface.Addrs()
+ if err != nil {
+ return nil, err
+ }
+
+ // Get only IPv4 or IPv6 addresses.
+ ips := make([]net.IP, 0, len(addrs))
+ for _, addr := range addrs {
+ parts := strings.Split(addr.String(), "/")
+ var ip net.IP
+ // To16() returns IPv4 addresses as IPv4-mapped IPv6 addresses.
+ // So we check whether To4() returns nil to test whether the
+ // address is v4 or v6.
+ if v4 := net.ParseIP(parts[0]).To4(); ipv6 && v4 == nil {
+ ip = net.ParseIP(parts[0]).To16()
+ } else {
+ ip = v4
+ }
+ if ip != nil {
+ ips = append(ips, ip)
+ }
+ }
+ return ips, nil
+}
+
+func getNonLoopbackInterface() (net.Interface, bool) {
if interfaces, err := net.Interfaces(); err == nil {
for _, intf := range interfaces {
if intf.Name != "lo" {
- ifname = intf.Name
- break
+ return intf, true
}
}
}
+ return net.Interface{}, false
+}
+
+func htons(x uint16) uint16 {
+ buf := make([]byte, 2)
+ binary.BigEndian.PutUint16(buf, x)
+ return binary.LittleEndian.Uint16(buf)
+}
+
+func localIP(ipv6 bool) string {
+ if ipv6 {
+ return "::1"
+ }
+ return "127.0.0.1"
+}
- return ifname, ifname != ""
+func nowhereIP(ipv6 bool) string {
+ if ipv6 {
+ return "2001:db8::1"
+ }
+ return "192.0.2.1"
}
diff --git a/test/iptables/nat.go b/test/iptables/nat.go
index 0a10ce7fe..b7fea2527 100644
--- a/test/iptables/nat.go
+++ b/test/iptables/nat.go
@@ -18,16 +18,17 @@ import (
"errors"
"fmt"
"net"
+ "syscall"
"time"
)
-const (
- redirectPort = 42
-)
+const redirectPort = 42
func init() {
RegisterTestCase(NATPreRedirectUDPPort{})
RegisterTestCase(NATPreRedirectTCPPort{})
+ RegisterTestCase(NATPreRedirectTCPOutgoing{})
+ RegisterTestCase(NATOutRedirectTCPIncoming{})
RegisterTestCase(NATOutRedirectUDPPort{})
RegisterTestCase(NATOutRedirectTCPPort{})
RegisterTestCase(NATDropUDP{})
@@ -39,6 +40,9 @@ func init() {
RegisterTestCase(NATOutDontRedirectIP{})
RegisterTestCase(NATOutRedirectInvert{})
RegisterTestCase(NATRedirectRequiresProtocol{})
+ RegisterTestCase(NATLoopbackSkipsPrerouting{})
+ RegisterTestCase(NATPreOriginalDst{})
+ RegisterTestCase(NATOutOriginalDst{})
}
// NATPreRedirectUDPPort tests that packets are redirected to different port.
@@ -50,8 +54,8 @@ func (NATPreRedirectUDPPort) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (NATPreRedirectUDPPort) ContainerAction(ip net.IP) error {
- if err := natTable("-A", "PREROUTING", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil {
+func (NATPreRedirectUDPPort) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil {
return err
}
@@ -63,8 +67,8 @@ func (NATPreRedirectUDPPort) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (NATPreRedirectUDPPort) LocalAction(ip net.IP) error {
- return sendUDPLoop(ip, acceptPort, sendloopDuration)
+func (NATPreRedirectUDPPort) LocalAction(ip net.IP, ipv6 bool) error {
+ return spawnUDPLoop(ip, acceptPort, sendloopDuration)
}
// NATPreRedirectTCPPort tests that connections are redirected on specified ports.
@@ -76,8 +80,8 @@ func (NATPreRedirectTCPPort) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (NATPreRedirectTCPPort) ContainerAction(ip net.IP) error {
- if err := natTable("-A", "PREROUTING", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", dropPort), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err != nil {
+func (NATPreRedirectTCPPort) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := natTable(ipv6, "-A", "PREROUTING", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", dropPort), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err != nil {
return err
}
@@ -86,10 +90,60 @@ func (NATPreRedirectTCPPort) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (NATPreRedirectTCPPort) LocalAction(ip net.IP) error {
+func (NATPreRedirectTCPPort) LocalAction(ip net.IP, ipv6 bool) error {
return connectTCP(ip, dropPort, sendloopDuration)
}
+// NATPreRedirectTCPOutgoing verifies that outgoing TCP connections aren't
+// affected by PREROUTING connection tracking.
+type NATPreRedirectTCPOutgoing struct{}
+
+// Name implements TestCase.Name.
+func (NATPreRedirectTCPOutgoing) Name() string {
+ return "NATPreRedirectTCPOutgoing"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (NATPreRedirectTCPOutgoing) ContainerAction(ip net.IP, ipv6 bool) error {
+ // Redirect all incoming TCP traffic to a closed port.
+ if err := natTable(ipv6, "-A", "PREROUTING", "-p", "tcp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", dropPort)); err != nil {
+ return err
+ }
+
+ // Establish a connection to the host process.
+ return connectTCP(ip, acceptPort, sendloopDuration)
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (NATPreRedirectTCPOutgoing) LocalAction(ip net.IP, ipv6 bool) error {
+ return listenTCP(acceptPort, sendloopDuration)
+}
+
+// NATOutRedirectTCPIncoming verifies that incoming TCP connections aren't
+// affected by OUTPUT connection tracking.
+type NATOutRedirectTCPIncoming struct{}
+
+// Name implements TestCase.Name.
+func (NATOutRedirectTCPIncoming) Name() string {
+ return "NATOutRedirectTCPIncoming"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (NATOutRedirectTCPIncoming) ContainerAction(ip net.IP, ipv6 bool) error {
+ // Redirect all outgoing TCP traffic to a closed port.
+ if err := natTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", dropPort)); err != nil {
+ return err
+ }
+
+ // Establish a connection to the host process.
+ return listenTCP(acceptPort, sendloopDuration)
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (NATOutRedirectTCPIncoming) LocalAction(ip net.IP, ipv6 bool) error {
+ return connectTCP(ip, acceptPort, sendloopDuration)
+}
+
// NATOutRedirectUDPPort tests that packets are redirected to different port.
type NATOutRedirectUDPPort struct{}
@@ -99,13 +153,12 @@ func (NATOutRedirectUDPPort) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (NATOutRedirectUDPPort) ContainerAction(ip net.IP) error {
- dest := []byte{200, 0, 0, 1}
- return loopbackTest(dest, "-A", "OUTPUT", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort))
+func (NATOutRedirectUDPPort) ContainerAction(ip net.IP, ipv6 bool) error {
+ return loopbackTest(ipv6, net.ParseIP(nowhereIP(ipv6)), "-A", "OUTPUT", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort))
}
// LocalAction implements TestCase.LocalAction.
-func (NATOutRedirectUDPPort) LocalAction(ip net.IP) error {
+func (NATOutRedirectUDPPort) LocalAction(ip net.IP, ipv6 bool) error {
// No-op.
return nil
}
@@ -120,8 +173,8 @@ func (NATDropUDP) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (NATDropUDP) ContainerAction(ip net.IP) error {
- if err := natTable("-A", "PREROUTING", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil {
+func (NATDropUDP) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil {
return err
}
@@ -133,8 +186,8 @@ func (NATDropUDP) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (NATDropUDP) LocalAction(ip net.IP) error {
- return sendUDPLoop(ip, acceptPort, sendloopDuration)
+func (NATDropUDP) LocalAction(ip net.IP, ipv6 bool) error {
+ return spawnUDPLoop(ip, acceptPort, sendloopDuration)
}
// NATAcceptAll tests that all UDP packets are accepted.
@@ -146,8 +199,8 @@ func (NATAcceptAll) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (NATAcceptAll) ContainerAction(ip net.IP) error {
- if err := natTable("-A", "PREROUTING", "-p", "udp", "-j", "ACCEPT"); err != nil {
+func (NATAcceptAll) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-j", "ACCEPT"); err != nil {
return err
}
@@ -159,8 +212,8 @@ func (NATAcceptAll) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (NATAcceptAll) LocalAction(ip net.IP) error {
- return sendUDPLoop(ip, acceptPort, sendloopDuration)
+func (NATAcceptAll) LocalAction(ip net.IP, ipv6 bool) error {
+ return spawnUDPLoop(ip, acceptPort, sendloopDuration)
}
// NATOutRedirectIP uses iptables to select packets based on destination IP and
@@ -173,14 +226,17 @@ func (NATOutRedirectIP) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (NATOutRedirectIP) ContainerAction(ip net.IP) error {
+func (NATOutRedirectIP) ContainerAction(ip net.IP, ipv6 bool) error {
// Redirect OUTPUT packets to a listening localhost port.
- dest := net.IP([]byte{200, 0, 0, 2})
- return loopbackTest(dest, "-A", "OUTPUT", "-d", dest.String(), "-p", "udp", "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort))
+ return loopbackTest(ipv6, net.ParseIP(nowhereIP(ipv6)),
+ "-A", "OUTPUT",
+ "-d", nowhereIP(ipv6),
+ "-p", "udp",
+ "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort))
}
// LocalAction implements TestCase.LocalAction.
-func (NATOutRedirectIP) LocalAction(ip net.IP) error {
+func (NATOutRedirectIP) LocalAction(ip net.IP, ipv6 bool) error {
// No-op.
return nil
}
@@ -195,15 +251,15 @@ func (NATOutDontRedirectIP) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (NATOutDontRedirectIP) ContainerAction(ip net.IP) error {
- if err := natTable("-A", "OUTPUT", "-d", localIP, "-p", "udp", "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", dropPort)); err != nil {
+func (NATOutDontRedirectIP) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := natTable(ipv6, "-A", "OUTPUT", "-d", localIP(ipv6), "-p", "udp", "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", dropPort)); err != nil {
return err
}
return sendUDPLoop(ip, acceptPort, sendloopDuration)
}
// LocalAction implements TestCase.LocalAction.
-func (NATOutDontRedirectIP) LocalAction(ip net.IP) error {
+func (NATOutDontRedirectIP) LocalAction(ip net.IP, ipv6 bool) error {
return listenUDP(acceptPort, sendloopDuration)
}
@@ -216,15 +272,21 @@ func (NATOutRedirectInvert) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (NATOutRedirectInvert) ContainerAction(ip net.IP) error {
+func (NATOutRedirectInvert) ContainerAction(ip net.IP, ipv6 bool) error {
// Redirect OUTPUT packets to a listening localhost port.
- dest := []byte{200, 0, 0, 3}
- destStr := "200.0.0.2"
- return loopbackTest(dest, "-A", "OUTPUT", "!", "-d", destStr, "-p", "udp", "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort))
+ dest := "192.0.2.2"
+ if ipv6 {
+ dest = "2001:db8::2"
+ }
+ return loopbackTest(ipv6, net.ParseIP(nowhereIP(ipv6)),
+ "-A", "OUTPUT",
+ "!", "-d", dest,
+ "-p", "udp",
+ "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort))
}
// LocalAction implements TestCase.LocalAction.
-func (NATOutRedirectInvert) LocalAction(ip net.IP) error {
+func (NATOutRedirectInvert) LocalAction(ip net.IP, ipv6 bool) error {
// No-op.
return nil
}
@@ -239,8 +301,8 @@ func (NATPreRedirectIP) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (NATPreRedirectIP) ContainerAction(ip net.IP) error {
- addrs, err := localAddrs()
+func (NATPreRedirectIP) ContainerAction(ip net.IP, ipv6 bool) error {
+ addrs, err := localAddrs(ipv6)
if err != nil {
return err
}
@@ -249,15 +311,15 @@ func (NATPreRedirectIP) ContainerAction(ip net.IP) error {
for _, addr := range addrs {
rules = append(rules, []string{"-A", "PREROUTING", "-p", "udp", "-d", addr, "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)})
}
- if err := natTableRules(rules); err != nil {
+ if err := natTableRules(ipv6, rules); err != nil {
return err
}
return listenUDP(acceptPort, sendloopDuration)
}
// LocalAction implements TestCase.LocalAction.
-func (NATPreRedirectIP) LocalAction(ip net.IP) error {
- return sendUDPLoop(ip, dropPort, sendloopDuration)
+func (NATPreRedirectIP) LocalAction(ip net.IP, ipv6 bool) error {
+ return spawnUDPLoop(ip, dropPort, sendloopDuration)
}
// NATPreDontRedirectIP tests that iptables matching with "-d" does not match
@@ -270,16 +332,16 @@ func (NATPreDontRedirectIP) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (NATPreDontRedirectIP) ContainerAction(ip net.IP) error {
- if err := natTable("-A", "PREROUTING", "-p", "udp", "-d", localIP, "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", dropPort)); err != nil {
+func (NATPreDontRedirectIP) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "-d", localIP(ipv6), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", dropPort)); err != nil {
return err
}
return listenUDP(acceptPort, sendloopDuration)
}
// LocalAction implements TestCase.LocalAction.
-func (NATPreDontRedirectIP) LocalAction(ip net.IP) error {
- return sendUDPLoop(ip, acceptPort, sendloopDuration)
+func (NATPreDontRedirectIP) LocalAction(ip net.IP, ipv6 bool) error {
+ return spawnUDPLoop(ip, acceptPort, sendloopDuration)
}
// NATPreRedirectInvert tests that iptables can match with "! -d".
@@ -291,16 +353,16 @@ func (NATPreRedirectInvert) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (NATPreRedirectInvert) ContainerAction(ip net.IP) error {
- if err := natTable("-A", "PREROUTING", "-p", "udp", "!", "-d", localIP, "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err != nil {
+func (NATPreRedirectInvert) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := natTable(ipv6, "-A", "PREROUTING", "-p", "udp", "!", "-d", localIP(ipv6), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err != nil {
return err
}
return listenUDP(acceptPort, sendloopDuration)
}
// LocalAction implements TestCase.LocalAction.
-func (NATPreRedirectInvert) LocalAction(ip net.IP) error {
- return sendUDPLoop(ip, dropPort, sendloopDuration)
+func (NATPreRedirectInvert) LocalAction(ip net.IP, ipv6 bool) error {
+ return spawnUDPLoop(ip, dropPort, sendloopDuration)
}
// NATRedirectRequiresProtocol tests that use of the --to-ports flag requires a
@@ -313,45 +375,19 @@ func (NATRedirectRequiresProtocol) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (NATRedirectRequiresProtocol) ContainerAction(ip net.IP) error {
- if err := natTable("-A", "PREROUTING", "-d", localIP, "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err == nil {
+func (NATRedirectRequiresProtocol) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := natTable(ipv6, "-A", "PREROUTING", "-d", localIP(ipv6), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err == nil {
return errors.New("expected an error using REDIRECT --to-ports without a protocol")
}
return nil
}
// LocalAction implements TestCase.LocalAction.
-func (NATRedirectRequiresProtocol) LocalAction(ip net.IP) error {
+func (NATRedirectRequiresProtocol) LocalAction(ip net.IP, ipv6 bool) error {
// No-op.
return nil
}
-// loopbackTests runs an iptables rule and ensures that packets sent to
-// dest:dropPort are received by localhost:acceptPort.
-func loopbackTest(dest net.IP, args ...string) error {
- if err := natTable(args...); err != nil {
- return err
- }
- sendCh := make(chan error)
- listenCh := make(chan error)
- go func() {
- sendCh <- sendUDPLoop(dest, dropPort, sendloopDuration)
- }()
- go func() {
- listenCh <- listenUDP(acceptPort, sendloopDuration)
- }()
- select {
- case err := <-listenCh:
- if err != nil {
- return err
- }
- case <-time.After(sendloopDuration):
- return errors.New("timed out")
- }
- // sendCh will always take the full sendloop time.
- return <-sendCh
-}
-
// NATOutRedirectTCPPort tests that connections are redirected on specified ports.
type NATOutRedirectTCPPort struct{}
@@ -361,15 +397,14 @@ func (NATOutRedirectTCPPort) Name() string {
}
// ContainerAction implements TestCase.ContainerAction.
-func (NATOutRedirectTCPPort) ContainerAction(ip net.IP) error {
- if err := natTable("-A", "OUTPUT", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", dropPort), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err != nil {
+func (NATOutRedirectTCPPort) ContainerAction(ip net.IP, ipv6 bool) error {
+ if err := natTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", dropPort), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err != nil {
return err
}
timeout := 20 * time.Second
- dest := []byte{127, 0, 0, 1}
localAddr := net.TCPAddr{
- IP: dest,
+ IP: net.ParseIP(localIP(ipv6)),
Port: acceptPort,
}
@@ -397,6 +432,213 @@ func (NATOutRedirectTCPPort) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (NATOutRedirectTCPPort) LocalAction(ip net.IP) error {
+func (NATOutRedirectTCPPort) LocalAction(ip net.IP, ipv6 bool) error {
return nil
}
+
+// NATLoopbackSkipsPrerouting tests that packets sent via loopback aren't
+// affected by PREROUTING rules.
+type NATLoopbackSkipsPrerouting struct{}
+
+// Name implements TestCase.Name.
+func (NATLoopbackSkipsPrerouting) Name() string {
+ return "NATLoopbackSkipsPrerouting"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (NATLoopbackSkipsPrerouting) ContainerAction(ip net.IP, ipv6 bool) error {
+ // Redirect anything sent to localhost to an unused port.
+ dest := []byte{127, 0, 0, 1}
+ if err := natTable(ipv6, "-A", "PREROUTING", "-p", "tcp", "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", dropPort)); err != nil {
+ return err
+ }
+
+ // Establish a connection via localhost. If the PREROUTING rule did apply to
+ // loopback traffic, the connection would fail.
+ sendCh := make(chan error)
+ go func() {
+ sendCh <- connectTCP(dest, acceptPort, sendloopDuration)
+ }()
+
+ if err := listenTCP(acceptPort, sendloopDuration); err != nil {
+ return err
+ }
+ return <-sendCh
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (NATLoopbackSkipsPrerouting) LocalAction(ip net.IP, ipv6 bool) error {
+ // No-op.
+ return nil
+}
+
+// NATPreOriginalDst tests that SO_ORIGINAL_DST returns the pre-NAT destination
+// of PREROUTING NATted packets.
+type NATPreOriginalDst struct{}
+
+// Name implements TestCase.Name.
+func (NATPreOriginalDst) Name() string {
+ return "NATPreOriginalDst"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (NATPreOriginalDst) ContainerAction(ip net.IP, ipv6 bool) error {
+ // Redirect incoming TCP connections to acceptPort.
+ if err := natTable(ipv6, "-A", "PREROUTING",
+ "-p", "tcp",
+ "--destination-port", fmt.Sprintf("%d", dropPort),
+ "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort)); err != nil {
+ return err
+ }
+
+ addrs, err := getInterfaceAddrs(ipv6)
+ if err != nil {
+ return err
+ }
+ return listenForRedirectedConn(ipv6, addrs)
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (NATPreOriginalDst) LocalAction(ip net.IP, ipv6 bool) error {
+ return connectTCP(ip, dropPort, sendloopDuration)
+}
+
+// NATOutOriginalDst tests that SO_ORIGINAL_DST returns the pre-NAT destination
+// of OUTBOUND NATted packets.
+type NATOutOriginalDst struct{}
+
+// Name implements TestCase.Name.
+func (NATOutOriginalDst) Name() string {
+ return "NATOutOriginalDst"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (NATOutOriginalDst) ContainerAction(ip net.IP, ipv6 bool) error {
+ // Redirect incoming TCP connections to acceptPort.
+ if err := natTable(ipv6, "-A", "OUTPUT", "-p", "tcp", "-j", "REDIRECT", "--to-port", fmt.Sprintf("%d", acceptPort)); err != nil {
+ return err
+ }
+
+ connCh := make(chan error)
+ go func() {
+ connCh <- connectTCP(ip, dropPort, sendloopDuration)
+ }()
+
+ if err := listenForRedirectedConn(ipv6, []net.IP{ip}); err != nil {
+ return err
+ }
+ return <-connCh
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (NATOutOriginalDst) LocalAction(ip net.IP, ipv6 bool) error {
+ // No-op.
+ return nil
+}
+
+func listenForRedirectedConn(ipv6 bool, originalDsts []net.IP) error {
+ // The net package doesn't give guarantee access to the connection's
+ // underlying FD, and thus we cannot call getsockopt. We have to use
+ // traditional syscalls for SO_ORIGINAL_DST.
+
+ // Create the listening socket, bind, listen, and accept.
+ family := syscall.AF_INET
+ if ipv6 {
+ family = syscall.AF_INET6
+ }
+ sockfd, err := syscall.Socket(family, syscall.SOCK_STREAM, 0)
+ if err != nil {
+ return err
+ }
+ defer syscall.Close(sockfd)
+
+ var bindAddr syscall.Sockaddr
+ if ipv6 {
+ bindAddr = &syscall.SockaddrInet6{
+ Port: acceptPort,
+ Addr: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // in6addr_any
+ }
+ } else {
+ bindAddr = &syscall.SockaddrInet4{
+ Port: acceptPort,
+ Addr: [4]byte{0, 0, 0, 0}, // INADDR_ANY
+ }
+ }
+ if err := syscall.Bind(sockfd, bindAddr); err != nil {
+ return err
+ }
+
+ if err := syscall.Listen(sockfd, 1); err != nil {
+ return err
+ }
+
+ connfd, _, err := syscall.Accept(sockfd)
+ if err != nil {
+ return err
+ }
+ defer syscall.Close(connfd)
+
+ // Verify that, despite listening on acceptPort, SO_ORIGINAL_DST
+ // indicates the packet was sent to originalDst:dropPort.
+ if ipv6 {
+ got, err := originalDestination6(connfd)
+ if err != nil {
+ return err
+ }
+ // The original destination could be any of our IPs.
+ for _, dst := range originalDsts {
+ want := syscall.RawSockaddrInet6{
+ Family: syscall.AF_INET6,
+ Port: htons(dropPort),
+ }
+ copy(want.Addr[:], dst.To16())
+ if got == want {
+ return nil
+ }
+ }
+ return fmt.Errorf("SO_ORIGINAL_DST returned %+v, but wanted one of %+v (note: port numbers are in network byte order)", got, originalDsts)
+ } else {
+ got, err := originalDestination4(connfd)
+ if err != nil {
+ return err
+ }
+ // The original destination could be any of our IPs.
+ for _, dst := range originalDsts {
+ want := syscall.RawSockaddrInet4{
+ Family: syscall.AF_INET,
+ Port: htons(dropPort),
+ }
+ copy(want.Addr[:], dst.To4())
+ if got == want {
+ return nil
+ }
+ }
+ return fmt.Errorf("SO_ORIGINAL_DST returned %+v, but wanted one of %+v (note: port numbers are in network byte order)", got, originalDsts)
+ }
+}
+
+// loopbackTests runs an iptables rule and ensures that packets sent to
+// dest:dropPort are received by localhost:acceptPort.
+func loopbackTest(ipv6 bool, dest net.IP, args ...string) error {
+ if err := natTable(ipv6, args...); err != nil {
+ return err
+ }
+ sendCh := make(chan error)
+ listenCh := make(chan error)
+ go func() {
+ sendCh <- sendUDPLoop(dest, dropPort, sendloopDuration)
+ }()
+ go func() {
+ listenCh <- listenUDP(acceptPort, sendloopDuration)
+ }()
+ select {
+ case err := <-listenCh:
+ if err != nil {
+ return err
+ }
+ case <-time.After(sendloopDuration):
+ return errors.New("timed out")
+ }
+ // sendCh will always take the full sendloop time.
+ return <-sendCh
+}
diff --git a/test/iptables/runner/main.go b/test/iptables/runner/main.go
index 6f77c0684..69d3ef121 100644
--- a/test/iptables/runner/main.go
+++ b/test/iptables/runner/main.go
@@ -24,7 +24,10 @@ import (
"gvisor.dev/gvisor/test/iptables"
)
-var name = flag.String("name", "", "name of the test to run")
+var (
+ name = flag.String("name", "", "name of the test to run")
+ ipv6 = flag.Bool("ipv6", false, "whether the test utilizes ip6tables")
+)
func main() {
flag.Parse()
@@ -43,7 +46,7 @@ func main() {
}
// Run the test.
- if err := test.ContainerAction(ip); err != nil {
+ if err := test.ContainerAction(ip, *ipv6); err != nil {
log.Fatalf("Failed running test %q: %v", *name, err)
}
@@ -57,7 +60,7 @@ func getIP() (net.IP, error) {
localAddr := net.TCPAddr{
Port: iptables.IPExchangePort,
}
- listener, err := net.ListenTCP("tcp4", &localAddr)
+ listener, err := net.ListenTCP("tcp", &localAddr)
if err != nil {
return net.IP{}, fmt.Errorf("failed listening for IP: %v", err)
}
diff --git a/test/packetdrill/defs.bzl b/test/packetdrill/defs.bzl
index f499c177b..fc28ce9ba 100644
--- a/test/packetdrill/defs.bzl
+++ b/test/packetdrill/defs.bzl
@@ -26,7 +26,7 @@ def _packetdrill_test_impl(ctx):
transitive_files = depset()
if hasattr(ctx.attr._test_runner, "data_runfiles"):
- transitive_files = depset(ctx.attr._test_runner.data_runfiles.files)
+ transitive_files = ctx.attr._test_runner.data_runfiles.files
runfiles = ctx.runfiles(
files = [test_runner] + ctx.files._init_script + ctx.files.scripts,
transitive_files = transitive_files,
@@ -60,11 +60,15 @@ _packetdrill_test = rule(
implementation = _packetdrill_test_impl,
)
-_PACKETDRILL_TAGS = ["local", "manual"]
+PACKETDRILL_TAGS = [
+ "local",
+ "manual",
+ "packetdrill",
+]
def packetdrill_linux_test(name, **kwargs):
if "tags" not in kwargs:
- kwargs["tags"] = _PACKETDRILL_TAGS
+ kwargs["tags"] = PACKETDRILL_TAGS
_packetdrill_test(
name = name,
flags = ["--dut_platform", "linux"],
@@ -73,7 +77,7 @@ def packetdrill_linux_test(name, **kwargs):
def packetdrill_netstack_test(name, **kwargs):
if "tags" not in kwargs:
- kwargs["tags"] = _PACKETDRILL_TAGS
+ kwargs["tags"] = PACKETDRILL_TAGS
_packetdrill_test(
name = name,
# This is the default runtime unless
diff --git a/test/packetimpact/README.md b/test/packetimpact/README.md
index a82ad996a..f46c67a0c 100644
--- a/test/packetimpact/README.md
+++ b/test/packetimpact/README.md
@@ -18,6 +18,27 @@ Packetimpact aims to provide:
* **Control-flow** like for loops, conditionals, and variables.
* **Flexibilty** to specify every byte in a packet or use multiple sockets.
+## How to run packetimpact tests?
+
+Build the test container image by running the following at the root of the
+repository:
+
+```bash
+$ make load-packetimpact
+```
+
+Run a test, e.g. `fin_wait2_timeout`, against Linux:
+
+```bash
+$ bazel test //test/packetimpact/tests:fin_wait2_timeout_linux_test
+```
+
+Run the same test, but against gVisor:
+
+```bash
+$ bazel test //test/packetimpact/tests:fin_wait2_timeout_netstack_test
+```
+
## When to use packetimpact?
There are a few ways to write networking tests for gVisor currently:
diff --git a/test/packetimpact/dut/posix_server.cc b/test/packetimpact/dut/posix_server.cc
index dc3024f44..29d4cc6fe 100644
--- a/test/packetimpact/dut/posix_server.cc
+++ b/test/packetimpact/dut/posix_server.cc
@@ -53,7 +53,10 @@
response_in6->set_flowinfo(ntohl(addr_in6->sin6_flowinfo));
response_in6->mutable_addr()->assign(
reinterpret_cast<const char *>(&addr_in6->sin6_addr.s6_addr), 16);
- response_in6->set_scope_id(ntohl(addr_in6->sin6_scope_id));
+ // sin6_scope_id is stored in host byte order.
+ //
+ // https://www.gnu.org/software/libc/manual/html_node/Internet-Address-Formats.html
+ response_in6->set_scope_id(addr_in6->sin6_scope_id);
return ::grpc::Status::OK;
}
}
@@ -61,7 +64,7 @@
}
::grpc::Status proto_to_sockaddr(const posix_server::Sockaddr &sockaddr_proto,
- sockaddr_storage *addr) {
+ sockaddr_storage *addr, socklen_t *addr_len) {
switch (sockaddr_proto.sockaddr_case()) {
case posix_server::Sockaddr::SockaddrCase::kIn: {
auto proto_in = sockaddr_proto.in();
@@ -74,6 +77,7 @@
addr_in->sin_port = htons(proto_in.port());
proto_in.addr().copy(reinterpret_cast<char *>(&addr_in->sin_addr.s_addr),
4);
+ *addr_len = sizeof(*addr_in);
break;
}
case posix_server::Sockaddr::SockaddrCase::kIn6: {
@@ -88,7 +92,11 @@
addr_in6->sin6_flowinfo = htonl(proto_in6.flowinfo());
proto_in6.addr().copy(
reinterpret_cast<char *>(&addr_in6->sin6_addr.s6_addr), 16);
- addr_in6->sin6_scope_id = htonl(proto_in6.scope_id());
+ // sin6_scope_id is stored in host byte order.
+ //
+ // https://www.gnu.org/software/libc/manual/html_node/Internet-Address-Formats.html
+ addr_in6->sin6_scope_id = proto_in6.scope_id();
+ *addr_len = sizeof(*addr_in6);
break;
}
case posix_server::Sockaddr::SockaddrCase::SOCKADDR_NOT_SET:
@@ -120,13 +128,14 @@ class PosixImpl final : public posix_server::Posix::Service {
}
sockaddr_storage addr;
- auto err = proto_to_sockaddr(request->addr(), &addr);
+ socklen_t addr_len;
+ auto err = proto_to_sockaddr(request->addr(), &addr, &addr_len);
if (!err.ok()) {
return err;
}
- response->set_ret(bind(request->sockfd(),
- reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
+ response->set_ret(
+ bind(request->sockfd(), reinterpret_cast<sockaddr *>(&addr), addr_len));
response->set_errno_(errno);
return ::grpc::Status::OK;
}
@@ -147,13 +156,22 @@ class PosixImpl final : public posix_server::Posix::Service {
"Missing address");
}
sockaddr_storage addr;
- auto err = proto_to_sockaddr(request->addr(), &addr);
+ socklen_t addr_len;
+ auto err = proto_to_sockaddr(request->addr(), &addr, &addr_len);
if (!err.ok()) {
return err;
}
- response->set_ret(connect(
- request->sockfd(), reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
+ response->set_ret(connect(request->sockfd(),
+ reinterpret_cast<sockaddr *>(&addr), addr_len));
+ response->set_errno_(errno);
+ return ::grpc::Status::OK;
+ }
+
+ ::grpc::Status Fcntl(grpc_impl::ServerContext *context,
+ const ::posix_server::FcntlRequest *request,
+ ::posix_server::FcntlResponse *response) override {
+ response->set_ret(::fcntl(request->fd(), request->cmd(), request->arg()));
response->set_errno_(errno);
return ::grpc::Status::OK;
}
@@ -237,14 +255,15 @@ class PosixImpl final : public posix_server::Posix::Service {
"Missing address");
}
sockaddr_storage addr;
- auto err = proto_to_sockaddr(request->dest_addr(), &addr);
+ socklen_t addr_len;
+ auto err = proto_to_sockaddr(request->dest_addr(), &addr, &addr_len);
if (!err.ok()) {
return err;
}
- response->set_ret(::sendto(
- request->sockfd(), request->buf().data(), request->buf().size(),
- request->flags(), reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
+ response->set_ret(::sendto(request->sockfd(), request->buf().data(),
+ request->buf().size(), request->flags(),
+ reinterpret_cast<sockaddr *>(&addr), addr_len));
response->set_errno_(errno);
return ::grpc::Status::OK;
}
diff --git a/test/packetimpact/netdevs/BUILD b/test/packetimpact/netdevs/BUILD
new file mode 100644
index 000000000..8d1193fed
--- /dev/null
+++ b/test/packetimpact/netdevs/BUILD
@@ -0,0 +1,23 @@
+load("//tools:defs.bzl", "go_library", "go_test")
+
+package(
+ licenses = ["notice"],
+)
+
+go_library(
+ name = "netdevs",
+ srcs = ["netdevs.go"],
+ visibility = ["//test/packetimpact:__subpackages__"],
+ deps = [
+ "//pkg/tcpip",
+ "//pkg/tcpip/header",
+ ],
+)
+
+go_test(
+ name = "netdevs_test",
+ size = "small",
+ srcs = ["netdevs_test.go"],
+ library = ":netdevs",
+ deps = ["@com_github_google_go_cmp//cmp:go_default_library"],
+)
diff --git a/test/packetimpact/netdevs/netdevs.go b/test/packetimpact/netdevs/netdevs.go
new file mode 100644
index 000000000..eecfe0730
--- /dev/null
+++ b/test/packetimpact/netdevs/netdevs.go
@@ -0,0 +1,115 @@
+// Copyright 2020 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 netdevs contains utilities for working with network devices.
+package netdevs
+
+import (
+ "fmt"
+ "net"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "gvisor.dev/gvisor/pkg/tcpip"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+)
+
+// A DeviceInfo represents a network device.
+type DeviceInfo struct {
+ ID uint32
+ MAC net.HardwareAddr
+ IPv4Addr net.IP
+ IPv4Net *net.IPNet
+ IPv6Addr net.IP
+ IPv6Net *net.IPNet
+}
+
+var (
+ deviceLine = regexp.MustCompile(`^\s*(\d+): (\w+)`)
+ linkLine = regexp.MustCompile(`^\s*link/\w+ ([0-9a-fA-F:]+)`)
+ inetLine = regexp.MustCompile(`^\s*inet ([0-9./]+)`)
+ inet6Line = regexp.MustCompile(`^\s*inet6 ([0-9a-fA-Z:/]+)`)
+)
+
+// ParseDevices parses the output from `ip addr show` into a map from device
+// name to information about the device.
+//
+// Note: if multiple IPv6 addresses are assigned to a device, the last address
+// displayed by `ip addr show` will be used. This is fine for packetimpact
+// because we will always only have at most one IPv6 address assigned to each
+// device.
+func ParseDevices(cmdOutput string) (map[string]DeviceInfo, error) {
+ var currentDevice string
+ var currentInfo DeviceInfo
+ deviceInfos := make(map[string]DeviceInfo)
+ for _, line := range strings.Split(cmdOutput, "\n") {
+ if m := deviceLine.FindStringSubmatch(line); m != nil {
+ if currentDevice != "" {
+ deviceInfos[currentDevice] = currentInfo
+ }
+ id, err := strconv.ParseUint(m[1], 10, 32)
+ if err != nil {
+ return nil, fmt.Errorf("parsing device ID %s: %w", m[1], err)
+ }
+ currentInfo = DeviceInfo{ID: uint32(id)}
+ currentDevice = m[2]
+ } else if m := linkLine.FindStringSubmatch(line); m != nil {
+ mac, err := net.ParseMAC(m[1])
+ if err != nil {
+ return nil, err
+ }
+ currentInfo.MAC = mac
+ } else if m := inetLine.FindStringSubmatch(line); m != nil {
+ ipv4Addr, ipv4Net, err := net.ParseCIDR(m[1])
+ if err != nil {
+ return nil, err
+ }
+ currentInfo.IPv4Addr = ipv4Addr
+ currentInfo.IPv4Net = ipv4Net
+ } else if m := inet6Line.FindStringSubmatch(line); m != nil {
+ ipv6Addr, ipv6Net, err := net.ParseCIDR(m[1])
+ if err != nil {
+ return nil, err
+ }
+ currentInfo.IPv6Addr = ipv6Addr
+ currentInfo.IPv6Net = ipv6Net
+ }
+ }
+ if currentDevice != "" {
+ deviceInfos[currentDevice] = currentInfo
+ }
+ return deviceInfos, nil
+}
+
+// MACToIP converts the MAC address to an IPv6 link local address as described
+// in RFC 4291 page 20: https://tools.ietf.org/html/rfc4291#page-20
+func MACToIP(mac net.HardwareAddr) net.IP {
+ addr := make([]byte, header.IPv6AddressSize)
+ addr[0] = 0xfe
+ addr[1] = 0x80
+ header.EthernetAdddressToModifiedEUI64IntoBuf(tcpip.LinkAddress(mac), addr[8:])
+ return net.IP(addr)
+}
+
+// FindDeviceByIP finds a DeviceInfo and device name from an IP address in the
+// output of ParseDevices.
+func FindDeviceByIP(ip net.IP, devices map[string]DeviceInfo) (string, DeviceInfo, error) {
+ for dev, info := range devices {
+ if info.IPv4Addr.Equal(ip) {
+ return dev, info, nil
+ }
+ }
+ return "", DeviceInfo{}, fmt.Errorf("can't find %s on any interface", ip)
+}
diff --git a/test/packetimpact/netdevs/netdevs_test.go b/test/packetimpact/netdevs/netdevs_test.go
new file mode 100644
index 000000000..24ad12198
--- /dev/null
+++ b/test/packetimpact/netdevs/netdevs_test.go
@@ -0,0 +1,227 @@
+// Copyright 2020 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 netdevs
+
+import (
+ "fmt"
+ "net"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+)
+
+func mustParseMAC(s string) net.HardwareAddr {
+ mac, err := net.ParseMAC(s)
+ if err != nil {
+ panic(fmt.Sprintf("failed to parse test MAC %q: %s", s, err))
+ }
+ return mac
+}
+
+func TestParseDevices(t *testing.T) {
+ for _, v := range []struct {
+ desc string
+ cmdOutput string
+ want map[string]DeviceInfo
+ }{
+ {
+ desc: "v4 and v6",
+ cmdOutput: `
+1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
+ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
+ inet 127.0.0.1/8 scope host lo
+ valid_lft forever preferred_lft forever
+ inet6 ::1/128 scope host
+ valid_lft forever preferred_lft forever
+2613: eth0@if2614: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
+ link/ether 02:42:c0:a8:09:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
+ inet 192.168.9.2/24 brd 192.168.9.255 scope global eth0
+ valid_lft forever preferred_lft forever
+ inet6 fe80::42:c0ff:fea8:902/64 scope link tentative
+ valid_lft forever preferred_lft forever
+2615: eth2@if2616: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
+ link/ether 02:42:df:f5:e1:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
+ inet 223.245.225.10/24 brd 223.245.225.255 scope global eth2
+ valid_lft forever preferred_lft forever
+ inet6 fe80::42:dfff:fef5:e10a/64 scope link tentative
+ valid_lft forever preferred_lft forever
+2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
+ link/ether 02:42:da:33:13:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
+ inet 218.51.19.10/24 brd 218.51.19.255 scope global eth1
+ valid_lft forever preferred_lft forever
+ inet6 fe80::42:daff:fe33:130a/64 scope link tentative
+ valid_lft forever preferred_lft forever`,
+ want: map[string]DeviceInfo{
+ "lo": DeviceInfo{
+ ID: 1,
+ MAC: mustParseMAC("00:00:00:00:00:00"),
+ IPv4Addr: net.IPv4(127, 0, 0, 1),
+ IPv4Net: &net.IPNet{
+ IP: net.IPv4(127, 0, 0, 0),
+ Mask: net.CIDRMask(8, 32),
+ },
+ IPv6Addr: net.ParseIP("::1"),
+ IPv6Net: &net.IPNet{
+ IP: net.ParseIP("::1"),
+ Mask: net.CIDRMask(128, 128),
+ },
+ },
+ "eth0": DeviceInfo{
+ ID: 2613,
+ MAC: mustParseMAC("02:42:c0:a8:09:02"),
+ IPv4Addr: net.IPv4(192, 168, 9, 2),
+ IPv4Net: &net.IPNet{
+ IP: net.IPv4(192, 168, 9, 0),
+ Mask: net.CIDRMask(24, 32),
+ },
+ IPv6Addr: net.ParseIP("fe80::42:c0ff:fea8:902"),
+ IPv6Net: &net.IPNet{
+ IP: net.ParseIP("fe80::"),
+ Mask: net.CIDRMask(64, 128),
+ },
+ },
+ "eth1": DeviceInfo{
+ ID: 2617,
+ MAC: mustParseMAC("02:42:da:33:13:0a"),
+ IPv4Addr: net.IPv4(218, 51, 19, 10),
+ IPv4Net: &net.IPNet{
+ IP: net.IPv4(218, 51, 19, 0),
+ Mask: net.CIDRMask(24, 32),
+ },
+ IPv6Addr: net.ParseIP("fe80::42:daff:fe33:130a"),
+ IPv6Net: &net.IPNet{
+ IP: net.ParseIP("fe80::"),
+ Mask: net.CIDRMask(64, 128),
+ },
+ },
+ "eth2": DeviceInfo{
+ ID: 2615,
+ MAC: mustParseMAC("02:42:df:f5:e1:0a"),
+ IPv4Addr: net.IPv4(223, 245, 225, 10),
+ IPv4Net: &net.IPNet{
+ IP: net.IPv4(223, 245, 225, 0),
+ Mask: net.CIDRMask(24, 32),
+ },
+ IPv6Addr: net.ParseIP("fe80::42:dfff:fef5:e10a"),
+ IPv6Net: &net.IPNet{
+ IP: net.ParseIP("fe80::"),
+ Mask: net.CIDRMask(64, 128),
+ },
+ },
+ },
+ },
+ {
+ desc: "v4 only",
+ cmdOutput: `
+2613: eth0@if2614: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
+ link/ether 02:42:c0:a8:09:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
+ inet 192.168.9.2/24 brd 192.168.9.255 scope global eth0
+ valid_lft forever preferred_lft forever`,
+ want: map[string]DeviceInfo{
+ "eth0": DeviceInfo{
+ ID: 2613,
+ MAC: mustParseMAC("02:42:c0:a8:09:02"),
+ IPv4Addr: net.IPv4(192, 168, 9, 2),
+ IPv4Net: &net.IPNet{
+ IP: net.IPv4(192, 168, 9, 0),
+ Mask: net.CIDRMask(24, 32),
+ },
+ },
+ },
+ },
+ {
+ desc: "v6 only",
+ cmdOutput: `
+2615: eth2@if2616: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
+ link/ether 02:42:df:f5:e1:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
+ inet6 fe80::42:dfff:fef5:e10a/64 scope link tentative
+ valid_lft forever preferred_lft forever`,
+ want: map[string]DeviceInfo{
+ "eth2": DeviceInfo{
+ ID: 2615,
+ MAC: mustParseMAC("02:42:df:f5:e1:0a"),
+ IPv6Addr: net.ParseIP("fe80::42:dfff:fef5:e10a"),
+ IPv6Net: &net.IPNet{
+ IP: net.ParseIP("fe80::"),
+ Mask: net.CIDRMask(64, 128),
+ },
+ },
+ },
+ },
+ } {
+ t.Run(v.desc, func(t *testing.T) {
+ got, err := ParseDevices(v.cmdOutput)
+ if err != nil {
+ t.Errorf("ParseDevices(\n%s\n) got unexpected error: %s", v.cmdOutput, err)
+ }
+ if diff := cmp.Diff(v.want, got); diff != "" {
+ t.Errorf("ParseDevices(\n%s\n) got output diff (-want, +got):\n%s", v.cmdOutput, diff)
+ }
+ })
+ }
+}
+
+func TestParseDevicesErrors(t *testing.T) {
+ for _, v := range []struct {
+ desc string
+ cmdOutput string
+ }{
+ {
+ desc: "invalid MAC addr",
+ cmdOutput: `
+2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
+ link/ether 02:42:da:33:13:0a:ffffffff brd ff:ff:ff:ff:ff:ff link-netnsid 0
+ inet 218.51.19.10/24 brd 218.51.19.255 scope global eth1
+ valid_lft forever preferred_lft forever
+ inet6 fe80::42:daff:fe33:130a/64 scope link tentative
+ valid_lft forever preferred_lft forever`,
+ },
+ {
+ desc: "invalid v4 addr",
+ cmdOutput: `
+2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
+ link/ether 02:42:da:33:13:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
+ inet 1234.4321.424242.0/24 brd 218.51.19.255 scope global eth1
+ valid_lft forever preferred_lft forever
+ inet6 fe80::42:daff:fe33:130a/64 scope link tentative
+ valid_lft forever preferred_lft forever`,
+ },
+ {
+ desc: "invalid v6 addr",
+ cmdOutput: `
+2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
+ link/ether 02:42:da:33:13:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
+ inet 218.51.19.10/24 brd 218.51.19.255 scope global eth1
+ valid_lft forever preferred_lft forever
+ inet6 fe80:ffffffff::42:daff:fe33:130a/64 scope link tentative
+ valid_lft forever preferred_lft forever`,
+ },
+ {
+ desc: "invalid CIDR missing prefixlen",
+ cmdOutput: `
+2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
+ link/ether 02:42:da:33:13:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
+ inet 218.51.19.10 brd 218.51.19.255 scope global eth1
+ valid_lft forever preferred_lft forever
+ inet6 fe80::42:daff:fe33:130a scope link tentative
+ valid_lft forever preferred_lft forever`,
+ },
+ } {
+ t.Run(v.desc, func(t *testing.T) {
+ if _, err := ParseDevices(v.cmdOutput); err == nil {
+ t.Errorf("ParseDevices(\n%s\n) succeeded unexpectedly, want error", v.cmdOutput)
+ }
+ })
+ }
+}
diff --git a/test/packetimpact/proto/posix_server.proto b/test/packetimpact/proto/posix_server.proto
index 9dca563f1..ccd20b10d 100644
--- a/test/packetimpact/proto/posix_server.proto
+++ b/test/packetimpact/proto/posix_server.proto
@@ -91,6 +91,17 @@ message ConnectResponse {
int32 errno_ = 2; // "errno" may fail to compile in c++.
}
+message FcntlRequest {
+ int32 fd = 1;
+ int32 cmd = 2;
+ int32 arg = 3;
+}
+
+message FcntlResponse {
+ int32 ret = 1;
+ int32 errno_ = 2;
+}
+
message GetSockNameRequest {
int32 sockfd = 1;
}
@@ -139,7 +150,7 @@ message SendRequest {
message SendResponse {
int32 ret = 1;
- int32 errno_ = 2;
+ int32 errno_ = 2; // "errno" may fail to compile in c++.
}
message SendToRequest {
@@ -198,6 +209,8 @@ service Posix {
rpc Close(CloseRequest) returns (CloseResponse);
// Call connect() on the DUT.
rpc Connect(ConnectRequest) returns (ConnectResponse);
+ // Call fcntl() on the DUT.
+ rpc Fcntl(FcntlRequest) returns (FcntlResponse);
// Call getsockname() on the DUT.
rpc GetSockName(GetSockNameRequest) returns (GetSockNameResponse);
// Call getsockopt() on the DUT.
diff --git a/test/packetimpact/runner/BUILD b/test/packetimpact/runner/BUILD
new file mode 100644
index 000000000..bad4f0183
--- /dev/null
+++ b/test/packetimpact/runner/BUILD
@@ -0,0 +1,21 @@
+load("//tools:defs.bzl", "go_test")
+
+package(
+ default_visibility = ["//test/packetimpact:__subpackages__"],
+ licenses = ["notice"],
+)
+
+go_test(
+ name = "packetimpact_test",
+ srcs = ["packetimpact_test.go"],
+ tags = [
+ # Not intended to be run directly.
+ "local",
+ "manual",
+ ],
+ deps = [
+ "//pkg/test/dockerutil",
+ "//test/packetimpact/netdevs",
+ "@com_github_docker_docker//api/types/mount:go_default_library",
+ ],
+)
diff --git a/test/packetimpact/tests/defs.bzl b/test/packetimpact/runner/defs.bzl
index 45dce64ab..79b3c9162 100644
--- a/test/packetimpact/tests/defs.bzl
+++ b/test/packetimpact/runner/defs.bzl
@@ -11,23 +11,21 @@ def _packetimpact_test_impl(ctx):
# permission problems, because all runfiles may not be owned by the
# current user, and no other users will be mapped in that namespace.
# Make sure that everything is readable here.
- "find . -type f -exec chmod a+rx {} \\;",
- "find . -type d -exec chmod a+rx {} \\;",
- "%s %s --posix_server_binary %s --testbench_binary %s $@\n" % (
+ "find . -type f -or -type d -exec chmod a+rx {} \\;",
+ "%s %s --testbench_binary %s $@\n" % (
test_runner.short_path,
" ".join(ctx.attr.flags),
- ctx.files._posix_server_binary[0].short_path,
ctx.files.testbench_binary[0].short_path,
),
])
ctx.actions.write(bench, bench_content, is_executable = True)
- transitive_files = depset()
+ transitive_files = []
if hasattr(ctx.attr._test_runner, "data_runfiles"):
- transitive_files = depset(ctx.attr._test_runner.data_runfiles.files)
+ transitive_files.append(ctx.attr._test_runner.data_runfiles.files)
runfiles = ctx.runfiles(
files = [test_runner] + ctx.files.testbench_binary + ctx.files._posix_server_binary,
- transitive_files = transitive_files,
+ transitive_files = depset(transitive = transitive_files),
collect_default = True,
collect_data = True,
)
@@ -38,7 +36,7 @@ _packetimpact_test = rule(
"_test_runner": attr.label(
executable = True,
cfg = "target",
- default = ":test_runner",
+ default = ":packetimpact_test",
),
"_posix_server_binary": attr.label(
cfg = "target",
@@ -57,7 +55,11 @@ _packetimpact_test = rule(
implementation = _packetimpact_test_impl,
)
-PACKETIMPACT_TAGS = ["local", "manual"]
+PACKETIMPACT_TAGS = [
+ "local",
+ "manual",
+ "packetimpact",
+]
def packetimpact_linux_test(
name,
@@ -69,6 +71,7 @@ def packetimpact_linux_test(
Args:
name: name of the test
testbench_binary: the testbench binary
+ expect_failure: the test must fail
**kwargs: all the other args, forwarded to _packetimpact_test
"""
expect_failure_flag = ["--expect_failure"] if expect_failure else []
@@ -76,7 +79,7 @@ def packetimpact_linux_test(
name = name + "_linux_test",
testbench_binary = testbench_binary,
flags = ["--dut_platform", "linux"] + expect_failure_flag,
- tags = PACKETIMPACT_TAGS + ["packetimpact"],
+ tags = PACKETIMPACT_TAGS,
**kwargs
)
@@ -102,7 +105,7 @@ def packetimpact_netstack_test(
# This is the default runtime unless
# "--test_arg=--runtime=OTHER_RUNTIME" is used to override the value.
flags = ["--dut_platform", "netstack", "--runtime=runsc-d"] + expect_failure_flag,
- tags = PACKETIMPACT_TAGS + ["packetimpact"],
+ tags = PACKETIMPACT_TAGS,
**kwargs
)
@@ -113,8 +116,8 @@ def packetimpact_go_test(name, size = "small", pure = True, expect_linux_failure
name: name of the test
size: size of the test
pure: make a static go binary
- expect_linux_failure: expect the test to fail for Linux
- expect_netstack_failure: expect the test to fail for Netstack
+ expect_linux_failure: the test must fail for Linux
+ expect_netstack_failure: the test must fail for Netstack
**kwargs: all the other args, forwarded to go_test
"""
testbench_binary = name + "_test"
@@ -122,7 +125,10 @@ def packetimpact_go_test(name, size = "small", pure = True, expect_linux_failure
name = testbench_binary,
size = size,
pure = pure,
- tags = PACKETIMPACT_TAGS,
+ tags = [
+ "local",
+ "manual",
+ ],
**kwargs
)
packetimpact_linux_test(
diff --git a/test/packetimpact/runner/packetimpact_test.go b/test/packetimpact/runner/packetimpact_test.go
new file mode 100644
index 000000000..74e1e6def
--- /dev/null
+++ b/test/packetimpact/runner/packetimpact_test.go
@@ -0,0 +1,389 @@
+// Copyright 2020 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.
+
+// The runner starts docker containers and networking for a packetimpact test.
+package packetimpact_test
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "math/rand"
+ "net"
+ "os"
+ "os/exec"
+ "path"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/docker/docker/api/types/mount"
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/packetimpact/netdevs"
+)
+
+// stringList implements flag.Value.
+type stringList []string
+
+// String implements flag.Value.String.
+func (l *stringList) String() string {
+ return strings.Join(*l, ",")
+}
+
+// Set implements flag.Value.Set.
+func (l *stringList) Set(value string) error {
+ *l = append(*l, value)
+ return nil
+}
+
+var (
+ dutPlatform = flag.String("dut_platform", "", "either \"linux\" or \"netstack\"")
+ testbenchBinary = flag.String("testbench_binary", "", "path to the testbench binary")
+ tshark = flag.Bool("tshark", false, "use more verbose tshark in logs instead of tcpdump")
+ extraTestArgs = stringList{}
+ expectFailure = flag.Bool("expect_failure", false, "expect that the test will fail when run")
+
+ dutAddr = net.IPv4(0, 0, 0, 10)
+ testbenchAddr = net.IPv4(0, 0, 0, 20)
+)
+
+const ctrlPort = "40000"
+
+// logger implements testutil.Logger.
+//
+// Labels logs based on their source and formats multi-line logs.
+type logger string
+
+// Name implements testutil.Logger.Name.
+func (l logger) Name() string {
+ return string(l)
+}
+
+// Logf implements testutil.Logger.Logf.
+func (l logger) Logf(format string, args ...interface{}) {
+ lines := strings.Split(fmt.Sprintf(format, args...), "\n")
+ log.Printf("%s: %s", l, lines[0])
+ for _, line := range lines[1:] {
+ log.Printf("%*s %s", len(l), "", line)
+ }
+}
+
+func TestOne(t *testing.T) {
+ flag.Var(&extraTestArgs, "extra_test_arg", "extra arguments to pass to the testbench")
+ flag.Parse()
+ if *dutPlatform != "linux" && *dutPlatform != "netstack" {
+ t.Fatal("--dut_platform should be either linux or netstack")
+ }
+ if *testbenchBinary == "" {
+ t.Fatal("--testbench_binary is missing")
+ }
+ if *dutPlatform == "netstack" {
+ if _, err := dockerutil.RuntimePath(); err != nil {
+ t.Fatal("--runtime is missing or invalid with --dut_platform=netstack:", err)
+ }
+ }
+ dockerutil.EnsureSupportedDockerVersion()
+ ctx := context.Background()
+
+ // Create the networks needed for the test. One control network is needed for
+ // the gRPC control packets and one test network on which to transmit the test
+ // packets.
+ ctrlNet := dockerutil.NewNetwork(ctx, logger("ctrlNet"))
+ testNet := dockerutil.NewNetwork(ctx, logger("testNet"))
+ for _, dn := range []*dockerutil.Network{ctrlNet, testNet} {
+ for {
+ if err := createDockerNetwork(ctx, dn); err != nil {
+ t.Log("creating docker network:", err)
+ const wait = 100 * time.Millisecond
+ t.Logf("sleeping %s and will try creating docker network again", wait)
+ // This can fail if another docker network claimed the same IP so we'll
+ // just try again.
+ time.Sleep(wait)
+ continue
+ }
+ break
+ }
+ defer func(dn *dockerutil.Network) {
+ if err := dn.Cleanup(ctx); err != nil {
+ t.Errorf("unable to cleanup container %s: %s", dn.Name, err)
+ }
+ }(dn)
+ // Sanity check.
+ inspect, err := dn.Inspect(ctx)
+ if err != nil {
+ t.Fatalf("failed to inspect network %s: %v", dn.Name, err)
+ } else if inspect.Name != dn.Name {
+ t.Fatalf("name mismatch for network want: %s got: %s", dn.Name, inspect.Name)
+ }
+
+ }
+
+ tmpDir, err := ioutil.TempDir("", "container-output")
+ if err != nil {
+ t.Fatal("creating temp dir:", err)
+ }
+ defer os.RemoveAll(tmpDir)
+
+ const testOutputDir = "/tmp/testoutput"
+
+ // Create the Docker container for the DUT.
+ dut := dockerutil.MakeContainer(ctx, logger("dut"))
+ if *dutPlatform == "linux" {
+ dut = dockerutil.MakeNativeContainer(ctx, logger("dut"))
+ }
+
+ runOpts := dockerutil.RunOpts{
+ Image: "packetimpact",
+ CapAdd: []string{"NET_ADMIN"},
+ Mounts: []mount.Mount{mount.Mount{
+ Type: mount.TypeBind,
+ Source: tmpDir,
+ Target: testOutputDir,
+ ReadOnly: false,
+ }},
+ }
+
+ const containerPosixServerBinary = "/packetimpact/posix_server"
+ dut.CopyFiles(&runOpts, "/packetimpact", "/test/packetimpact/dut/posix_server")
+
+ conf, hostconf, _ := dut.ConfigsFrom(runOpts, containerPosixServerBinary, "--ip=0.0.0.0", "--port="+ctrlPort)
+ hostconf.AutoRemove = true
+ hostconf.Sysctls = map[string]string{"net.ipv6.conf.all.disable_ipv6": "0"}
+
+ if err := dut.CreateFrom(ctx, conf, hostconf, nil); err != nil {
+ t.Fatalf("unable to create container %s: %v", dut.Name, err)
+ }
+
+ defer dut.CleanUp(ctx)
+
+ // Add ctrlNet as eth1 and testNet as eth2.
+ const testNetDev = "eth2"
+ if err := addNetworks(ctx, dut, dutAddr, []*dockerutil.Network{ctrlNet, testNet}); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := dut.Start(ctx); err != nil {
+ t.Fatalf("unable to start container %s: %s", dut.Name, err)
+ }
+
+ if _, err := dut.WaitForOutput(ctx, "Server listening.*\n", 60*time.Second); err != nil {
+ t.Fatalf("%s on container %s never listened: %s", containerPosixServerBinary, dut.Name, err)
+ }
+
+ dutTestDevice, dutDeviceInfo, err := deviceByIP(ctx, dut, addressInSubnet(dutAddr, *testNet.Subnet))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ remoteMAC := dutDeviceInfo.MAC
+ remoteIPv6 := dutDeviceInfo.IPv6Addr
+ // Netstack as DUT doesn't assign IPv6 addresses automatically so do it if
+ // needed.
+ if remoteIPv6 == nil {
+ if _, err := dut.Exec(ctx, dockerutil.ExecOpts{}, "ip", "addr", "add", netdevs.MACToIP(remoteMAC).String(), "scope", "link", "dev", dutTestDevice); err != nil {
+ t.Fatalf("unable to ip addr add on container %s: %s", dut.Name, err)
+ }
+ // Now try again, to make sure that it worked.
+ _, dutDeviceInfo, err = deviceByIP(ctx, dut, addressInSubnet(dutAddr, *testNet.Subnet))
+ if err != nil {
+ t.Fatal(err)
+ }
+ remoteIPv6 = dutDeviceInfo.IPv6Addr
+ if remoteIPv6 == nil {
+ t.Fatal("unable to set IPv6 address on container", dut.Name)
+ }
+ }
+
+ // Create the Docker container for the testbench.
+ testbench := dockerutil.MakeNativeContainer(ctx, logger("testbench"))
+
+ tbb := path.Base(*testbenchBinary)
+ containerTestbenchBinary := "/packetimpact/" + tbb
+ runOpts = dockerutil.RunOpts{
+ Image: "packetimpact",
+ CapAdd: []string{"NET_ADMIN"},
+ Mounts: []mount.Mount{mount.Mount{
+ Type: mount.TypeBind,
+ Source: tmpDir,
+ Target: testOutputDir,
+ ReadOnly: false,
+ }},
+ }
+ testbench.CopyFiles(&runOpts, "/packetimpact", "/test/packetimpact/tests/"+tbb)
+
+ // Run tcpdump in the test bench unbuffered, without DNS resolution, just on
+ // the interface with the test packets.
+ snifferArgs := []string{
+ "tcpdump",
+ "-S", "-vvv", "-U", "-n",
+ "-i", testNetDev,
+ "-w", testOutputDir + "/dump.pcap",
+ }
+ snifferRegex := "tcpdump: listening.*\n"
+ if *tshark {
+ // Run tshark in the test bench unbuffered, without DNS resolution, just on
+ // the interface with the test packets.
+ snifferArgs = []string{
+ "tshark", "-V", "-l", "-n", "-i", testNetDev,
+ "-o", "tcp.check_checksum:TRUE",
+ "-o", "udp.check_checksum:TRUE",
+ }
+ snifferRegex = "Capturing on.*\n"
+ }
+
+ defer func() {
+ if err := exec.Command("/bin/cp", "-r", tmpDir, os.Getenv("TEST_UNDECLARED_OUTPUTS_DIR")).Run(); err != nil {
+ t.Error("unable to copy container output files:", err)
+ }
+ }()
+
+ conf, hostconf, _ = testbench.ConfigsFrom(runOpts, snifferArgs...)
+ hostconf.AutoRemove = true
+ hostconf.Sysctls = map[string]string{"net.ipv6.conf.all.disable_ipv6": "0"}
+
+ if err := testbench.CreateFrom(ctx, conf, hostconf, nil); err != nil {
+ t.Fatalf("unable to create container %s: %s", testbench.Name, err)
+ }
+ defer testbench.CleanUp(ctx)
+
+ // Add ctrlNet as eth1 and testNet as eth2.
+ if err := addNetworks(ctx, testbench, testbenchAddr, []*dockerutil.Network{ctrlNet, testNet}); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := testbench.Start(ctx); err != nil {
+ t.Fatalf("unable to start container %s: %s", testbench.Name, err)
+ }
+
+ // Kill so that it will flush output.
+ defer func() {
+ time.Sleep(1 * time.Second)
+ testbench.Exec(ctx, dockerutil.ExecOpts{}, "killall", snifferArgs[0])
+ }()
+
+ if _, err := testbench.WaitForOutput(ctx, snifferRegex, 60*time.Second); err != nil {
+ t.Fatalf("sniffer on %s never listened: %s", dut.Name, err)
+ }
+
+ // Because the Linux kernel receives the SYN-ACK but didn't send the SYN it
+ // will issue an RST. To prevent this IPtables can be used to filter out all
+ // incoming packets. The raw socket that packetimpact tests use will still see
+ // everything.
+ for _, bin := range []string{"iptables", "ip6tables"} {
+ if logs, err := testbench.Exec(ctx, dockerutil.ExecOpts{}, bin, "-A", "INPUT", "-i", testNetDev, "-p", "tcp", "-j", "DROP"); err != nil {
+ t.Fatalf("unable to Exec %s on container %s: %s, logs from testbench:\n%s", bin, testbench.Name, err, logs)
+ }
+ }
+
+ // FIXME(b/156449515): Some piece of the system has a race. The old
+ // bash script version had a sleep, so we have one too. The race should
+ // be fixed and this sleep removed.
+ time.Sleep(time.Second)
+
+ // Start a packetimpact test on the test bench. The packetimpact test sends
+ // and receives packets and also sends POSIX socket commands to the
+ // posix_server to be executed on the DUT.
+ testArgs := []string{containerTestbenchBinary}
+ testArgs = append(testArgs, extraTestArgs...)
+ testArgs = append(testArgs,
+ "--posix_server_ip", addressInSubnet(dutAddr, *ctrlNet.Subnet).String(),
+ "--posix_server_port", ctrlPort,
+ "--remote_ipv4", addressInSubnet(dutAddr, *testNet.Subnet).String(),
+ "--local_ipv4", addressInSubnet(testbenchAddr, *testNet.Subnet).String(),
+ "--remote_ipv6", remoteIPv6.String(),
+ "--remote_mac", remoteMAC.String(),
+ "--remote_interface_id", fmt.Sprintf("%d", dutDeviceInfo.ID),
+ "--device", testNetDev,
+ "--dut_type", *dutPlatform,
+ )
+ testbenchLogs, err := testbench.Exec(ctx, dockerutil.ExecOpts{}, testArgs...)
+ if (err != nil) != *expectFailure {
+ var dutLogs string
+ if logs, err := dut.Logs(ctx); err != nil {
+ dutLogs = fmt.Sprintf("failed to fetch DUT logs: %s", err)
+ } else {
+ dutLogs = logs
+ }
+
+ t.Errorf(`test error: %v, expect failure: %t
+
+====== Begin of DUT Logs ======
+
+%s
+
+====== End of DUT Logs ======
+
+====== Begin of Testbench Logs ======
+
+%s
+
+====== End of Testbench Logs ======`,
+ err, *expectFailure, dutLogs, testbenchLogs)
+ }
+}
+
+func addNetworks(ctx context.Context, d *dockerutil.Container, addr net.IP, networks []*dockerutil.Network) error {
+ for _, dn := range networks {
+ ip := addressInSubnet(addr, *dn.Subnet)
+ // Connect to the network with the specified IP address.
+ if err := dn.Connect(ctx, d, ip.String(), ""); err != nil {
+ return fmt.Errorf("unable to connect container %s to network %s: %w", d.Name, dn.Name, err)
+ }
+ }
+ return nil
+}
+
+// addressInSubnet combines the subnet provided with the address and returns a
+// new address. The return address bits come from the subnet where the mask is 1
+// and from the ip address where the mask is 0.
+func addressInSubnet(addr net.IP, subnet net.IPNet) net.IP {
+ var octets []byte
+ for i := 0; i < 4; i++ {
+ octets = append(octets, (subnet.IP.To4()[i]&subnet.Mask[i])+(addr.To4()[i]&(^subnet.Mask[i])))
+ }
+ return net.IP(octets)
+}
+
+// createDockerNetwork makes a randomly-named network that will start with the
+// namePrefix. The network will be a random /24 subnet.
+func createDockerNetwork(ctx context.Context, n *dockerutil.Network) error {
+ randSource := rand.NewSource(time.Now().UnixNano())
+ r1 := rand.New(randSource)
+ // Class C, 192.0.0.0 to 223.255.255.255, transitionally has mask 24.
+ ip := net.IPv4(byte(r1.Intn(224-192)+192), byte(r1.Intn(256)), byte(r1.Intn(256)), 0)
+ n.Subnet = &net.IPNet{
+ IP: ip,
+ Mask: ip.DefaultMask(),
+ }
+ return n.Create(ctx)
+}
+
+// deviceByIP finds a deviceInfo and device name from an IP address.
+func deviceByIP(ctx context.Context, d *dockerutil.Container, ip net.IP) (string, netdevs.DeviceInfo, error) {
+ out, err := d.Exec(ctx, dockerutil.ExecOpts{}, "ip", "addr", "show")
+ if err != nil {
+ return "", netdevs.DeviceInfo{}, fmt.Errorf("listing devices on %s container: %w", d.Name, err)
+ }
+ devs, err := netdevs.ParseDevices(out)
+ if err != nil {
+ return "", netdevs.DeviceInfo{}, fmt.Errorf("parsing devices from %s container: %w", d.Name, err)
+ }
+ testDevice, deviceInfo, err := netdevs.FindDeviceByIP(ip, devs)
+ if err != nil {
+ return "", netdevs.DeviceInfo{}, fmt.Errorf("can't find deviceInfo for container %s: %w", d.Name, err)
+ }
+ return testDevice, deviceInfo, nil
+}
diff --git a/test/packetimpact/testbench/BUILD b/test/packetimpact/testbench/BUILD
index 682933067..5a0ee1367 100644
--- a/test/packetimpact/testbench/BUILD
+++ b/test/packetimpact/testbench/BUILD
@@ -21,9 +21,10 @@ go_library(
"//pkg/tcpip/header",
"//pkg/tcpip/seqnum",
"//pkg/usermem",
+ "//test/packetimpact/netdevs",
"//test/packetimpact/proto:posix_server_go_proto",
- "@com_github_google_go-cmp//cmp:go_default_library",
- "@com_github_google_go-cmp//cmp/cmpopts:go_default_library",
+ "@com_github_google_go_cmp//cmp:go_default_library",
+ "@com_github_google_go_cmp//cmp/cmpopts:go_default_library",
"@com_github_mohae_deepcopy//:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//keepalive:go_default_library",
diff --git a/test/packetimpact/testbench/connections.go b/test/packetimpact/testbench/connections.go
index 463fd0556..3af5f83fd 100644
--- a/test/packetimpact/testbench/connections.go
+++ b/test/packetimpact/testbench/connections.go
@@ -41,38 +41,46 @@ func portFromSockaddr(sa unix.Sockaddr) (uint16, error) {
return 0, fmt.Errorf("sockaddr type %T does not contain port", sa)
}
-// pickPort makes a new socket and returns the socket FD and port. The domain should be AF_INET or AF_INET6. The caller must close the FD when done with
+// pickPort makes a new socket and returns the socket FD and port. The domain
+// should be AF_INET or AF_INET6. The caller must close the FD when done with
// the port if there is no error.
-func pickPort(domain, typ int) (fd int, sa unix.Sockaddr, err error) {
+func pickPort(domain, typ int) (fd int, port uint16, err error) {
fd, err = unix.Socket(domain, typ, 0)
if err != nil {
- return -1, nil, err
+ return -1, 0, fmt.Errorf("creating socket: %w", err)
}
defer func() {
if err != nil {
- err = multierr.Append(err, unix.Close(fd))
+ if cerr := unix.Close(fd); cerr != nil {
+ err = multierr.Append(err, fmt.Errorf("failed to close socket %d: %w", fd, cerr))
+ }
}
}()
+ var sa unix.Sockaddr
switch domain {
case unix.AF_INET:
var sa4 unix.SockaddrInet4
copy(sa4.Addr[:], net.ParseIP(LocalIPv4).To4())
sa = &sa4
case unix.AF_INET6:
- var sa6 unix.SockaddrInet6
+ sa6 := unix.SockaddrInet6{ZoneId: uint32(LocalInterfaceID)}
copy(sa6.Addr[:], net.ParseIP(LocalIPv6).To16())
sa = &sa6
default:
- return -1, nil, fmt.Errorf("invalid domain %d, it should be one of unix.AF_INET or unix.AF_INET6", domain)
+ return -1, 0, fmt.Errorf("invalid domain %d, it should be one of unix.AF_INET or unix.AF_INET6", domain)
}
if err = unix.Bind(fd, sa); err != nil {
- return -1, nil, err
+ return -1, 0, fmt.Errorf("binding to %+v: %w", sa, err)
}
sa, err = unix.Getsockname(fd)
if err != nil {
- return -1, nil, err
+ return -1, 0, fmt.Errorf("Getsocketname(%d): %w", fd, err)
+ }
+ port, err = portFromSockaddr(sa)
+ if err != nil {
+ return -1, 0, fmt.Errorf("extracting port from socket address %+v: %w", sa, err)
}
- return fd, sa, nil
+ return fd, port, nil
}
// layerState stores the state of a layer of a connection.
@@ -114,12 +122,12 @@ var _ layerState = (*etherState)(nil)
func newEtherState(out, in Ether) (*etherState, error) {
lMAC, err := tcpip.ParseMACAddress(LocalMAC)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("parsing local MAC: %q: %w", LocalMAC, err)
}
rMAC, err := tcpip.ParseMACAddress(RemoteMAC)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("parsing remote MAC: %q: %w", RemoteMAC, err)
}
s := etherState{
out: Ether{SrcAddr: &lMAC, DstAddr: &rMAC},
@@ -267,11 +275,7 @@ func SeqNumValue(v seqnum.Value) *seqnum.Value {
// newTCPState creates a new TCPState.
func newTCPState(domain int, out, in TCP) (*tcpState, error) {
- portPickerFD, localAddr, err := pickPort(domain, unix.SOCK_STREAM)
- if err != nil {
- return nil, err
- }
- localPort, err := portFromSockaddr(localAddr)
+ portPickerFD, localPort, err := pickPort(domain, unix.SOCK_STREAM)
if err != nil {
return nil, err
}
@@ -374,14 +378,10 @@ type udpState struct {
var _ layerState = (*udpState)(nil)
// newUDPState creates a new udpState.
-func newUDPState(domain int, out, in UDP) (*udpState, unix.Sockaddr, error) {
- portPickerFD, localAddr, err := pickPort(domain, unix.SOCK_DGRAM)
+func newUDPState(domain int, out, in UDP) (*udpState, error) {
+ portPickerFD, localPort, err := pickPort(domain, unix.SOCK_DGRAM)
if err != nil {
- return nil, nil, err
- }
- localPort, err := portFromSockaddr(localAddr)
- if err != nil {
- return nil, nil, err
+ return nil, fmt.Errorf("picking port: %w", err)
}
s := udpState{
out: UDP{SrcPort: &localPort},
@@ -389,12 +389,12 @@ func newUDPState(domain int, out, in UDP) (*udpState, unix.Sockaddr, error) {
portPickerFD: portPickerFD,
}
if err := s.out.merge(&out); err != nil {
- return nil, nil, err
+ return nil, err
}
if err := s.in.merge(&in); err != nil {
- return nil, nil, err
+ return nil, err
}
- return &s, localAddr, nil
+ return &s, nil
}
func (s *udpState) outgoing() Layer {
@@ -429,8 +429,6 @@ type Connection struct {
layerStates []layerState
injector Injector
sniffer Sniffer
- localAddr unix.Sockaddr
- t *testing.T
}
// Returns the default incoming frame against which to match. If received is
@@ -463,7 +461,9 @@ func (conn *Connection) match(override, received Layers) bool {
}
// Close frees associated resources held by the Connection.
-func (conn *Connection) Close() {
+func (conn *Connection) Close(t *testing.T) {
+ t.Helper()
+
errs := multierr.Combine(conn.sniffer.close(), conn.injector.close())
for _, s := range conn.layerStates {
if err := s.close(); err != nil {
@@ -471,31 +471,62 @@ func (conn *Connection) Close() {
}
}
if errs != nil {
- conn.t.Fatalf("unable to close %+v: %s", conn, errs)
+ t.Fatalf("unable to close %+v: %s", conn, errs)
}
}
-// CreateFrame builds a frame for the connection with layer overriding defaults
-// of the innermost layer and additionalLayers added after it.
-func (conn *Connection) CreateFrame(layer Layer, additionalLayers ...Layer) Layers {
+// CreateFrame builds a frame for the connection with defaults overriden
+// from the innermost layer out, and additionalLayers added after it.
+//
+// Note that overrideLayers can have a length that is less than the number
+// of layers in this connection, and in such cases the innermost layers are
+// overriden first. As an example, valid values of overrideLayers for a TCP-
+// over-IPv4-over-Ethernet connection are: nil, [TCP], [IPv4, TCP], and
+// [Ethernet, IPv4, TCP].
+func (conn *Connection) CreateFrame(t *testing.T, overrideLayers Layers, additionalLayers ...Layer) Layers {
+ t.Helper()
+
var layersToSend Layers
- for _, s := range conn.layerStates {
- layersToSend = append(layersToSend, s.outgoing())
- }
- if err := layersToSend[len(layersToSend)-1].merge(layer); err != nil {
- conn.t.Fatalf("can't merge %+v into %+v: %s", layer, layersToSend[len(layersToSend)-1], err)
+ for i, s := range conn.layerStates {
+ layer := s.outgoing()
+ // overrideLayers and conn.layerStates have their tails aligned, so
+ // to find the index we move backwards by the distance i is to the
+ // end.
+ if j := len(overrideLayers) - (len(conn.layerStates) - i); j >= 0 {
+ if err := layer.merge(overrideLayers[j]); err != nil {
+ t.Fatalf("can't merge %+v into %+v: %s", layer, overrideLayers[j], err)
+ }
+ }
+ layersToSend = append(layersToSend, layer)
}
layersToSend = append(layersToSend, additionalLayers...)
return layersToSend
}
+// SendFrameStateless sends a frame without updating any of the layer states.
+//
+// This method is useful for sending out-of-band control messages such as
+// ICMP packets, where it would not make sense to update the transport layer's
+// state using the ICMP header.
+func (conn *Connection) SendFrameStateless(t *testing.T, frame Layers) {
+ t.Helper()
+
+ outBytes, err := frame.ToBytes()
+ if err != nil {
+ t.Fatalf("can't build outgoing packet: %s", err)
+ }
+ conn.injector.Send(t, outBytes)
+}
+
// SendFrame sends a frame on the wire and updates the state of all layers.
-func (conn *Connection) SendFrame(frame Layers) {
+func (conn *Connection) SendFrame(t *testing.T, frame Layers) {
+ t.Helper()
+
outBytes, err := frame.ToBytes()
if err != nil {
- conn.t.Fatalf("can't build outgoing packet: %s", err)
+ t.Fatalf("can't build outgoing packet: %s", err)
}
- conn.injector.Send(outBytes)
+ conn.injector.Send(t, outBytes)
// frame might have nil values where the caller wanted to use default values.
// sentFrame will have no nil values in it because it comes from parsing the
@@ -504,25 +535,32 @@ func (conn *Connection) SendFrame(frame Layers) {
// Update the state of each layer based on what was sent.
for i, s := range conn.layerStates {
if err := s.sent(sentFrame[i]); err != nil {
- conn.t.Fatalf("Unable to update the state of %+v with %s: %s", s, sentFrame[i], err)
+ t.Fatalf("Unable to update the state of %+v with %s: %s", s, sentFrame[i], err)
}
}
}
-// Send a packet with reasonable defaults. Potentially override the final layer
-// in the connection with the provided layer and add additionLayers.
-func (conn *Connection) Send(layer Layer, additionalLayers ...Layer) {
- conn.SendFrame(conn.CreateFrame(layer, additionalLayers...))
+// send sends a packet, possibly with layers of this connection overridden and
+// additional layers added.
+//
+// Types defined with Connection as the underlying type should expose
+// type-safe versions of this method.
+func (conn *Connection) send(t *testing.T, overrideLayers Layers, additionalLayers ...Layer) {
+ t.Helper()
+
+ conn.SendFrame(t, conn.CreateFrame(t, overrideLayers, additionalLayers...))
}
// recvFrame gets the next successfully parsed frame (of type Layers) within the
// timeout provided. If no parsable frame arrives before the timeout, it returns
// nil.
-func (conn *Connection) recvFrame(timeout time.Duration) Layers {
+func (conn *Connection) recvFrame(t *testing.T, timeout time.Duration) Layers {
+ t.Helper()
+
if timeout <= 0 {
return nil
}
- b := conn.sniffer.Recv(timeout)
+ b := conn.sniffer.Recv(t, timeout)
if b == nil {
return nil
}
@@ -542,32 +580,36 @@ func (e *layersError) Error() string {
// Expect expects a frame with the final layerStates layer matching the
// provided Layer within the timeout specified. If it doesn't arrive in time,
// an error is returned.
-func (conn *Connection) Expect(layer Layer, timeout time.Duration) (Layer, error) {
+func (conn *Connection) Expect(t *testing.T, layer Layer, timeout time.Duration) (Layer, error) {
+ t.Helper()
+
// Make a frame that will ignore all but the final layer.
layers := make([]Layer, len(conn.layerStates))
layers[len(layers)-1] = layer
- gotFrame, err := conn.ExpectFrame(layers, timeout)
+ gotFrame, err := conn.ExpectFrame(t, layers, timeout)
if err != nil {
return nil, err
}
if len(conn.layerStates)-1 < len(gotFrame) {
return gotFrame[len(conn.layerStates)-1], nil
}
- conn.t.Fatal("the received frame should be at least as long as the expected layers")
+ t.Fatalf("the received frame should be at least as long as the expected layers, got %d layers, want at least %d layers, got frame: %#v", len(gotFrame), len(conn.layerStates), gotFrame)
panic("unreachable")
}
// ExpectFrame expects a frame that matches the provided Layers within the
// timeout specified. If one arrives in time, the Layers is returned without an
// error. If it doesn't arrive in time, it returns nil and error is non-nil.
-func (conn *Connection) ExpectFrame(layers Layers, timeout time.Duration) (Layers, error) {
+func (conn *Connection) ExpectFrame(t *testing.T, layers Layers, timeout time.Duration) (Layers, error) {
+ t.Helper()
+
deadline := time.Now().Add(timeout)
var errs error
for {
var gotLayers Layers
if timeout = time.Until(deadline); timeout > 0 {
- gotLayers = conn.recvFrame(timeout)
+ gotLayers = conn.recvFrame(t, timeout)
}
if gotLayers == nil {
if errs == nil {
@@ -578,7 +620,7 @@ func (conn *Connection) ExpectFrame(layers Layers, timeout time.Duration) (Layer
if conn.match(layers, gotLayers) {
for i, s := range conn.layerStates {
if err := s.received(gotLayers[i]); err != nil {
- conn.t.Fatal(err)
+ t.Fatalf("failed to update test connection's layer states based on received frame: %s", err)
}
}
return gotLayers, nil
@@ -589,8 +631,10 @@ func (conn *Connection) ExpectFrame(layers Layers, timeout time.Duration) (Layer
// Drain drains the sniffer's receive buffer by receiving packets until there's
// nothing else to receive.
-func (conn *Connection) Drain() {
- conn.sniffer.Drain()
+func (conn *Connection) Drain(t *testing.T) {
+ t.Helper()
+
+ conn.sniffer.Drain(t)
}
// TCPIPv4 maintains the state for all the layers in a TCP/IPv4 connection.
@@ -598,6 +642,8 @@ type TCPIPv4 Connection
// NewTCPIPv4 creates a new TCPIPv4 connection with reasonable defaults.
func NewTCPIPv4(t *testing.T, outgoingTCP, incomingTCP TCP) TCPIPv4 {
+ t.Helper()
+
etherState, err := newEtherState(Ether{}, Ether{})
if err != nil {
t.Fatalf("can't make etherState: %s", err)
@@ -623,84 +669,174 @@ func NewTCPIPv4(t *testing.T, outgoingTCP, incomingTCP TCP) TCPIPv4 {
layerStates: []layerState{etherState, ipv4State, tcpState},
injector: injector,
sniffer: sniffer,
- t: t,
}
}
-// Handshake performs a TCP 3-way handshake. The input Connection should have a
+// Connect performs a TCP 3-way handshake. The input Connection should have a
// final TCP Layer.
-func (conn *TCPIPv4) Handshake() {
+func (conn *TCPIPv4) Connect(t *testing.T) {
+ t.Helper()
+
// Send the SYN.
- conn.Send(TCP{Flags: Uint8(header.TCPFlagSyn)})
+ conn.Send(t, TCP{Flags: Uint8(header.TCPFlagSyn)})
// Wait for the SYN-ACK.
- synAck, err := conn.Expect(TCP{Flags: Uint8(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second)
- if synAck == nil {
- conn.t.Fatalf("didn't get synack during handshake: %s", err)
+ synAck, err := conn.Expect(t, TCP{Flags: Uint8(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second)
+ if err != nil {
+ t.Fatalf("didn't get synack during handshake: %s", err)
}
conn.layerStates[len(conn.layerStates)-1].(*tcpState).synAck = synAck
// Send an ACK.
- conn.Send(TCP{Flags: Uint8(header.TCPFlagAck)})
+ conn.Send(t, TCP{Flags: Uint8(header.TCPFlagAck)})
+}
+
+// ConnectWithOptions performs a TCP 3-way handshake with given TCP options.
+// The input Connection should have a final TCP Layer.
+func (conn *TCPIPv4) ConnectWithOptions(t *testing.T, options []byte) {
+ t.Helper()
+
+ // Send the SYN.
+ conn.Send(t, TCP{Flags: Uint8(header.TCPFlagSyn), Options: options})
+
+ // Wait for the SYN-ACK.
+ synAck, err := conn.Expect(t, TCP{Flags: Uint8(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second)
+ if err != nil {
+ t.Fatalf("didn't get synack during handshake: %s", err)
+ }
+ conn.layerStates[len(conn.layerStates)-1].(*tcpState).synAck = synAck
+
+ // Send an ACK.
+ conn.Send(t, TCP{Flags: Uint8(header.TCPFlagAck)})
}
// ExpectData is a convenient method that expects a Layer and the Layer after
// it. If it doens't arrive in time, it returns nil.
-func (conn *TCPIPv4) ExpectData(tcp *TCP, payload *Payload, timeout time.Duration) (Layers, error) {
+func (conn *TCPIPv4) ExpectData(t *testing.T, tcp *TCP, payload *Payload, timeout time.Duration) (Layers, error) {
+ t.Helper()
+
+ expected := make([]Layer, len(conn.layerStates))
+ expected[len(expected)-1] = tcp
+ if payload != nil {
+ expected = append(expected, payload)
+ }
+ return (*Connection)(conn).ExpectFrame(t, expected, timeout)
+}
+
+// ExpectNextData attempts to receive the next incoming segment for the
+// connection and expects that to match the given layers.
+//
+// It differs from ExpectData() in that here we are only interested in the next
+// received segment, while ExpectData() can receive multiple segments for the
+// connection until there is a match with given layers or a timeout.
+func (conn *TCPIPv4) ExpectNextData(t *testing.T, tcp *TCP, payload *Payload, timeout time.Duration) (Layers, error) {
+ t.Helper()
+
+ // Receive the first incoming TCP segment for this connection.
+ got, err := conn.ExpectData(t, &TCP{}, nil, timeout)
+ if err != nil {
+ return nil, err
+ }
+
expected := make([]Layer, len(conn.layerStates))
expected[len(expected)-1] = tcp
if payload != nil {
expected = append(expected, payload)
+ tcp.SeqNum = Uint32(uint32(*conn.RemoteSeqNum(t)) - uint32(payload.Length()))
}
- return (*Connection)(conn).ExpectFrame(expected, timeout)
+ if !(*Connection)(conn).match(expected, got) {
+ return nil, fmt.Errorf("next frame is not matching %s during %s: got %s", expected, timeout, got)
+ }
+ return got, nil
}
// Send a packet with reasonable defaults. Potentially override the TCP layer in
// the connection with the provided layer and add additionLayers.
-func (conn *TCPIPv4) Send(tcp TCP, additionalLayers ...Layer) {
- (*Connection)(conn).Send(&tcp, additionalLayers...)
+func (conn *TCPIPv4) Send(t *testing.T, tcp TCP, additionalLayers ...Layer) {
+ t.Helper()
+
+ (*Connection)(conn).send(t, Layers{&tcp}, additionalLayers...)
}
// Close frees associated resources held by the TCPIPv4 connection.
-func (conn *TCPIPv4) Close() {
- (*Connection)(conn).Close()
+func (conn *TCPIPv4) Close(t *testing.T) {
+ t.Helper()
+
+ (*Connection)(conn).Close(t)
}
// Expect expects a frame with the TCP layer matching the provided TCP within
// the timeout specified. If it doesn't arrive in time, an error is returned.
-func (conn *TCPIPv4) Expect(tcp TCP, timeout time.Duration) (*TCP, error) {
- layer, err := (*Connection)(conn).Expect(&tcp, timeout)
+func (conn *TCPIPv4) Expect(t *testing.T, tcp TCP, timeout time.Duration) (*TCP, error) {
+ t.Helper()
+
+ layer, err := (*Connection)(conn).Expect(t, &tcp, timeout)
if layer == nil {
return nil, err
}
gotTCP, ok := layer.(*TCP)
if !ok {
- conn.t.Fatalf("expected %s to be TCP", layer)
+ t.Fatalf("expected %s to be TCP", layer)
}
return gotTCP, err
}
-func (conn *TCPIPv4) state() *tcpState {
- state, ok := conn.layerStates[len(conn.layerStates)-1].(*tcpState)
+func (conn *TCPIPv4) tcpState(t *testing.T) *tcpState {
+ t.Helper()
+
+ state, ok := conn.layerStates[2].(*tcpState)
+ if !ok {
+ t.Fatalf("got transport-layer state type=%T, expected tcpState", conn.layerStates[2])
+ }
+ return state
+}
+
+func (conn *TCPIPv4) ipv4State(t *testing.T) *ipv4State {
+ t.Helper()
+
+ state, ok := conn.layerStates[1].(*ipv4State)
if !ok {
- conn.t.Fatalf("expected final state of %v to be tcpState", conn.layerStates)
+ t.Fatalf("expected network-layer state type=%T, expected ipv4State", conn.layerStates[1])
}
return state
}
// RemoteSeqNum returns the next expected sequence number from the DUT.
-func (conn *TCPIPv4) RemoteSeqNum() *seqnum.Value {
- return conn.state().remoteSeqNum
+func (conn *TCPIPv4) RemoteSeqNum(t *testing.T) *seqnum.Value {
+ t.Helper()
+
+ return conn.tcpState(t).remoteSeqNum
}
// LocalSeqNum returns the next sequence number to send from the testbench.
-func (conn *TCPIPv4) LocalSeqNum() *seqnum.Value {
- return conn.state().localSeqNum
+func (conn *TCPIPv4) LocalSeqNum(t *testing.T) *seqnum.Value {
+ t.Helper()
+
+ return conn.tcpState(t).localSeqNum
}
// SynAck returns the SynAck that was part of the handshake.
-func (conn *TCPIPv4) SynAck() *TCP {
- return conn.state().synAck
+func (conn *TCPIPv4) SynAck(t *testing.T) *TCP {
+ t.Helper()
+
+ return conn.tcpState(t).synAck
+}
+
+// LocalAddr gets the local socket address of this connection.
+func (conn *TCPIPv4) LocalAddr(t *testing.T) *unix.SockaddrInet4 {
+ t.Helper()
+
+ sa := &unix.SockaddrInet4{Port: int(*conn.tcpState(t).out.SrcPort)}
+ copy(sa.Addr[:], *conn.ipv4State(t).out.SrcAddr)
+ return sa
+}
+
+// Drain drains the sniffer's receive buffer by receiving packets until there's
+// nothing else to receive.
+func (conn *TCPIPv4) Drain(t *testing.T) {
+ t.Helper()
+
+ conn.sniffer.Drain(t)
}
// IPv6Conn maintains the state for all the layers in a IPv6 connection.
@@ -708,6 +844,8 @@ type IPv6Conn Connection
// NewIPv6Conn creates a new IPv6Conn connection with reasonable defaults.
func NewIPv6Conn(t *testing.T, outgoingIPv6, incomingIPv6 IPv6) IPv6Conn {
+ t.Helper()
+
etherState, err := newEtherState(Ether{}, Ether{})
if err != nil {
t.Fatalf("can't make EtherState: %s", err)
@@ -730,36 +868,30 @@ func NewIPv6Conn(t *testing.T, outgoingIPv6, incomingIPv6 IPv6) IPv6Conn {
layerStates: []layerState{etherState, ipv6State},
injector: injector,
sniffer: sniffer,
- t: t,
}
}
-// SendFrame sends a frame on the wire and updates the state of all layers.
-func (conn *IPv6Conn) SendFrame(frame Layers) {
- (*Connection)(conn).SendFrame(frame)
-}
+// Send sends a frame with ipv6 overriding the IPv6 layer defaults and
+// additionalLayers added after it.
+func (conn *IPv6Conn) Send(t *testing.T, ipv6 IPv6, additionalLayers ...Layer) {
+ t.Helper()
-// CreateFrame builds a frame for the connection with ipv6 overriding the ipv6
-// layer defaults and additionalLayers added after it.
-func (conn *IPv6Conn) CreateFrame(ipv6 IPv6, additionalLayers ...Layer) Layers {
- return (*Connection)(conn).CreateFrame(&ipv6, additionalLayers...)
+ (*Connection)(conn).send(t, Layers{&ipv6}, additionalLayers...)
}
// Close to clean up any resources held.
-func (conn *IPv6Conn) Close() {
- (*Connection)(conn).Close()
+func (conn *IPv6Conn) Close(t *testing.T) {
+ t.Helper()
+
+ (*Connection)(conn).Close(t)
}
// ExpectFrame expects a frame that matches the provided Layers within the
// timeout specified. If it doesn't arrive in time, an error is returned.
-func (conn *IPv6Conn) ExpectFrame(frame Layers, timeout time.Duration) (Layers, error) {
- return (*Connection)(conn).ExpectFrame(frame, timeout)
-}
+func (conn *IPv6Conn) ExpectFrame(t *testing.T, frame Layers, timeout time.Duration) (Layers, error) {
+ t.Helper()
-// Drain drains the sniffer's receive buffer by receiving packets until there's
-// nothing else to receive.
-func (conn *TCPIPv4) Drain() {
- conn.sniffer.Drain()
+ return (*Connection)(conn).ExpectFrame(t, frame, timeout)
}
// UDPIPv4 maintains the state for all the layers in a UDP/IPv4 connection.
@@ -767,6 +899,8 @@ type UDPIPv4 Connection
// NewUDPIPv4 creates a new UDPIPv4 connection with reasonable defaults.
func NewUDPIPv4(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv4 {
+ t.Helper()
+
etherState, err := newEtherState(Ether{}, Ether{})
if err != nil {
t.Fatalf("can't make etherState: %s", err)
@@ -775,7 +909,7 @@ func NewUDPIPv4(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv4 {
if err != nil {
t.Fatalf("can't make ipv4State: %s", err)
}
- udpState, localAddr, err := newUDPState(unix.AF_INET, outgoingUDP, incomingUDP)
+ udpState, err := newUDPState(unix.AF_INET, outgoingUDP, incomingUDP)
if err != nil {
t.Fatalf("can't make udpState: %s", err)
}
@@ -792,78 +926,280 @@ func NewUDPIPv4(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv4 {
layerStates: []layerState{etherState, ipv4State, udpState},
injector: injector,
sniffer: sniffer,
- localAddr: localAddr,
- t: t,
}
}
+func (conn *UDPIPv4) udpState(t *testing.T) *udpState {
+ t.Helper()
+
+ state, ok := conn.layerStates[2].(*udpState)
+ if !ok {
+ t.Fatalf("got transport-layer state type=%T, expected udpState", conn.layerStates[2])
+ }
+ return state
+}
+
+func (conn *UDPIPv4) ipv4State(t *testing.T) *ipv4State {
+ t.Helper()
+
+ state, ok := conn.layerStates[1].(*ipv4State)
+ if !ok {
+ t.Fatalf("got network-layer state type=%T, expected ipv4State", conn.layerStates[1])
+ }
+ return state
+}
+
// LocalAddr gets the local socket address of this connection.
-func (conn *UDPIPv4) LocalAddr() unix.Sockaddr {
- return conn.localAddr
+func (conn *UDPIPv4) LocalAddr(t *testing.T) *unix.SockaddrInet4 {
+ t.Helper()
+
+ sa := &unix.SockaddrInet4{Port: int(*conn.udpState(t).out.SrcPort)}
+ copy(sa.Addr[:], *conn.ipv4State(t).out.SrcAddr)
+ return sa
}
-// CreateFrame builds a frame for the connection with layer overriding defaults
-// of the innermost layer and additionalLayers added after it.
-func (conn *UDPIPv4) CreateFrame(layer Layer, additionalLayers ...Layer) Layers {
- return (*Connection)(conn).CreateFrame(layer, additionalLayers...)
+// Send sends a packet with reasonable defaults, potentially overriding the UDP
+// layer and adding additionLayers.
+func (conn *UDPIPv4) Send(t *testing.T, udp UDP, additionalLayers ...Layer) {
+ t.Helper()
+
+ (*Connection)(conn).send(t, Layers{&udp}, additionalLayers...)
}
-// Send a packet with reasonable defaults. Potentially override the UDP layer in
-// the connection with the provided layer and add additionLayers.
-func (conn *UDPIPv4) Send(udp UDP, additionalLayers ...Layer) {
- (*Connection)(conn).Send(&udp, additionalLayers...)
+// SendIP sends a packet with reasonable defaults, potentially overriding the
+// UDP and IPv4 headers and adding additionLayers.
+func (conn *UDPIPv4) SendIP(t *testing.T, ip IPv4, udp UDP, additionalLayers ...Layer) {
+ t.Helper()
+
+ (*Connection)(conn).send(t, Layers{&ip, &udp}, additionalLayers...)
}
-// SendFrame sends a frame on the wire and updates the state of all layers.
-func (conn *UDPIPv4) SendFrame(frame Layers) {
- (*Connection)(conn).SendFrame(frame)
+// Expect expects a frame with the UDP layer matching the provided UDP within
+// the timeout specified. If it doesn't arrive in time, an error is returned.
+func (conn *UDPIPv4) Expect(t *testing.T, udp UDP, timeout time.Duration) (*UDP, error) {
+ t.Helper()
+
+ layer, err := (*Connection)(conn).Expect(t, &udp, timeout)
+ if err != nil {
+ return nil, err
+ }
+ gotUDP, ok := layer.(*UDP)
+ if !ok {
+ t.Fatalf("expected %s to be UDP", layer)
+ }
+ return gotUDP, nil
}
-// SendIP sends a packet with additionalLayers following the IP layer in the
-// connection.
-func (conn *UDPIPv4) SendIP(additionalLayers ...Layer) {
- var layersToSend Layers
- for _, s := range conn.layerStates[:len(conn.layerStates)-1] {
- layersToSend = append(layersToSend, s.outgoing())
+// ExpectData is a convenient method that expects a Layer and the Layer after
+// it. If it doens't arrive in time, it returns nil.
+func (conn *UDPIPv4) ExpectData(t *testing.T, udp UDP, payload Payload, timeout time.Duration) (Layers, error) {
+ t.Helper()
+
+ expected := make([]Layer, len(conn.layerStates))
+ expected[len(expected)-1] = &udp
+ if payload.length() != 0 {
+ expected = append(expected, &payload)
}
- layersToSend = append(layersToSend, additionalLayers...)
- conn.SendFrame(layersToSend)
+ return (*Connection)(conn).ExpectFrame(t, expected, timeout)
+}
+
+// Close frees associated resources held by the UDPIPv4 connection.
+func (conn *UDPIPv4) Close(t *testing.T) {
+ t.Helper()
+
+ (*Connection)(conn).Close(t)
+}
+
+// Drain drains the sniffer's receive buffer by receiving packets until there's
+// nothing else to receive.
+func (conn *UDPIPv4) Drain(t *testing.T) {
+ t.Helper()
+
+ conn.sniffer.Drain(t)
+}
+
+// UDPIPv6 maintains the state for all the layers in a UDP/IPv6 connection.
+type UDPIPv6 Connection
+
+// NewUDPIPv6 creates a new UDPIPv6 connection with reasonable defaults.
+func NewUDPIPv6(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv6 {
+ t.Helper()
+
+ etherState, err := newEtherState(Ether{}, Ether{})
+ if err != nil {
+ t.Fatalf("can't make etherState: %s", err)
+ }
+ ipv6State, err := newIPv6State(IPv6{}, IPv6{})
+ if err != nil {
+ t.Fatalf("can't make IPv6State: %s", err)
+ }
+ udpState, err := newUDPState(unix.AF_INET6, outgoingUDP, incomingUDP)
+ if err != nil {
+ t.Fatalf("can't make udpState: %s", err)
+ }
+ injector, err := NewInjector(t)
+ if err != nil {
+ t.Fatalf("can't make injector: %s", err)
+ }
+ sniffer, err := NewSniffer(t)
+ if err != nil {
+ t.Fatalf("can't make sniffer: %s", err)
+ }
+ return UDPIPv6{
+ layerStates: []layerState{etherState, ipv6State, udpState},
+ injector: injector,
+ sniffer: sniffer,
+ }
+}
+
+func (conn *UDPIPv6) udpState(t *testing.T) *udpState {
+ t.Helper()
+
+ state, ok := conn.layerStates[2].(*udpState)
+ if !ok {
+ t.Fatalf("got transport-layer state type=%T, expected udpState", conn.layerStates[2])
+ }
+ return state
+}
+
+func (conn *UDPIPv6) ipv6State(t *testing.T) *ipv6State {
+ t.Helper()
+
+ state, ok := conn.layerStates[1].(*ipv6State)
+ if !ok {
+ t.Fatalf("got network-layer state type=%T, expected ipv6State", conn.layerStates[1])
+ }
+ return state
+}
+
+// LocalAddr gets the local socket address of this connection.
+func (conn *UDPIPv6) LocalAddr(t *testing.T) *unix.SockaddrInet6 {
+ t.Helper()
+
+ sa := &unix.SockaddrInet6{
+ Port: int(*conn.udpState(t).out.SrcPort),
+ // Local address is in perspective to the remote host, so it's scoped to the
+ // ID of the remote interface.
+ ZoneId: uint32(RemoteInterfaceID),
+ }
+ copy(sa.Addr[:], *conn.ipv6State(t).out.SrcAddr)
+ return sa
+}
+
+// Send sends a packet with reasonable defaults, potentially overriding the UDP
+// layer and adding additionLayers.
+func (conn *UDPIPv6) Send(t *testing.T, udp UDP, additionalLayers ...Layer) {
+ t.Helper()
+
+ (*Connection)(conn).send(t, Layers{&udp}, additionalLayers...)
+}
+
+// SendIPv6 sends a packet with reasonable defaults, potentially overriding the
+// UDP and IPv6 headers and adding additionLayers.
+func (conn *UDPIPv6) SendIPv6(t *testing.T, ip IPv6, udp UDP, additionalLayers ...Layer) {
+ t.Helper()
+
+ (*Connection)(conn).send(t, Layers{&ip, &udp}, additionalLayers...)
}
// Expect expects a frame with the UDP layer matching the provided UDP within
// the timeout specified. If it doesn't arrive in time, an error is returned.
-func (conn *UDPIPv4) Expect(udp UDP, timeout time.Duration) (*UDP, error) {
- conn.t.Helper()
- layer, err := (*Connection)(conn).Expect(&udp, timeout)
- if layer == nil {
+func (conn *UDPIPv6) Expect(t *testing.T, udp UDP, timeout time.Duration) (*UDP, error) {
+ t.Helper()
+
+ layer, err := (*Connection)(conn).Expect(t, &udp, timeout)
+ if err != nil {
return nil, err
}
gotUDP, ok := layer.(*UDP)
if !ok {
- conn.t.Fatalf("expected %s to be UDP", layer)
+ t.Fatalf("expected %s to be UDP", layer)
}
- return gotUDP, err
+ return gotUDP, nil
}
// ExpectData is a convenient method that expects a Layer and the Layer after
// it. If it doens't arrive in time, it returns nil.
-func (conn *UDPIPv4) ExpectData(udp UDP, payload Payload, timeout time.Duration) (Layers, error) {
- conn.t.Helper()
+func (conn *UDPIPv6) ExpectData(t *testing.T, udp UDP, payload Payload, timeout time.Duration) (Layers, error) {
+ t.Helper()
+
expected := make([]Layer, len(conn.layerStates))
expected[len(expected)-1] = &udp
if payload.length() != 0 {
expected = append(expected, &payload)
}
- return (*Connection)(conn).ExpectFrame(expected, timeout)
+ return (*Connection)(conn).ExpectFrame(t, expected, timeout)
}
-// Close frees associated resources held by the UDPIPv4 connection.
-func (conn *UDPIPv4) Close() {
- (*Connection)(conn).Close()
+// Close frees associated resources held by the UDPIPv6 connection.
+func (conn *UDPIPv6) Close(t *testing.T) {
+ t.Helper()
+
+ (*Connection)(conn).Close(t)
}
// Drain drains the sniffer's receive buffer by receiving packets until there's
// nothing else to receive.
-func (conn *UDPIPv4) Drain() {
- conn.sniffer.Drain()
+func (conn *UDPIPv6) Drain(t *testing.T) {
+ t.Helper()
+
+ conn.sniffer.Drain(t)
+}
+
+// TCPIPv6 maintains the state for all the layers in a TCP/IPv6 connection.
+type TCPIPv6 Connection
+
+// NewTCPIPv6 creates a new TCPIPv6 connection with reasonable defaults.
+func NewTCPIPv6(t *testing.T, outgoingTCP, incomingTCP TCP) TCPIPv6 {
+ etherState, err := newEtherState(Ether{}, Ether{})
+ if err != nil {
+ t.Fatalf("can't make etherState: %s", err)
+ }
+ ipv6State, err := newIPv6State(IPv6{}, IPv6{})
+ if err != nil {
+ t.Fatalf("can't make ipv6State: %s", err)
+ }
+ tcpState, err := newTCPState(unix.AF_INET6, outgoingTCP, incomingTCP)
+ if err != nil {
+ t.Fatalf("can't make tcpState: %s", err)
+ }
+ injector, err := NewInjector(t)
+ if err != nil {
+ t.Fatalf("can't make injector: %s", err)
+ }
+ sniffer, err := NewSniffer(t)
+ if err != nil {
+ t.Fatalf("can't make sniffer: %s", err)
+ }
+
+ return TCPIPv6{
+ layerStates: []layerState{etherState, ipv6State, tcpState},
+ injector: injector,
+ sniffer: sniffer,
+ }
+}
+
+func (conn *TCPIPv6) SrcPort() uint16 {
+ state := conn.layerStates[2].(*tcpState)
+ return *state.out.SrcPort
+}
+
+// ExpectData is a convenient method that expects a Layer and the Layer after
+// it. If it doens't arrive in time, it returns nil.
+func (conn *TCPIPv6) ExpectData(t *testing.T, tcp *TCP, payload *Payload, timeout time.Duration) (Layers, error) {
+ t.Helper()
+
+ expected := make([]Layer, len(conn.layerStates))
+ expected[len(expected)-1] = tcp
+ if payload != nil {
+ expected = append(expected, payload)
+ }
+ return (*Connection)(conn).ExpectFrame(t, expected, timeout)
+}
+
+// Close frees associated resources held by the TCPIPv6 connection.
+func (conn *TCPIPv6) Close(t *testing.T) {
+ t.Helper()
+
+ (*Connection)(conn).Close(t)
}
diff --git a/test/packetimpact/testbench/dut.go b/test/packetimpact/testbench/dut.go
index a78b7d7ee..73c532e75 100644
--- a/test/packetimpact/testbench/dut.go
+++ b/test/packetimpact/testbench/dut.go
@@ -16,6 +16,7 @@ package testbench
import (
"context"
+ "flag"
"net"
"strconv"
"syscall"
@@ -30,13 +31,19 @@ import (
// DUT communicates with the DUT to force it to make POSIX calls.
type DUT struct {
- t *testing.T
conn *grpc.ClientConn
posixServer POSIXClient
}
// NewDUT creates a new connection with the DUT over gRPC.
func NewDUT(t *testing.T) DUT {
+ t.Helper()
+
+ flag.Parse()
+ if err := genPseudoFlags(); err != nil {
+ t.Fatal("generating psuedo flags:", err)
+ }
+
posixServerAddress := POSIXServerIP + ":" + strconv.Itoa(POSIXServerPort)
conn, err := grpc.Dial(posixServerAddress, grpc.WithInsecure(), grpc.WithKeepaliveParams(keepalive.ClientParameters{Timeout: RPCKeepalive}))
if err != nil {
@@ -44,7 +51,6 @@ func NewDUT(t *testing.T) DUT {
}
posixServer := NewPOSIXClient(conn)
return DUT{
- t: t,
conn: conn,
posixServer: posixServer,
}
@@ -55,8 +61,9 @@ func (dut *DUT) TearDown() {
dut.conn.Close()
}
-func (dut *DUT) sockaddrToProto(sa unix.Sockaddr) *pb.Sockaddr {
- dut.t.Helper()
+func (dut *DUT) sockaddrToProto(t *testing.T, sa unix.Sockaddr) *pb.Sockaddr {
+ t.Helper()
+
switch s := sa.(type) {
case *unix.SockaddrInet4:
return &pb.Sockaddr{
@@ -81,12 +88,13 @@ func (dut *DUT) sockaddrToProto(sa unix.Sockaddr) *pb.Sockaddr {
},
}
}
- dut.t.Fatalf("can't parse Sockaddr: %+v", sa)
+ t.Fatalf("can't parse Sockaddr struct: %+v", sa)
return nil
}
-func (dut *DUT) protoToSockaddr(sa *pb.Sockaddr) unix.Sockaddr {
- dut.t.Helper()
+func (dut *DUT) protoToSockaddr(t *testing.T, sa *pb.Sockaddr) unix.Sockaddr {
+ t.Helper()
+
switch s := sa.Sockaddr.(type) {
case *pb.Sockaddr_In:
ret := unix.SockaddrInet4{
@@ -100,31 +108,34 @@ func (dut *DUT) protoToSockaddr(sa *pb.Sockaddr) unix.Sockaddr {
ZoneId: s.In6.GetScopeId(),
}
copy(ret.Addr[:], s.In6.GetAddr())
+ return &ret
}
- dut.t.Fatalf("can't parse Sockaddr: %+v", sa)
+ t.Fatalf("can't parse Sockaddr proto: %#v", sa)
return nil
}
// CreateBoundSocket makes a new socket on the DUT, with type typ and protocol
// proto, and bound to the IP address addr. Returns the new file descriptor and
// the port that was selected on the DUT.
-func (dut *DUT) CreateBoundSocket(typ, proto int32, addr net.IP) (int32, uint16) {
- dut.t.Helper()
+func (dut *DUT) CreateBoundSocket(t *testing.T, typ, proto int32, addr net.IP) (int32, uint16) {
+ t.Helper()
+
var fd int32
if addr.To4() != nil {
- fd = dut.Socket(unix.AF_INET, typ, proto)
+ fd = dut.Socket(t, unix.AF_INET, typ, proto)
sa := unix.SockaddrInet4{}
copy(sa.Addr[:], addr.To4())
- dut.Bind(fd, &sa)
+ dut.Bind(t, fd, &sa)
} else if addr.To16() != nil {
- fd = dut.Socket(unix.AF_INET6, typ, proto)
+ fd = dut.Socket(t, unix.AF_INET6, typ, proto)
sa := unix.SockaddrInet6{}
copy(sa.Addr[:], addr.To16())
- dut.Bind(fd, &sa)
+ sa.ZoneId = uint32(RemoteInterfaceID)
+ dut.Bind(t, fd, &sa)
} else {
- dut.t.Fatalf("unknown ip addr type for remoteIP")
+ t.Fatalf("invalid IP address: %s", addr)
}
- sa := dut.GetSockName(fd)
+ sa := dut.GetSockName(t, fd)
var port int
switch s := sa.(type) {
case *unix.SockaddrInet4:
@@ -132,15 +143,17 @@ func (dut *DUT) CreateBoundSocket(typ, proto int32, addr net.IP) (int32, uint16)
case *unix.SockaddrInet6:
port = s.Port
default:
- dut.t.Fatalf("unknown sockaddr type from getsockname: %t", sa)
+ t.Fatalf("unknown sockaddr type from getsockname: %T", sa)
}
return fd, uint16(port)
}
// CreateListener makes a new TCP connection. If it fails, the test ends.
-func (dut *DUT) CreateListener(typ, proto, backlog int32) (int32, uint16) {
- fd, remotePort := dut.CreateBoundSocket(typ, proto, net.ParseIP(RemoteIPv4))
- dut.Listen(fd, backlog)
+func (dut *DUT) CreateListener(t *testing.T, typ, proto, backlog int32) (int32, uint16) {
+ t.Helper()
+
+ fd, remotePort := dut.CreateBoundSocket(t, typ, proto, net.ParseIP(RemoteIPv4))
+ dut.Listen(t, fd, backlog)
return fd, remotePort
}
@@ -150,53 +163,57 @@ func (dut *DUT) CreateListener(typ, proto, backlog int32) (int32, uint16) {
// Accept calls accept on the DUT and causes a fatal test failure if it doesn't
// succeed. If more control over the timeout or error handling is needed, use
// AcceptWithErrno.
-func (dut *DUT) Accept(sockfd int32) (int32, unix.Sockaddr) {
- dut.t.Helper()
+func (dut *DUT) Accept(t *testing.T, sockfd int32) (int32, unix.Sockaddr) {
+ t.Helper()
+
ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
defer cancel()
- fd, sa, err := dut.AcceptWithErrno(ctx, sockfd)
+ fd, sa, err := dut.AcceptWithErrno(ctx, t, sockfd)
if fd < 0 {
- dut.t.Fatalf("failed to accept: %s", err)
+ t.Fatalf("failed to accept: %s", err)
}
return fd, sa
}
// AcceptWithErrno calls accept on the DUT.
-func (dut *DUT) AcceptWithErrno(ctx context.Context, sockfd int32) (int32, unix.Sockaddr, error) {
- dut.t.Helper()
+func (dut *DUT) AcceptWithErrno(ctx context.Context, t *testing.T, sockfd int32) (int32, unix.Sockaddr, error) {
+ t.Helper()
+
req := pb.AcceptRequest{
Sockfd: sockfd,
}
resp, err := dut.posixServer.Accept(ctx, &req)
if err != nil {
- dut.t.Fatalf("failed to call Accept: %s", err)
+ t.Fatalf("failed to call Accept: %s", err)
}
- return resp.GetFd(), dut.protoToSockaddr(resp.GetAddr()), syscall.Errno(resp.GetErrno_())
+ return resp.GetFd(), dut.protoToSockaddr(t, resp.GetAddr()), syscall.Errno(resp.GetErrno_())
}
// Bind calls bind on the DUT and causes a fatal test failure if it doesn't
// succeed. If more control over the timeout or error handling is
// needed, use BindWithErrno.
-func (dut *DUT) Bind(fd int32, sa unix.Sockaddr) {
- dut.t.Helper()
+func (dut *DUT) Bind(t *testing.T, fd int32, sa unix.Sockaddr) {
+ t.Helper()
+
ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
defer cancel()
- ret, err := dut.BindWithErrno(ctx, fd, sa)
+ ret, err := dut.BindWithErrno(ctx, t, fd, sa)
if ret != 0 {
- dut.t.Fatalf("failed to bind socket: %s", err)
+ t.Fatalf("failed to bind socket: %s", err)
}
}
// BindWithErrno calls bind on the DUT.
-func (dut *DUT) BindWithErrno(ctx context.Context, fd int32, sa unix.Sockaddr) (int32, error) {
- dut.t.Helper()
+func (dut *DUT) BindWithErrno(ctx context.Context, t *testing.T, fd int32, sa unix.Sockaddr) (int32, error) {
+ t.Helper()
+
req := pb.BindRequest{
Sockfd: fd,
- Addr: dut.sockaddrToProto(sa),
+ Addr: dut.sockaddrToProto(t, sa),
}
resp, err := dut.posixServer.Bind(ctx, &req)
if err != nil {
- dut.t.Fatalf("failed to call Bind: %s", err)
+ t.Fatalf("failed to call Bind: %s", err)
}
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
}
@@ -204,25 +221,27 @@ func (dut *DUT) BindWithErrno(ctx context.Context, fd int32, sa unix.Sockaddr) (
// Close calls close on the DUT and causes a fatal test failure if it doesn't
// succeed. If more control over the timeout or error handling is needed, use
// CloseWithErrno.
-func (dut *DUT) Close(fd int32) {
- dut.t.Helper()
+func (dut *DUT) Close(t *testing.T, fd int32) {
+ t.Helper()
+
ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
defer cancel()
- ret, err := dut.CloseWithErrno(ctx, fd)
+ ret, err := dut.CloseWithErrno(ctx, t, fd)
if ret != 0 {
- dut.t.Fatalf("failed to close: %s", err)
+ t.Fatalf("failed to close: %s", err)
}
}
// CloseWithErrno calls close on the DUT.
-func (dut *DUT) CloseWithErrno(ctx context.Context, fd int32) (int32, error) {
- dut.t.Helper()
+func (dut *DUT) CloseWithErrno(ctx context.Context, t *testing.T, fd int32) (int32, error) {
+ t.Helper()
+
req := pb.CloseRequest{
Fd: fd,
}
resp, err := dut.posixServer.Close(ctx, &req)
if err != nil {
- dut.t.Fatalf("failed to call Close: %s", err)
+ t.Fatalf("failed to call Close: %s", err)
}
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
}
@@ -230,26 +249,61 @@ func (dut *DUT) CloseWithErrno(ctx context.Context, fd int32) (int32, error) {
// Connect calls connect on the DUT and causes a fatal test failure if it
// doesn't succeed. If more control over the timeout or error handling is
// needed, use ConnectWithErrno.
-func (dut *DUT) Connect(fd int32, sa unix.Sockaddr) {
- dut.t.Helper()
+func (dut *DUT) Connect(t *testing.T, fd int32, sa unix.Sockaddr) {
+ t.Helper()
+
ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
defer cancel()
- ret, err := dut.ConnectWithErrno(ctx, fd, sa)
- if ret != 0 {
- dut.t.Fatalf("failed to connect socket: %s", err)
+ ret, err := dut.ConnectWithErrno(ctx, t, fd, sa)
+ // Ignore 'operation in progress' error that can be returned when the socket
+ // is non-blocking.
+ if err != syscall.Errno(unix.EINPROGRESS) && ret != 0 {
+ t.Fatalf("failed to connect socket: %s", err)
}
}
// ConnectWithErrno calls bind on the DUT.
-func (dut *DUT) ConnectWithErrno(ctx context.Context, fd int32, sa unix.Sockaddr) (int32, error) {
- dut.t.Helper()
+func (dut *DUT) ConnectWithErrno(ctx context.Context, t *testing.T, fd int32, sa unix.Sockaddr) (int32, error) {
+ t.Helper()
+
req := pb.ConnectRequest{
Sockfd: fd,
- Addr: dut.sockaddrToProto(sa),
+ Addr: dut.sockaddrToProto(t, sa),
}
resp, err := dut.posixServer.Connect(ctx, &req)
if err != nil {
- dut.t.Fatalf("failed to call Connect: %s", err)
+ t.Fatalf("failed to call Connect: %s", err)
+ }
+ return resp.GetRet(), syscall.Errno(resp.GetErrno_())
+}
+
+// Fcntl calls fcntl on the DUT and causes a fatal test failure if it
+// doesn't succeed. If more control over the timeout or error handling is
+// needed, use FcntlWithErrno.
+func (dut *DUT) Fcntl(t *testing.T, fd, cmd, arg int32) int32 {
+ t.Helper()
+
+ ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
+ defer cancel()
+ ret, err := dut.FcntlWithErrno(ctx, t, fd, cmd, arg)
+ if ret == -1 {
+ t.Fatalf("failed to Fcntl: ret=%d, errno=%s", ret, err)
+ }
+ return ret
+}
+
+// FcntlWithErrno calls fcntl on the DUT.
+func (dut *DUT) FcntlWithErrno(ctx context.Context, t *testing.T, fd, cmd, arg int32) (int32, error) {
+ t.Helper()
+
+ req := pb.FcntlRequest{
+ Fd: fd,
+ Cmd: cmd,
+ Arg: arg,
+ }
+ resp, err := dut.posixServer.Fcntl(ctx, &req)
+ if err != nil {
+ t.Fatalf("failed to call Fcntl: %s", err)
}
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
}
@@ -257,32 +311,35 @@ func (dut *DUT) ConnectWithErrno(ctx context.Context, fd int32, sa unix.Sockaddr
// GetSockName calls getsockname on the DUT and causes a fatal test failure if
// it doesn't succeed. If more control over the timeout or error handling is
// needed, use GetSockNameWithErrno.
-func (dut *DUT) GetSockName(sockfd int32) unix.Sockaddr {
- dut.t.Helper()
+func (dut *DUT) GetSockName(t *testing.T, sockfd int32) unix.Sockaddr {
+ t.Helper()
+
ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
defer cancel()
- ret, sa, err := dut.GetSockNameWithErrno(ctx, sockfd)
+ ret, sa, err := dut.GetSockNameWithErrno(ctx, t, sockfd)
if ret != 0 {
- dut.t.Fatalf("failed to getsockname: %s", err)
+ t.Fatalf("failed to getsockname: %s", err)
}
return sa
}
// GetSockNameWithErrno calls getsockname on the DUT.
-func (dut *DUT) GetSockNameWithErrno(ctx context.Context, sockfd int32) (int32, unix.Sockaddr, error) {
- dut.t.Helper()
+func (dut *DUT) GetSockNameWithErrno(ctx context.Context, t *testing.T, sockfd int32) (int32, unix.Sockaddr, error) {
+ t.Helper()
+
req := pb.GetSockNameRequest{
Sockfd: sockfd,
}
resp, err := dut.posixServer.GetSockName(ctx, &req)
if err != nil {
- dut.t.Fatalf("failed to call Bind: %s", err)
+ t.Fatalf("failed to call Bind: %s", err)
}
- return resp.GetRet(), dut.protoToSockaddr(resp.GetAddr()), syscall.Errno(resp.GetErrno_())
+ return resp.GetRet(), dut.protoToSockaddr(t, resp.GetAddr()), syscall.Errno(resp.GetErrno_())
}
-func (dut *DUT) getSockOpt(ctx context.Context, sockfd, level, optname, optlen int32, typ pb.GetSockOptRequest_SockOptType) (int32, *pb.SockOptVal, error) {
- dut.t.Helper()
+func (dut *DUT) getSockOpt(ctx context.Context, t *testing.T, sockfd, level, optname, optlen int32, typ pb.GetSockOptRequest_SockOptType) (int32, *pb.SockOptVal, error) {
+ t.Helper()
+
req := pb.GetSockOptRequest{
Sockfd: sockfd,
Level: level,
@@ -292,11 +349,11 @@ func (dut *DUT) getSockOpt(ctx context.Context, sockfd, level, optname, optlen i
}
resp, err := dut.posixServer.GetSockOpt(ctx, &req)
if err != nil {
- dut.t.Fatalf("failed to call GetSockOpt: %s", err)
+ t.Fatalf("failed to call GetSockOpt: %s", err)
}
optval := resp.GetOptval()
if optval == nil {
- dut.t.Fatalf("GetSockOpt response does not contain a value")
+ t.Fatalf("GetSockOpt response does not contain a value")
}
return resp.GetRet(), optval, syscall.Errno(resp.GetErrno_())
}
@@ -306,13 +363,14 @@ func (dut *DUT) getSockOpt(ctx context.Context, sockfd, level, optname, optlen i
// needed, use GetSockOptWithErrno. Because endianess and the width of values
// might differ between the testbench and DUT architectures, prefer to use a
// more specific GetSockOptXxx function.
-func (dut *DUT) GetSockOpt(sockfd, level, optname, optlen int32) []byte {
- dut.t.Helper()
+func (dut *DUT) GetSockOpt(t *testing.T, sockfd, level, optname, optlen int32) []byte {
+ t.Helper()
+
ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
defer cancel()
- ret, optval, err := dut.GetSockOptWithErrno(ctx, sockfd, level, optname, optlen)
+ ret, optval, err := dut.GetSockOptWithErrno(ctx, t, sockfd, level, optname, optlen)
if ret != 0 {
- dut.t.Fatalf("failed to GetSockOpt: %s", err)
+ t.Fatalf("failed to GetSockOpt: %s", err)
}
return optval
}
@@ -320,12 +378,13 @@ func (dut *DUT) GetSockOpt(sockfd, level, optname, optlen int32) []byte {
// GetSockOptWithErrno calls getsockopt on the DUT. Because endianess and the
// width of values might differ between the testbench and DUT architectures,
// prefer to use a more specific GetSockOptXxxWithErrno function.
-func (dut *DUT) GetSockOptWithErrno(ctx context.Context, sockfd, level, optname, optlen int32) (int32, []byte, error) {
- dut.t.Helper()
- ret, optval, errno := dut.getSockOpt(ctx, sockfd, level, optname, optlen, pb.GetSockOptRequest_BYTES)
+func (dut *DUT) GetSockOptWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname, optlen int32) (int32, []byte, error) {
+ t.Helper()
+
+ ret, optval, errno := dut.getSockOpt(ctx, t, sockfd, level, optname, optlen, pb.GetSockOptRequest_BYTES)
bytesval, ok := optval.Val.(*pb.SockOptVal_Bytesval)
if !ok {
- dut.t.Fatalf("GetSockOpt got value type: %T, want bytes", optval)
+ t.Fatalf("GetSockOpt got value type: %T, want bytes", optval.Val)
}
return ret, bytesval.Bytesval, errno
}
@@ -333,24 +392,26 @@ func (dut *DUT) GetSockOptWithErrno(ctx context.Context, sockfd, level, optname,
// GetSockOptInt calls getsockopt on the DUT and causes a fatal test failure
// if it doesn't succeed. If more control over the int optval or error handling
// is needed, use GetSockOptIntWithErrno.
-func (dut *DUT) GetSockOptInt(sockfd, level, optname int32) int32 {
- dut.t.Helper()
+func (dut *DUT) GetSockOptInt(t *testing.T, sockfd, level, optname int32) int32 {
+ t.Helper()
+
ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
defer cancel()
- ret, intval, err := dut.GetSockOptIntWithErrno(ctx, sockfd, level, optname)
+ ret, intval, err := dut.GetSockOptIntWithErrno(ctx, t, sockfd, level, optname)
if ret != 0 {
- dut.t.Fatalf("failed to GetSockOptInt: %s", err)
+ t.Fatalf("failed to GetSockOptInt: %s", err)
}
return intval
}
// GetSockOptIntWithErrno calls getsockopt with an integer optval.
-func (dut *DUT) GetSockOptIntWithErrno(ctx context.Context, sockfd, level, optname int32) (int32, int32, error) {
- dut.t.Helper()
- ret, optval, errno := dut.getSockOpt(ctx, sockfd, level, optname, 0, pb.GetSockOptRequest_INT)
+func (dut *DUT) GetSockOptIntWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname int32) (int32, int32, error) {
+ t.Helper()
+
+ ret, optval, errno := dut.getSockOpt(ctx, t, sockfd, level, optname, 0, pb.GetSockOptRequest_INT)
intval, ok := optval.Val.(*pb.SockOptVal_Intval)
if !ok {
- dut.t.Fatalf("GetSockOpt got value type: %T, want int", optval)
+ t.Fatalf("GetSockOpt got value type: %T, want int", optval.Val)
}
return ret, intval.Intval, errno
}
@@ -358,24 +419,26 @@ func (dut *DUT) GetSockOptIntWithErrno(ctx context.Context, sockfd, level, optna
// GetSockOptTimeval calls getsockopt on the DUT and causes a fatal test failure
// if it doesn't succeed. If more control over the timeout or error handling is
// needed, use GetSockOptTimevalWithErrno.
-func (dut *DUT) GetSockOptTimeval(sockfd, level, optname int32) unix.Timeval {
- dut.t.Helper()
+func (dut *DUT) GetSockOptTimeval(t *testing.T, sockfd, level, optname int32) unix.Timeval {
+ t.Helper()
+
ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
defer cancel()
- ret, timeval, err := dut.GetSockOptTimevalWithErrno(ctx, sockfd, level, optname)
+ ret, timeval, err := dut.GetSockOptTimevalWithErrno(ctx, t, sockfd, level, optname)
if ret != 0 {
- dut.t.Fatalf("failed to GetSockOptTimeval: %s", err)
+ t.Fatalf("failed to GetSockOptTimeval: %s", err)
}
return timeval
}
// GetSockOptTimevalWithErrno calls getsockopt and returns a timeval.
-func (dut *DUT) GetSockOptTimevalWithErrno(ctx context.Context, sockfd, level, optname int32) (int32, unix.Timeval, error) {
- dut.t.Helper()
- ret, optval, errno := dut.getSockOpt(ctx, sockfd, level, optname, 0, pb.GetSockOptRequest_TIME)
+func (dut *DUT) GetSockOptTimevalWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname int32) (int32, unix.Timeval, error) {
+ t.Helper()
+
+ ret, optval, errno := dut.getSockOpt(ctx, t, sockfd, level, optname, 0, pb.GetSockOptRequest_TIME)
tv, ok := optval.Val.(*pb.SockOptVal_Timeval)
if !ok {
- dut.t.Fatalf("GetSockOpt got value type: %T, want timeval", optval)
+ t.Fatalf("GetSockOpt got value type: %T, want timeval", optval.Val)
}
timeval := unix.Timeval{
Sec: tv.Timeval.Seconds,
@@ -387,26 +450,28 @@ func (dut *DUT) GetSockOptTimevalWithErrno(ctx context.Context, sockfd, level, o
// Listen calls listen on the DUT and causes a fatal test failure if it doesn't
// succeed. If more control over the timeout or error handling is needed, use
// ListenWithErrno.
-func (dut *DUT) Listen(sockfd, backlog int32) {
- dut.t.Helper()
+func (dut *DUT) Listen(t *testing.T, sockfd, backlog int32) {
+ t.Helper()
+
ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
defer cancel()
- ret, err := dut.ListenWithErrno(ctx, sockfd, backlog)
+ ret, err := dut.ListenWithErrno(ctx, t, sockfd, backlog)
if ret != 0 {
- dut.t.Fatalf("failed to listen: %s", err)
+ t.Fatalf("failed to listen: %s", err)
}
}
// ListenWithErrno calls listen on the DUT.
-func (dut *DUT) ListenWithErrno(ctx context.Context, sockfd, backlog int32) (int32, error) {
- dut.t.Helper()
+func (dut *DUT) ListenWithErrno(ctx context.Context, t *testing.T, sockfd, backlog int32) (int32, error) {
+ t.Helper()
+
req := pb.ListenRequest{
Sockfd: sockfd,
Backlog: backlog,
}
resp, err := dut.posixServer.Listen(ctx, &req)
if err != nil {
- dut.t.Fatalf("failed to call Listen: %s", err)
+ t.Fatalf("failed to call Listen: %s", err)
}
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
}
@@ -414,20 +479,22 @@ func (dut *DUT) ListenWithErrno(ctx context.Context, sockfd, backlog int32) (int
// Send calls send on the DUT and causes a fatal test failure if it doesn't
// succeed. If more control over the timeout or error handling is needed, use
// SendWithErrno.
-func (dut *DUT) Send(sockfd int32, buf []byte, flags int32) int32 {
- dut.t.Helper()
+func (dut *DUT) Send(t *testing.T, sockfd int32, buf []byte, flags int32) int32 {
+ t.Helper()
+
ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
defer cancel()
- ret, err := dut.SendWithErrno(ctx, sockfd, buf, flags)
+ ret, err := dut.SendWithErrno(ctx, t, sockfd, buf, flags)
if ret == -1 {
- dut.t.Fatalf("failed to send: %s", err)
+ t.Fatalf("failed to send: %s", err)
}
return ret
}
// SendWithErrno calls send on the DUT.
-func (dut *DUT) SendWithErrno(ctx context.Context, sockfd int32, buf []byte, flags int32) (int32, error) {
- dut.t.Helper()
+func (dut *DUT) SendWithErrno(ctx context.Context, t *testing.T, sockfd int32, buf []byte, flags int32) (int32, error) {
+ t.Helper()
+
req := pb.SendRequest{
Sockfd: sockfd,
Buf: buf,
@@ -435,7 +502,7 @@ func (dut *DUT) SendWithErrno(ctx context.Context, sockfd int32, buf []byte, fla
}
resp, err := dut.posixServer.Send(ctx, &req)
if err != nil {
- dut.t.Fatalf("failed to call Send: %s", err)
+ t.Fatalf("failed to call Send: %s", err)
}
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
}
@@ -443,35 +510,52 @@ func (dut *DUT) SendWithErrno(ctx context.Context, sockfd int32, buf []byte, fla
// SendTo calls sendto on the DUT and causes a fatal test failure if it doesn't
// succeed. If more control over the timeout or error handling is needed, use
// SendToWithErrno.
-func (dut *DUT) SendTo(sockfd int32, buf []byte, flags int32, destAddr unix.Sockaddr) int32 {
- dut.t.Helper()
+func (dut *DUT) SendTo(t *testing.T, sockfd int32, buf []byte, flags int32, destAddr unix.Sockaddr) int32 {
+ t.Helper()
+
ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
defer cancel()
- ret, err := dut.SendToWithErrno(ctx, sockfd, buf, flags, destAddr)
+ ret, err := dut.SendToWithErrno(ctx, t, sockfd, buf, flags, destAddr)
if ret == -1 {
- dut.t.Fatalf("failed to sendto: %s", err)
+ t.Fatalf("failed to sendto: %s", err)
}
return ret
}
// SendToWithErrno calls sendto on the DUT.
-func (dut *DUT) SendToWithErrno(ctx context.Context, sockfd int32, buf []byte, flags int32, destAddr unix.Sockaddr) (int32, error) {
- dut.t.Helper()
+func (dut *DUT) SendToWithErrno(ctx context.Context, t *testing.T, sockfd int32, buf []byte, flags int32, destAddr unix.Sockaddr) (int32, error) {
+ t.Helper()
+
req := pb.SendToRequest{
Sockfd: sockfd,
Buf: buf,
Flags: flags,
- DestAddr: dut.sockaddrToProto(destAddr),
+ DestAddr: dut.sockaddrToProto(t, destAddr),
}
resp, err := dut.posixServer.SendTo(ctx, &req)
if err != nil {
- dut.t.Fatalf("faled to call SendTo: %s", err)
+ t.Fatalf("faled to call SendTo: %s", err)
}
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
}
-func (dut *DUT) setSockOpt(ctx context.Context, sockfd, level, optname int32, optval *pb.SockOptVal) (int32, error) {
- dut.t.Helper()
+// SetNonBlocking will set O_NONBLOCK flag for fd if nonblocking
+// is true, otherwise it will clear the flag.
+func (dut *DUT) SetNonBlocking(t *testing.T, fd int32, nonblocking bool) {
+ t.Helper()
+
+ flags := dut.Fcntl(t, fd, unix.F_GETFL, 0)
+ if nonblocking {
+ flags |= unix.O_NONBLOCK
+ } else {
+ flags &= ^unix.O_NONBLOCK
+ }
+ dut.Fcntl(t, fd, unix.F_SETFL, flags)
+}
+
+func (dut *DUT) setSockOpt(ctx context.Context, t *testing.T, sockfd, level, optname int32, optval *pb.SockOptVal) (int32, error) {
+ t.Helper()
+
req := pb.SetSockOptRequest{
Sockfd: sockfd,
Level: level,
@@ -480,7 +564,7 @@ func (dut *DUT) setSockOpt(ctx context.Context, sockfd, level, optname int32, op
}
resp, err := dut.posixServer.SetSockOpt(ctx, &req)
if err != nil {
- dut.t.Fatalf("failed to call SetSockOpt: %s", err)
+ t.Fatalf("failed to call SetSockOpt: %s", err)
}
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
}
@@ -490,81 +574,89 @@ func (dut *DUT) setSockOpt(ctx context.Context, sockfd, level, optname int32, op
// needed, use SetSockOptWithErrno. Because endianess and the width of values
// might differ between the testbench and DUT architectures, prefer to use a
// more specific SetSockOptXxx function.
-func (dut *DUT) SetSockOpt(sockfd, level, optname int32, optval []byte) {
- dut.t.Helper()
+func (dut *DUT) SetSockOpt(t *testing.T, sockfd, level, optname int32, optval []byte) {
+ t.Helper()
+
ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
defer cancel()
- ret, err := dut.SetSockOptWithErrno(ctx, sockfd, level, optname, optval)
+ ret, err := dut.SetSockOptWithErrno(ctx, t, sockfd, level, optname, optval)
if ret != 0 {
- dut.t.Fatalf("failed to SetSockOpt: %s", err)
+ t.Fatalf("failed to SetSockOpt: %s", err)
}
}
// SetSockOptWithErrno calls setsockopt on the DUT. Because endianess and the
// width of values might differ between the testbench and DUT architectures,
// prefer to use a more specific SetSockOptXxxWithErrno function.
-func (dut *DUT) SetSockOptWithErrno(ctx context.Context, sockfd, level, optname int32, optval []byte) (int32, error) {
- dut.t.Helper()
- return dut.setSockOpt(ctx, sockfd, level, optname, &pb.SockOptVal{Val: &pb.SockOptVal_Bytesval{optval}})
+func (dut *DUT) SetSockOptWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname int32, optval []byte) (int32, error) {
+ t.Helper()
+
+ return dut.setSockOpt(ctx, t, sockfd, level, optname, &pb.SockOptVal{Val: &pb.SockOptVal_Bytesval{optval}})
}
// SetSockOptInt calls setsockopt on the DUT and causes a fatal test failure
// if it doesn't succeed. If more control over the int optval or error handling
// is needed, use SetSockOptIntWithErrno.
-func (dut *DUT) SetSockOptInt(sockfd, level, optname, optval int32) {
- dut.t.Helper()
+func (dut *DUT) SetSockOptInt(t *testing.T, sockfd, level, optname, optval int32) {
+ t.Helper()
+
ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
defer cancel()
- ret, err := dut.SetSockOptIntWithErrno(ctx, sockfd, level, optname, optval)
+ ret, err := dut.SetSockOptIntWithErrno(ctx, t, sockfd, level, optname, optval)
if ret != 0 {
- dut.t.Fatalf("failed to SetSockOptInt: %s", err)
+ t.Fatalf("failed to SetSockOptInt: %s", err)
}
}
// SetSockOptIntWithErrno calls setsockopt with an integer optval.
-func (dut *DUT) SetSockOptIntWithErrno(ctx context.Context, sockfd, level, optname, optval int32) (int32, error) {
- dut.t.Helper()
- return dut.setSockOpt(ctx, sockfd, level, optname, &pb.SockOptVal{Val: &pb.SockOptVal_Intval{optval}})
+func (dut *DUT) SetSockOptIntWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname, optval int32) (int32, error) {
+ t.Helper()
+
+ return dut.setSockOpt(ctx, t, sockfd, level, optname, &pb.SockOptVal{Val: &pb.SockOptVal_Intval{optval}})
}
// SetSockOptTimeval calls setsockopt on the DUT and causes a fatal test failure
// if it doesn't succeed. If more control over the timeout or error handling is
// needed, use SetSockOptTimevalWithErrno.
-func (dut *DUT) SetSockOptTimeval(sockfd, level, optname int32, tv *unix.Timeval) {
- dut.t.Helper()
+func (dut *DUT) SetSockOptTimeval(t *testing.T, sockfd, level, optname int32, tv *unix.Timeval) {
+ t.Helper()
+
ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
defer cancel()
- ret, err := dut.SetSockOptTimevalWithErrno(ctx, sockfd, level, optname, tv)
+ ret, err := dut.SetSockOptTimevalWithErrno(ctx, t, sockfd, level, optname, tv)
if ret != 0 {
- dut.t.Fatalf("failed to SetSockOptTimeval: %s", err)
+ t.Fatalf("failed to SetSockOptTimeval: %s", err)
}
}
// SetSockOptTimevalWithErrno calls setsockopt with the timeval converted to
// bytes.
-func (dut *DUT) SetSockOptTimevalWithErrno(ctx context.Context, sockfd, level, optname int32, tv *unix.Timeval) (int32, error) {
- dut.t.Helper()
+func (dut *DUT) SetSockOptTimevalWithErrno(ctx context.Context, t *testing.T, sockfd, level, optname int32, tv *unix.Timeval) (int32, error) {
+ t.Helper()
+
timeval := pb.Timeval{
Seconds: int64(tv.Sec),
Microseconds: int64(tv.Usec),
}
- return dut.setSockOpt(ctx, sockfd, level, optname, &pb.SockOptVal{Val: &pb.SockOptVal_Timeval{&timeval}})
+ return dut.setSockOpt(ctx, t, sockfd, level, optname, &pb.SockOptVal{Val: &pb.SockOptVal_Timeval{&timeval}})
}
// Socket calls socket on the DUT and returns the file descriptor. If socket
// fails on the DUT, the test ends.
-func (dut *DUT) Socket(domain, typ, proto int32) int32 {
- dut.t.Helper()
- fd, err := dut.SocketWithErrno(domain, typ, proto)
+func (dut *DUT) Socket(t *testing.T, domain, typ, proto int32) int32 {
+ t.Helper()
+
+ fd, err := dut.SocketWithErrno(t, domain, typ, proto)
if fd < 0 {
- dut.t.Fatalf("failed to create socket: %s", err)
+ t.Fatalf("failed to create socket: %s", err)
}
return fd
}
// SocketWithErrno calls socket on the DUT and returns the fd and errno.
-func (dut *DUT) SocketWithErrno(domain, typ, proto int32) (int32, error) {
- dut.t.Helper()
+func (dut *DUT) SocketWithErrno(t *testing.T, domain, typ, proto int32) (int32, error) {
+ t.Helper()
+
req := pb.SocketRequest{
Domain: domain,
Type: typ,
@@ -573,7 +665,7 @@ func (dut *DUT) SocketWithErrno(domain, typ, proto int32) (int32, error) {
ctx := context.Background()
resp, err := dut.posixServer.Socket(ctx, &req)
if err != nil {
- dut.t.Fatalf("failed to call Socket: %s", err)
+ t.Fatalf("failed to call Socket: %s", err)
}
return resp.GetFd(), syscall.Errno(resp.GetErrno_())
}
@@ -581,20 +673,22 @@ func (dut *DUT) SocketWithErrno(domain, typ, proto int32) (int32, error) {
// Recv calls recv on the DUT and causes a fatal test failure if it doesn't
// succeed. If more control over the timeout or error handling is needed, use
// RecvWithErrno.
-func (dut *DUT) Recv(sockfd, len, flags int32) []byte {
- dut.t.Helper()
+func (dut *DUT) Recv(t *testing.T, sockfd, len, flags int32) []byte {
+ t.Helper()
+
ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
defer cancel()
- ret, buf, err := dut.RecvWithErrno(ctx, sockfd, len, flags)
+ ret, buf, err := dut.RecvWithErrno(ctx, t, sockfd, len, flags)
if ret == -1 {
- dut.t.Fatalf("failed to recv: %s", err)
+ t.Fatalf("failed to recv: %s", err)
}
return buf
}
// RecvWithErrno calls recv on the DUT.
-func (dut *DUT) RecvWithErrno(ctx context.Context, sockfd, len, flags int32) (int32, []byte, error) {
- dut.t.Helper()
+func (dut *DUT) RecvWithErrno(ctx context.Context, t *testing.T, sockfd, len, flags int32) (int32, []byte, error) {
+ t.Helper()
+
req := pb.RecvRequest{
Sockfd: sockfd,
Len: len,
@@ -602,7 +696,7 @@ func (dut *DUT) RecvWithErrno(ctx context.Context, sockfd, len, flags int32) (in
}
resp, err := dut.posixServer.Recv(ctx, &req)
if err != nil {
- dut.t.Fatalf("failed to call Recv: %s", err)
+ t.Fatalf("failed to call Recv: %s", err)
}
return resp.GetRet(), resp.GetBuf(), syscall.Errno(resp.GetErrno_())
}
diff --git a/test/packetimpact/testbench/layers.go b/test/packetimpact/testbench/layers.go
index 1b0e5b8fc..24aa46cce 100644
--- a/test/packetimpact/testbench/layers.go
+++ b/test/packetimpact/testbench/layers.go
@@ -15,6 +15,7 @@
package testbench
import (
+ "encoding/binary"
"encoding/hex"
"fmt"
"reflect"
@@ -470,17 +471,11 @@ func (l *IPv6) ToBytes() ([]byte, error) {
if l.NextHeader != nil {
fields.NextHeader = *l.NextHeader
} else {
- switch n := l.next().(type) {
- case *TCP:
- fields.NextHeader = uint8(header.TCPProtocolNumber)
- case *UDP:
- fields.NextHeader = uint8(header.UDPProtocolNumber)
- case *ICMPv6:
- fields.NextHeader = uint8(header.ICMPv6ProtocolNumber)
- default:
- // TODO(b/150301488): Support more protocols as needed.
- return nil, fmt.Errorf("ToBytes can't deduce the IPv6 header's next protocol: %#v", n)
+ nh, err := nextHeaderByLayer(l.next())
+ if err != nil {
+ return nil, err
}
+ fields.NextHeader = nh
}
if l.HopLimit != nil {
fields.HopLimit = *l.HopLimit
@@ -495,6 +490,27 @@ func (l *IPv6) ToBytes() ([]byte, error) {
return h, nil
}
+// nextIPv6PayloadParser finds the corresponding parser for nextHeader.
+func nextIPv6PayloadParser(nextHeader uint8) layerParser {
+ switch tcpip.TransportProtocolNumber(nextHeader) {
+ case header.TCPProtocolNumber:
+ return parseTCP
+ case header.UDPProtocolNumber:
+ return parseUDP
+ case header.ICMPv6ProtocolNumber:
+ return parseICMPv6
+ }
+ switch header.IPv6ExtensionHeaderIdentifier(nextHeader) {
+ case header.IPv6HopByHopOptionsExtHdrIdentifier:
+ return parseIPv6HopByHopOptionsExtHdr
+ case header.IPv6DestinationOptionsExtHdrIdentifier:
+ return parseIPv6DestinationOptionsExtHdr
+ case header.IPv6FragmentExtHdrIdentifier:
+ return parseIPv6FragmentExtHdr
+ }
+ return parsePayload
+}
+
// parseIPv6 parses the bytes assuming that they start with an ipv6 header and
// continues parsing further encapsulations.
func parseIPv6(b []byte) (Layer, layerParser) {
@@ -509,18 +525,7 @@ func parseIPv6(b []byte) (Layer, layerParser) {
SrcAddr: Address(h.SourceAddress()),
DstAddr: Address(h.DestinationAddress()),
}
- var nextParser layerParser
- switch h.TransportProtocol() {
- case header.TCPProtocolNumber:
- nextParser = parseTCP
- case header.UDPProtocolNumber:
- nextParser = parseUDP
- case header.ICMPv6ProtocolNumber:
- nextParser = parseICMPv6
- default:
- // Assume that the rest is a payload.
- nextParser = parsePayload
- }
+ nextParser := nextIPv6PayloadParser(h.NextHeader())
return &ipv6, nextParser
}
@@ -538,13 +543,241 @@ func (l *IPv6) merge(other Layer) error {
return mergeLayer(l, other)
}
+// IPv6HopByHopOptionsExtHdr can construct and match an IPv6HopByHopOptions
+// Extension Header.
+type IPv6HopByHopOptionsExtHdr struct {
+ LayerBase
+ NextHeader *header.IPv6ExtensionHeaderIdentifier
+ Options []byte
+}
+
+// IPv6DestinationOptionsExtHdr can construct and match an IPv6DestinationOptions
+// Extension Header.
+type IPv6DestinationOptionsExtHdr struct {
+ LayerBase
+ NextHeader *header.IPv6ExtensionHeaderIdentifier
+ Options []byte
+}
+
+// IPv6FragmentExtHdr can construct and match an IPv6 Fragment Extension Header.
+type IPv6FragmentExtHdr struct {
+ LayerBase
+ NextHeader *header.IPv6ExtensionHeaderIdentifier
+ FragmentOffset *uint16
+ MoreFragments *bool
+ Identification *uint32
+}
+
+// nextHeaderByLayer finds the correct next header protocol value for layer l.
+func nextHeaderByLayer(l Layer) (uint8, error) {
+ if l == nil {
+ return uint8(header.IPv6NoNextHeaderIdentifier), nil
+ }
+ switch l.(type) {
+ case *TCP:
+ return uint8(header.TCPProtocolNumber), nil
+ case *UDP:
+ return uint8(header.UDPProtocolNumber), nil
+ case *ICMPv6:
+ return uint8(header.ICMPv6ProtocolNumber), nil
+ case *Payload:
+ return uint8(header.IPv6NoNextHeaderIdentifier), nil
+ case *IPv6HopByHopOptionsExtHdr:
+ return uint8(header.IPv6HopByHopOptionsExtHdrIdentifier), nil
+ case *IPv6DestinationOptionsExtHdr:
+ return uint8(header.IPv6DestinationOptionsExtHdrIdentifier), nil
+ case *IPv6FragmentExtHdr:
+ return uint8(header.IPv6FragmentExtHdrIdentifier), nil
+ default:
+ // TODO(b/161005083): Support more protocols as needed.
+ return 0, fmt.Errorf("failed to deduce the IPv6 header's next protocol: %T", l)
+ }
+}
+
+// ipv6OptionsExtHdrToBytes serializes an options extension header into bytes.
+func ipv6OptionsExtHdrToBytes(nextHeader *header.IPv6ExtensionHeaderIdentifier, nextLayer Layer, options []byte) ([]byte, error) {
+ length := len(options) + 2
+ if length%8 != 0 {
+ return nil, fmt.Errorf("IPv6 extension headers must be a multiple of 8 octets long, but the length given: %d, options: %s", length, hex.Dump(options))
+ }
+ bytes := make([]byte, length)
+ if nextHeader != nil {
+ bytes[0] = byte(*nextHeader)
+ } else {
+ nh, err := nextHeaderByLayer(nextLayer)
+ if err != nil {
+ return nil, err
+ }
+ bytes[0] = nh
+ }
+ // ExtHdrLen field is the length of the extension header
+ // in 8-octet unit, ignoring the first 8 octets.
+ // https://tools.ietf.org/html/rfc2460#section-4.3
+ // https://tools.ietf.org/html/rfc2460#section-4.6
+ bytes[1] = uint8((length - 8) / 8)
+ copy(bytes[2:], options)
+ return bytes, nil
+}
+
+// IPv6ExtHdrIdent is a helper routine that allocates a new
+// header.IPv6ExtensionHeaderIdentifier value to store v and returns a pointer
+// to it.
+func IPv6ExtHdrIdent(id header.IPv6ExtensionHeaderIdentifier) *header.IPv6ExtensionHeaderIdentifier {
+ return &id
+}
+
+// ToBytes implements Layer.ToBytes.
+func (l *IPv6HopByHopOptionsExtHdr) ToBytes() ([]byte, error) {
+ return ipv6OptionsExtHdrToBytes(l.NextHeader, l.next(), l.Options)
+}
+
+// ToBytes implements Layer.ToBytes.
+func (l *IPv6DestinationOptionsExtHdr) ToBytes() ([]byte, error) {
+ return ipv6OptionsExtHdrToBytes(l.NextHeader, l.next(), l.Options)
+}
+
+// ToBytes implements Layer.ToBytes.
+func (l *IPv6FragmentExtHdr) ToBytes() ([]byte, error) {
+ var offset, mflag uint16
+ var ident uint32
+ bytes := make([]byte, header.IPv6FragmentExtHdrLength)
+ if l.NextHeader != nil {
+ bytes[0] = byte(*l.NextHeader)
+ } else {
+ nh, err := nextHeaderByLayer(l.next())
+ if err != nil {
+ return nil, err
+ }
+ bytes[0] = nh
+ }
+ bytes[1] = 0 // reserved
+ if l.MoreFragments != nil && *l.MoreFragments {
+ mflag = 1
+ }
+ if l.FragmentOffset != nil {
+ offset = *l.FragmentOffset
+ }
+ if l.Identification != nil {
+ ident = *l.Identification
+ }
+ offsetAndMflag := offset<<3 | mflag
+ binary.BigEndian.PutUint16(bytes[2:], offsetAndMflag)
+ binary.BigEndian.PutUint32(bytes[4:], ident)
+
+ return bytes, nil
+}
+
+// parseIPv6ExtHdr parses an IPv6 extension header and returns the NextHeader
+// field, the rest of the payload and a parser function for the corresponding
+// next extension header.
+func parseIPv6ExtHdr(b []byte) (header.IPv6ExtensionHeaderIdentifier, []byte, layerParser) {
+ nextHeader := b[0]
+ // For HopByHop and Destination options extension headers,
+ // This field is the length of the extension header in
+ // 8-octet units, not including the first 8 octets.
+ // https://tools.ietf.org/html/rfc2460#section-4.3
+ // https://tools.ietf.org/html/rfc2460#section-4.6
+ length := b[1]*8 + 8
+ data := b[2:length]
+ nextParser := nextIPv6PayloadParser(nextHeader)
+ return header.IPv6ExtensionHeaderIdentifier(nextHeader), data, nextParser
+}
+
+// parseIPv6HopByHopOptionsExtHdr parses the bytes assuming that they start
+// with an IPv6 HopByHop Options Extension Header.
+func parseIPv6HopByHopOptionsExtHdr(b []byte) (Layer, layerParser) {
+ nextHeader, options, nextParser := parseIPv6ExtHdr(b)
+ return &IPv6HopByHopOptionsExtHdr{NextHeader: &nextHeader, Options: options}, nextParser
+}
+
+// parseIPv6DestinationOptionsExtHdr parses the bytes assuming that they start
+// with an IPv6 Destination Options Extension Header.
+func parseIPv6DestinationOptionsExtHdr(b []byte) (Layer, layerParser) {
+ nextHeader, options, nextParser := parseIPv6ExtHdr(b)
+ return &IPv6DestinationOptionsExtHdr{NextHeader: &nextHeader, Options: options}, nextParser
+}
+
+// Bool is a helper routine that allocates a new
+// bool value to store v and returns a pointer to it.
+func Bool(v bool) *bool {
+ return &v
+}
+
+// parseIPv6FragmentExtHdr parses the bytes assuming that they start
+// with an IPv6 Fragment Extension Header.
+func parseIPv6FragmentExtHdr(b []byte) (Layer, layerParser) {
+ nextHeader := b[0]
+ var extHdr header.IPv6FragmentExtHdr
+ copy(extHdr[:], b[2:])
+ return &IPv6FragmentExtHdr{
+ NextHeader: IPv6ExtHdrIdent(header.IPv6ExtensionHeaderIdentifier(nextHeader)),
+ FragmentOffset: Uint16(extHdr.FragmentOffset()),
+ MoreFragments: Bool(extHdr.More()),
+ Identification: Uint32(extHdr.ID()),
+ }, nextIPv6PayloadParser(nextHeader)
+}
+
+func (l *IPv6HopByHopOptionsExtHdr) length() int {
+ return len(l.Options) + 2
+}
+
+func (l *IPv6HopByHopOptionsExtHdr) match(other Layer) bool {
+ return equalLayer(l, other)
+}
+
+// merge overrides the values in l with the values from other but only in fields
+// where the value is not nil.
+func (l *IPv6HopByHopOptionsExtHdr) merge(other Layer) error {
+ return mergeLayer(l, other)
+}
+
+func (l *IPv6HopByHopOptionsExtHdr) String() string {
+ return stringLayer(l)
+}
+
+func (l *IPv6DestinationOptionsExtHdr) length() int {
+ return len(l.Options) + 2
+}
+
+func (l *IPv6DestinationOptionsExtHdr) match(other Layer) bool {
+ return equalLayer(l, other)
+}
+
+// merge overrides the values in l with the values from other but only in fields
+// where the value is not nil.
+func (l *IPv6DestinationOptionsExtHdr) merge(other Layer) error {
+ return mergeLayer(l, other)
+}
+
+func (l *IPv6DestinationOptionsExtHdr) String() string {
+ return stringLayer(l)
+}
+
+func (*IPv6FragmentExtHdr) length() int {
+ return header.IPv6FragmentExtHdrLength
+}
+
+func (l *IPv6FragmentExtHdr) match(other Layer) bool {
+ return equalLayer(l, other)
+}
+
+// merge overrides the values in l with the values from other but only in fields
+// where the value is not nil.
+func (l *IPv6FragmentExtHdr) merge(other Layer) error {
+ return mergeLayer(l, other)
+}
+
+func (l *IPv6FragmentExtHdr) String() string {
+ return stringLayer(l)
+}
+
// ICMPv6 can construct and match an ICMPv6 encapsulation.
type ICMPv6 struct {
LayerBase
- Type *header.ICMPv6Type
- Code *byte
- Checksum *uint16
- NDPPayload []byte
+ Type *header.ICMPv6Type
+ Code *byte
+ Checksum *uint16
+ Payload []byte
}
func (l *ICMPv6) String() string {
@@ -555,7 +788,7 @@ func (l *ICMPv6) String() string {
// ToBytes implements Layer.ToBytes.
func (l *ICMPv6) ToBytes() ([]byte, error) {
- b := make([]byte, header.ICMPv6HeaderSize+len(l.NDPPayload))
+ b := make([]byte, header.ICMPv6HeaderSize+len(l.Payload))
h := header.ICMPv6(b)
if l.Type != nil {
h.SetType(*l.Type)
@@ -563,12 +796,23 @@ func (l *ICMPv6) ToBytes() ([]byte, error) {
if l.Code != nil {
h.SetCode(*l.Code)
}
- copy(h.NDPPayload(), l.NDPPayload)
+ copy(h.NDPPayload(), l.Payload)
if l.Checksum != nil {
h.SetChecksum(*l.Checksum)
} else {
- ipv6 := l.Prev().(*IPv6)
- h.SetChecksum(header.ICMPv6Checksum(h, *ipv6.SrcAddr, *ipv6.DstAddr, buffer.VectorisedView{}))
+ // It is possible that the ICMPv6 header does not follow the IPv6 header
+ // immediately, there could be one or more extension headers in between.
+ // We need to search forward to find the IPv6 header.
+ for prev := l.Prev(); prev != nil; prev = prev.Prev() {
+ if ipv6, ok := prev.(*IPv6); ok {
+ payload, err := payload(l)
+ if err != nil {
+ return nil, err
+ }
+ h.SetChecksum(header.ICMPv6Checksum(h, *ipv6.SrcAddr, *ipv6.DstAddr, payload))
+ break
+ }
+ }
}
return h, nil
}
@@ -589,10 +833,10 @@ func Byte(v byte) *byte {
func parseICMPv6(b []byte) (Layer, layerParser) {
h := header.ICMPv6(b)
icmpv6 := ICMPv6{
- Type: ICMPv6Type(h.Type()),
- Code: Byte(h.Code()),
- Checksum: Uint16(h.Checksum()),
- NDPPayload: h.NDPPayload(),
+ Type: ICMPv6Type(h.Type()),
+ Code: Byte(h.Code()),
+ Checksum: Uint16(h.Checksum()),
+ Payload: h.NDPPayload(),
}
return &icmpv6, nil
}
@@ -602,7 +846,7 @@ func (l *ICMPv6) match(other Layer) bool {
}
func (l *ICMPv6) length() int {
- return header.ICMPv6HeaderSize + len(l.NDPPayload)
+ return header.ICMPv6HeaderSize + len(l.Payload)
}
// merge overrides the values in l with the values from other but only in fields
@@ -768,12 +1012,14 @@ func payload(l Layer) (buffer.VectorisedView, error) {
func layerChecksum(l Layer, protoNumber tcpip.TransportProtocolNumber) (uint16, error) {
totalLength := uint16(totalLength(l))
var xsum uint16
- switch s := l.Prev().(type) {
+ switch p := l.Prev().(type) {
case *IPv4:
- xsum = header.PseudoHeaderChecksum(protoNumber, *s.SrcAddr, *s.DstAddr, totalLength)
+ xsum = header.PseudoHeaderChecksum(protoNumber, *p.SrcAddr, *p.DstAddr, totalLength)
+ case *IPv6:
+ xsum = header.PseudoHeaderChecksum(protoNumber, *p.SrcAddr, *p.DstAddr, totalLength)
default:
- // TODO(b/150301488): Support more protocols, like IPv6.
- return 0, fmt.Errorf("can't get src and dst addr from previous layer: %#v", s)
+ // TODO(b/161246171): Support more protocols.
+ return 0, fmt.Errorf("checksum for protocol %d is not supported when previous layer is %T", protoNumber, p)
}
payloadBytes, err := payload(l)
if err != nil {
@@ -939,6 +1185,11 @@ func (l *Payload) ToBytes() ([]byte, error) {
return l.Bytes, nil
}
+// Length returns payload byte length.
+func (l *Payload) Length() int {
+ return l.length()
+}
+
func (l *Payload) match(other Layer) bool {
return equalLayer(l, other)
}
diff --git a/test/packetimpact/testbench/layers_test.go b/test/packetimpact/testbench/layers_test.go
index c7f00e70d..a2a763034 100644
--- a/test/packetimpact/testbench/layers_test.go
+++ b/test/packetimpact/testbench/layers_test.go
@@ -505,3 +505,224 @@ func TestTCPOptions(t *testing.T) {
})
}
}
+
+func TestIPv6ExtHdrOptions(t *testing.T) {
+ for _, tt := range []struct {
+ description string
+ wantBytes []byte
+ wantLayers Layers
+ }{
+ {
+ description: "IPv6/HopByHop",
+ wantBytes: []byte{
+ // IPv6 Header
+ 0x60, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
+ // HopByHop Options
+ 0x3b, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00,
+ },
+ wantLayers: []Layer{
+ &IPv6{
+ SrcAddr: Address(tcpip.Address(net.ParseIP("::1"))),
+ DstAddr: Address(tcpip.Address(net.ParseIP("fe80::dead:beef"))),
+ },
+ &IPv6HopByHopOptionsExtHdr{
+ NextHeader: IPv6ExtHdrIdent(header.IPv6NoNextHeaderIdentifier),
+ Options: []byte{0x05, 0x02, 0x00, 0x00, 0x01, 0x00},
+ },
+ &Payload{
+ Bytes: nil,
+ },
+ },
+ },
+ {
+ description: "IPv6/HopByHop/Payload",
+ wantBytes: []byte{
+ // IPv6 Header
+ 0x60, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
+ // HopByHop Options
+ 0x3b, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00,
+ // Sample Data
+ 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x44, 0x61, 0x74, 0x61,
+ },
+ wantLayers: []Layer{
+ &IPv6{
+ SrcAddr: Address(tcpip.Address(net.ParseIP("::1"))),
+ DstAddr: Address(tcpip.Address(net.ParseIP("fe80::dead:beef"))),
+ },
+ &IPv6HopByHopOptionsExtHdr{
+ NextHeader: IPv6ExtHdrIdent(header.IPv6NoNextHeaderIdentifier),
+ Options: []byte{0x05, 0x02, 0x00, 0x00, 0x01, 0x00},
+ },
+ &Payload{
+ Bytes: []byte("Sample Data"),
+ },
+ },
+ },
+ {
+ description: "IPv6/HopByHop/Destination/ICMPv6",
+ wantBytes: []byte{
+ // IPv6 Header
+ 0x60, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
+ // HopByHop Options
+ 0x3c, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00,
+ // Destination Options
+ 0x3a, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00,
+ // ICMPv6 Param Problem
+ 0x04, 0x00, 0x5f, 0x98, 0x00, 0x00, 0x00, 0x06,
+ },
+ wantLayers: []Layer{
+ &IPv6{
+ SrcAddr: Address(tcpip.Address(net.ParseIP("::1"))),
+ DstAddr: Address(tcpip.Address(net.ParseIP("fe80::dead:beef"))),
+ },
+ &IPv6HopByHopOptionsExtHdr{
+ NextHeader: IPv6ExtHdrIdent(header.IPv6DestinationOptionsExtHdrIdentifier),
+ Options: []byte{0x05, 0x02, 0x00, 0x00, 0x01, 0x00},
+ },
+ &IPv6DestinationOptionsExtHdr{
+ NextHeader: IPv6ExtHdrIdent(header.IPv6ExtensionHeaderIdentifier(header.ICMPv6ProtocolNumber)),
+ Options: []byte{0x05, 0x02, 0x00, 0x00, 0x01, 0x00},
+ },
+ &ICMPv6{
+ Type: ICMPv6Type(header.ICMPv6ParamProblem),
+ Code: Byte(0),
+ Checksum: Uint16(0x5f98),
+ Payload: []byte{0x00, 0x00, 0x00, 0x06},
+ },
+ },
+ },
+ {
+ description: "IPv6/HopByHop/Fragment",
+ wantBytes: []byte{
+ // IPv6 Header
+ 0x60, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
+ // HopByHop Options
+ 0x2c, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00,
+ // Fragment ExtHdr
+ 0x3b, 0x00, 0x03, 0x20, 0x00, 0x00, 0x00, 0x2a,
+ },
+ wantLayers: []Layer{
+ &IPv6{
+ SrcAddr: Address(tcpip.Address(net.ParseIP("::1"))),
+ DstAddr: Address(tcpip.Address(net.ParseIP("fe80::dead:beef"))),
+ },
+ &IPv6HopByHopOptionsExtHdr{
+ NextHeader: IPv6ExtHdrIdent(header.IPv6FragmentExtHdrIdentifier),
+ Options: []byte{0x05, 0x02, 0x00, 0x00, 0x01, 0x00},
+ },
+ &IPv6FragmentExtHdr{
+ NextHeader: IPv6ExtHdrIdent(header.IPv6NoNextHeaderIdentifier),
+ FragmentOffset: Uint16(100),
+ MoreFragments: Bool(false),
+ Identification: Uint32(42),
+ },
+ &Payload{
+ Bytes: nil,
+ },
+ },
+ },
+ {
+ description: "IPv6/DestOpt/Fragment/Payload",
+ wantBytes: []byte{
+ // IPv6 Header
+ 0x60, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x3c, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
+ // Destination Options
+ 0x2c, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00,
+ // Fragment ExtHdr
+ 0x3b, 0x00, 0x03, 0x21, 0x00, 0x00, 0x00, 0x2a,
+ // Sample Data
+ 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x44, 0x61, 0x74, 0x61,
+ },
+ wantLayers: []Layer{
+ &IPv6{
+ SrcAddr: Address(tcpip.Address(net.ParseIP("::1"))),
+ DstAddr: Address(tcpip.Address(net.ParseIP("fe80::dead:beef"))),
+ },
+ &IPv6DestinationOptionsExtHdr{
+ NextHeader: IPv6ExtHdrIdent(header.IPv6FragmentExtHdrIdentifier),
+ Options: []byte{0x05, 0x02, 0x00, 0x00, 0x01, 0x00},
+ },
+ &IPv6FragmentExtHdr{
+ NextHeader: IPv6ExtHdrIdent(header.IPv6NoNextHeaderIdentifier),
+ FragmentOffset: Uint16(100),
+ MoreFragments: Bool(true),
+ Identification: Uint32(42),
+ },
+ &Payload{
+ Bytes: []byte("Sample Data"),
+ },
+ },
+ },
+ {
+ description: "IPv6/Fragment/Payload",
+ wantBytes: []byte{
+ // IPv6 Header
+ 0x60, 0x00, 0x00, 0x00, 0x00, 0x13, 0x2c, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
+ // Fragment ExtHdr
+ 0x3b, 0x00, 0x03, 0x21, 0x00, 0x00, 0x00, 0x2a,
+ // Sample Data
+ 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x44, 0x61, 0x74, 0x61,
+ },
+ wantLayers: []Layer{
+ &IPv6{
+ SrcAddr: Address(tcpip.Address(net.ParseIP("::1"))),
+ DstAddr: Address(tcpip.Address(net.ParseIP("fe80::dead:beef"))),
+ },
+ &IPv6FragmentExtHdr{
+ NextHeader: IPv6ExtHdrIdent(header.IPv6NoNextHeaderIdentifier),
+ FragmentOffset: Uint16(100),
+ MoreFragments: Bool(true),
+ Identification: Uint32(42),
+ },
+ &Payload{
+ Bytes: []byte("Sample Data"),
+ },
+ },
+ },
+ } {
+ t.Run(tt.description, func(t *testing.T) {
+ layers := parse(parseIPv6, tt.wantBytes)
+ if !layers.match(tt.wantLayers) {
+ t.Fatalf("match failed with diff: %s", layers.diff(tt.wantLayers))
+ }
+ // Make sure we can generate correct next header values and checksums
+ for _, layer := range layers {
+ switch layer := layer.(type) {
+ case *IPv6HopByHopOptionsExtHdr:
+ layer.NextHeader = nil
+ case *IPv6DestinationOptionsExtHdr:
+ layer.NextHeader = nil
+ case *IPv6FragmentExtHdr:
+ layer.NextHeader = nil
+ case *ICMPv6:
+ layer.Checksum = nil
+ }
+ }
+ gotBytes, err := layers.ToBytes()
+ if err != nil {
+ t.Fatalf("ToBytes() failed on %s: %s", &layers, err)
+ }
+ if !bytes.Equal(tt.wantBytes, gotBytes) {
+ t.Fatalf("mismatching bytes, gotBytes: %x, wantBytes: %x", gotBytes, tt.wantBytes)
+ }
+ })
+ }
+}
diff --git a/test/packetimpact/testbench/rawsockets.go b/test/packetimpact/testbench/rawsockets.go
index 4665f60b2..57e822725 100644
--- a/test/packetimpact/testbench/rawsockets.go
+++ b/test/packetimpact/testbench/rawsockets.go
@@ -16,7 +16,6 @@ package testbench
import (
"encoding/binary"
- "flag"
"fmt"
"math"
"net"
@@ -29,7 +28,6 @@ import (
// Sniffer can sniff raw packets on the wire.
type Sniffer struct {
- t *testing.T
fd int
}
@@ -41,7 +39,8 @@ func htons(x uint16) uint16 {
// NewSniffer creates a Sniffer connected to *device.
func NewSniffer(t *testing.T) (Sniffer, error) {
- flag.Parse()
+ t.Helper()
+
snifferFd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, int(htons(unix.ETH_P_ALL)))
if err != nil {
return Sniffer{}, err
@@ -53,7 +52,6 @@ func NewSniffer(t *testing.T) (Sniffer, error) {
t.Fatalf("can't setsockopt SO_RCVBUF to 10M: %s", err)
}
return Sniffer{
- t: t,
fd: snifferFd,
}, nil
}
@@ -63,7 +61,9 @@ func NewSniffer(t *testing.T) (Sniffer, error) {
const maxReadSize int = 65536
// Recv tries to read one frame until the timeout is up.
-func (s *Sniffer) Recv(timeout time.Duration) []byte {
+func (s *Sniffer) Recv(t *testing.T, timeout time.Duration) []byte {
+ t.Helper()
+
deadline := time.Now().Add(timeout)
for {
timeout = deadline.Sub(time.Now())
@@ -77,7 +77,7 @@ func (s *Sniffer) Recv(timeout time.Duration) []byte {
}
if err := unix.SetsockoptTimeval(s.fd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv); err != nil {
- s.t.Fatalf("can't setsockopt SO_RCVTIMEO: %s", err)
+ t.Fatalf("can't setsockopt SO_RCVTIMEO: %s", err)
}
buf := make([]byte, maxReadSize)
@@ -87,10 +87,10 @@ func (s *Sniffer) Recv(timeout time.Duration) []byte {
continue
}
if err != nil {
- s.t.Fatalf("can't read: %s", err)
+ t.Fatalf("can't read: %s", err)
}
if nread > maxReadSize {
- s.t.Fatalf("received a truncated frame of %d bytes", nread)
+ t.Fatalf("received a truncated frame of %d bytes, want at most %d bytes", nread, maxReadSize)
}
return buf[:nread]
}
@@ -98,14 +98,16 @@ func (s *Sniffer) Recv(timeout time.Duration) []byte {
// Drain drains the Sniffer's socket receive buffer by receiving until there's
// nothing else to receive.
-func (s *Sniffer) Drain() {
- s.t.Helper()
+func (s *Sniffer) Drain(t *testing.T) {
+ t.Helper()
+
flags, err := unix.FcntlInt(uintptr(s.fd), unix.F_GETFL, 0)
if err != nil {
- s.t.Fatalf("failed to get sniffer socket fd flags: %s", err)
+ t.Fatalf("failed to get sniffer socket fd flags: %s", err)
}
- if _, err := unix.FcntlInt(uintptr(s.fd), unix.F_SETFL, flags|unix.O_NONBLOCK); err != nil {
- s.t.Fatalf("failed to make sniffer socket non-blocking: %s", err)
+ nonBlockingFlags := flags | unix.O_NONBLOCK
+ if _, err := unix.FcntlInt(uintptr(s.fd), unix.F_SETFL, nonBlockingFlags); err != nil {
+ t.Fatalf("failed to make sniffer socket non-blocking with flags %b: %s", nonBlockingFlags, err)
}
for {
buf := make([]byte, maxReadSize)
@@ -115,7 +117,7 @@ func (s *Sniffer) Drain() {
}
}
if _, err := unix.FcntlInt(uintptr(s.fd), unix.F_SETFL, flags); err != nil {
- s.t.Fatalf("failed to restore sniffer socket fd flags: %s", err)
+ t.Fatalf("failed to restore sniffer socket fd flags to %b: %s", flags, err)
}
}
@@ -130,13 +132,13 @@ func (s *Sniffer) close() error {
// Injector can inject raw frames.
type Injector struct {
- t *testing.T
fd int
}
// NewInjector creates a new injector on *device.
func NewInjector(t *testing.T) (Injector, error) {
- flag.Parse()
+ t.Helper()
+
ifInfo, err := net.InterfaceByName(Device)
if err != nil {
return Injector{}, err
@@ -159,15 +161,20 @@ func NewInjector(t *testing.T) (Injector, error) {
return Injector{}, err
}
return Injector{
- t: t,
fd: injectFd,
}, nil
}
// Send a raw frame.
-func (i *Injector) Send(b []byte) {
- if _, err := unix.Write(i.fd, b); err != nil {
- i.t.Fatalf("can't write: %s of len %d", err, len(b))
+func (i *Injector) Send(t *testing.T, b []byte) {
+ t.Helper()
+
+ n, err := unix.Write(i.fd, b)
+ if err != nil {
+ t.Fatalf("can't write bytes of len %d: %s", len(b), err)
+ }
+ if n != len(b) {
+ t.Fatalf("got %d bytes written, want %d", n, len(b))
}
}
diff --git a/test/packetimpact/testbench/testbench.go b/test/packetimpact/testbench/testbench.go
index a1242b189..242464e3a 100644
--- a/test/packetimpact/testbench/testbench.go
+++ b/test/packetimpact/testbench/testbench.go
@@ -16,29 +16,52 @@ package testbench
import (
"flag"
+ "fmt"
+ "math/rand"
+ "net"
+ "os/exec"
+ "testing"
"time"
+
+ "gvisor.dev/gvisor/test/packetimpact/netdevs"
)
var (
+ // DUTType is the type of device under test.
+ DUTType = ""
// Device is the local device on the test network.
Device = ""
+
// LocalIPv4 is the local IPv4 address on the test network.
LocalIPv4 = ""
+ // RemoteIPv4 is the DUT's IPv4 address on the test network.
+ RemoteIPv4 = ""
+ // IPv4PrefixLength is the network prefix length of the IPv4 test network.
+ IPv4PrefixLength = 0
+
// LocalIPv6 is the local IPv6 address on the test network.
LocalIPv6 = ""
+ // RemoteIPv6 is the DUT's IPv6 address on the test network.
+ RemoteIPv6 = ""
+
+ // LocalInterfaceID is the ID of the local interface on the test network.
+ LocalInterfaceID uint32
+ // RemoteInterfaceID is the ID of the remote interface on the test network.
+ //
+ // Not using uint32 because package flag does not support uint32.
+ RemoteInterfaceID uint64
+
// LocalMAC is the local MAC address on the test network.
LocalMAC = ""
+ // RemoteMAC is the DUT's MAC address on the test network.
+ RemoteMAC = ""
+
// POSIXServerIP is the POSIX server's IP address on the control network.
POSIXServerIP = ""
// POSIXServerPort is the UDP port the POSIX server is bound to on the
// control network.
POSIXServerPort = 40000
- // RemoteIPv4 is the DUT's IPv4 address on the test network.
- RemoteIPv4 = ""
- // RemoteIPv6 is the DUT's IPv6 address on the test network.
- RemoteIPv6 = ""
- // RemoteMAC is the DUT's MAC address on the test network.
- RemoteMAC = ""
+
// RPCKeepalive is the gRPC keepalive.
RPCKeepalive = 10 * time.Second
// RPCTimeout is the gRPC timeout.
@@ -55,9 +78,51 @@ func RegisterFlags(fs *flag.FlagSet) {
fs.DurationVar(&RPCKeepalive, "rpc_keepalive", RPCKeepalive, "gRPC keepalive")
fs.StringVar(&LocalIPv4, "local_ipv4", LocalIPv4, "local IPv4 address for test packets")
fs.StringVar(&RemoteIPv4, "remote_ipv4", RemoteIPv4, "remote IPv4 address for test packets")
- fs.StringVar(&LocalIPv6, "local_ipv6", LocalIPv6, "local IPv6 address for test packets")
fs.StringVar(&RemoteIPv6, "remote_ipv6", RemoteIPv6, "remote IPv6 address for test packets")
- fs.StringVar(&LocalMAC, "local_mac", LocalMAC, "local mac address for test packets")
fs.StringVar(&RemoteMAC, "remote_mac", RemoteMAC, "remote mac address for test packets")
fs.StringVar(&Device, "device", Device, "local device for test packets")
+ fs.StringVar(&DUTType, "dut_type", DUTType, "type of device under test")
+ fs.Uint64Var(&RemoteInterfaceID, "remote_interface_id", RemoteInterfaceID, "remote interface ID for test packets")
+}
+
+// genPseudoFlags populates flag-like global config based on real flags.
+//
+// genPseudoFlags must only be called after flag.Parse.
+func genPseudoFlags() error {
+ out, err := exec.Command("ip", "addr", "show").CombinedOutput()
+ if err != nil {
+ return fmt.Errorf("listing devices: %q: %w", string(out), err)
+ }
+ devs, err := netdevs.ParseDevices(string(out))
+ if err != nil {
+ return fmt.Errorf("parsing devices: %w", err)
+ }
+
+ _, deviceInfo, err := netdevs.FindDeviceByIP(net.ParseIP(LocalIPv4), devs)
+ if err != nil {
+ return fmt.Errorf("can't find deviceInfo: %w", err)
+ }
+
+ LocalMAC = deviceInfo.MAC.String()
+ LocalIPv6 = deviceInfo.IPv6Addr.String()
+ LocalInterfaceID = deviceInfo.ID
+
+ if deviceInfo.IPv4Net != nil {
+ IPv4PrefixLength, _ = deviceInfo.IPv4Net.Mask.Size()
+ } else {
+ IPv4PrefixLength, _ = net.ParseIP(LocalIPv4).DefaultMask().Size()
+ }
+
+ return nil
+}
+
+// GenerateRandomPayload generates a random byte slice of the specified length,
+// causing a fatal test failure if it is unable to do so.
+func GenerateRandomPayload(t *testing.T, n int) []byte {
+ t.Helper()
+ buf := make([]byte, n)
+ if _, err := rand.Read(buf); err != nil {
+ t.Fatalf("rand.Read(buf) failed: %s", err)
+ }
+ return buf
}
diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD
index c4ffda17e..0c2a05380 100644
--- a/test/packetimpact/tests/BUILD
+++ b/test/packetimpact/tests/BUILD
@@ -1,4 +1,4 @@
-load("defs.bzl", "packetimpact_go_test")
+load("//test/packetimpact/runner:defs.bzl", "packetimpact_go_test")
package(
default_visibility = ["//test/packetimpact:__subpackages__"],
@@ -16,14 +16,48 @@ packetimpact_go_test(
)
packetimpact_go_test(
- name = "udp_recv_multicast",
- srcs = ["udp_recv_multicast_test.go"],
+ name = "ipv4_id_uniqueness",
+ srcs = ["ipv4_id_uniqueness_test.go"],
+ deps = [
+ "//pkg/abi/linux",
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+packetimpact_go_test(
+ name = "udp_discard_mcast_source_addr",
+ srcs = ["udp_discard_mcast_source_addr_test.go"],
+ deps = [
+ "//pkg/tcpip",
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+packetimpact_go_test(
+ name = "udp_recv_mcast_bcast",
+ srcs = ["udp_recv_mcast_bcast_test.go"],
# TODO(b/152813495): Fix netstack then remove the line below.
expect_netstack_failure = True,
deps = [
"//pkg/tcpip",
"//pkg/tcpip/header",
"//test/packetimpact/testbench",
+ "@com_github_google_go_cmp//cmp:go_default_library",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+packetimpact_go_test(
+ name = "udp_any_addr_recv_unicast",
+ srcs = ["udp_any_addr_recv_unicast_test.go"],
+ deps = [
+ "//pkg/tcpip",
+ "//test/packetimpact/testbench",
+ "@com_github_google_go_cmp//cmp:go_default_library",
"@org_golang_x_sys//unix:go_default_library",
],
)
@@ -40,6 +74,19 @@ packetimpact_go_test(
)
packetimpact_go_test(
+ 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",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+packetimpact_go_test(
name = "tcp_window_shrink",
srcs = ["tcp_window_shrink_test.go"],
deps = [
@@ -111,10 +158,8 @@ packetimpact_go_test(
)
packetimpact_go_test(
- name = "tcp_should_piggyback",
- srcs = ["tcp_should_piggyback_test.go"],
- # TODO(b/153680566): Fix netstack then remove the line below.
- expect_netstack_failure = True,
+ name = "tcp_send_window_sizes_piggyback",
+ srcs = ["tcp_send_window_sizes_piggyback_test.go"],
deps = [
"//pkg/tcpip/header",
"//test/packetimpact/testbench",
@@ -157,6 +202,66 @@ packetimpact_go_test(
)
packetimpact_go_test(
+ name = "tcp_queue_receive_in_syn_sent",
+ srcs = ["tcp_queue_receive_in_syn_sent_test.go"],
+ deps = [
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+packetimpact_go_test(
+ name = "tcp_synsent_reset",
+ srcs = ["tcp_synsent_reset_test.go"],
+ deps = [
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+packetimpact_go_test(
+ name = "tcp_synrcvd_reset",
+ srcs = ["tcp_synrcvd_reset_test.go"],
+ deps = [
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+packetimpact_go_test(
+ name = "tcp_network_unreachable",
+ srcs = ["tcp_network_unreachable_test.go"],
+ deps = [
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+packetimpact_go_test(
+ name = "tcp_cork_mss",
+ srcs = ["tcp_cork_mss_test.go"],
+ deps = [
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+packetimpact_go_test(
+ name = "tcp_handshake_window_size",
+ srcs = ["tcp_handshake_window_size_test.go"],
+ deps = [
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+packetimpact_go_test(
name = "icmpv6_param_problem",
srcs = ["icmpv6_param_problem_test.go"],
# TODO(b/153485026): Fix netstack then remove the line below.
@@ -170,15 +275,38 @@ packetimpact_go_test(
)
packetimpact_go_test(
- name = "udp_send_recv_dgram",
- srcs = ["udp_send_recv_dgram_test.go"],
+ 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",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+packetimpact_go_test(
+ name = "ipv6_fragment_reassembly",
+ srcs = ["ipv6_fragment_reassembly_test.go"],
+ # TODO(b/160919104): Fix netstack then remove the line below.
+ expect_netstack_failure = True,
deps = [
+ "//pkg/tcpip",
+ "//pkg/tcpip/buffer",
+ "//pkg/tcpip/header",
"//test/packetimpact/testbench",
"@org_golang_x_sys//unix:go_default_library",
],
)
-sh_binary(
- name = "test_runner",
- srcs = ["test_runner.sh"],
+packetimpact_go_test(
+ name = "udp_send_recv_dgram",
+ srcs = ["udp_send_recv_dgram_test.go"],
+ deps = [
+ "//test/packetimpact/testbench",
+ "@com_github_google_go_cmp//cmp:go_default_library",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
)
diff --git a/test/packetimpact/tests/fin_wait2_timeout_test.go b/test/packetimpact/tests/fin_wait2_timeout_test.go
index c26ab78d9..a61054c2c 100644
--- a/test/packetimpact/tests/fin_wait2_timeout_test.go
+++ b/test/packetimpact/tests/fin_wait2_timeout_test.go
@@ -21,11 +21,11 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/header"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
)
func init() {
- tb.RegisterFlags(flag.CommandLine)
+ testbench.RegisterFlags(flag.CommandLine)
}
func TestFinWait2Timeout(t *testing.T) {
@@ -37,36 +37,36 @@ func TestFinWait2Timeout(t *testing.T) {
{"WithoutLinger2", false},
} {
t.Run(tt.description, func(t *testing.T) {
- dut := tb.NewDUT(t)
+ dut := testbench.NewDUT(t)
defer dut.TearDown()
- listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(listenFd)
- conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
- defer conn.Close()
- conn.Handshake()
+ listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(t, listenFd)
+ conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ defer conn.Close(t)
+ conn.Connect(t)
- acceptFd, _ := dut.Accept(listenFd)
+ acceptFd, _ := dut.Accept(t, listenFd)
if tt.linger2 {
tv := unix.Timeval{Sec: 1, Usec: 0}
- dut.SetSockOptTimeval(acceptFd, unix.SOL_TCP, unix.TCP_LINGER2, &tv)
+ dut.SetSockOptTimeval(t, acceptFd, unix.SOL_TCP, unix.TCP_LINGER2, &tv)
}
- dut.Close(acceptFd)
+ dut.Close(t, acceptFd)
- if _, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil {
+ if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil {
t.Fatalf("expected a FIN-ACK within 1 second but got none: %s", err)
}
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)})
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)})
time.Sleep(5 * time.Second)
- conn.Drain()
+ conn.Drain(t)
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)})
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)})
if tt.linger2 {
- if _, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)}, time.Second); err != nil {
+ if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)}, time.Second); err != nil {
t.Fatalf("expected a RST packet within a second but got none: %s", err)
}
} else {
- if got, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)}, 10*time.Second); got != nil || err == nil {
+ if got, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)}, 10*time.Second); got != nil || err == nil {
t.Fatalf("expected no RST packets within ten seconds but got one: %s", got)
}
}
diff --git a/test/packetimpact/tests/icmpv6_param_problem_test.go b/test/packetimpact/tests/icmpv6_param_problem_test.go
index bb1fc26fc..2d59d552d 100644
--- a/test/packetimpact/tests/icmpv6_param_problem_test.go
+++ b/test/packetimpact/tests/icmpv6_param_problem_test.go
@@ -21,32 +21,32 @@ import (
"time"
"gvisor.dev/gvisor/pkg/tcpip/header"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
)
func init() {
- tb.RegisterFlags(flag.CommandLine)
+ testbench.RegisterFlags(flag.CommandLine)
}
// TestICMPv6ParamProblemTest sends a packet with a bad next header. The DUT
// should respond with an ICMPv6 Parameter Problem message.
func TestICMPv6ParamProblemTest(t *testing.T) {
- dut := tb.NewDUT(t)
+ dut := testbench.NewDUT(t)
defer dut.TearDown()
- conn := tb.NewIPv6Conn(t, tb.IPv6{}, tb.IPv6{})
- defer conn.Close()
- ipv6 := tb.IPv6{
+ conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
+ defer conn.Close(t)
+ ipv6 := testbench.IPv6{
// 254 is reserved and used for experimentation and testing. This should
// cause an error.
- NextHeader: tb.Uint8(254),
+ NextHeader: testbench.Uint8(254),
}
- icmpv6 := tb.ICMPv6{
- Type: tb.ICMPv6Type(header.ICMPv6EchoRequest),
- NDPPayload: []byte("hello world"),
+ icmpv6 := testbench.ICMPv6{
+ Type: testbench.ICMPv6Type(header.ICMPv6EchoRequest),
+ Payload: []byte("hello world"),
}
- toSend := conn.CreateFrame(ipv6, &icmpv6)
- conn.SendFrame(toSend)
+ toSend := (*testbench.Connection)(&conn).CreateFrame(t, testbench.Layers{&ipv6}, &icmpv6)
+ (*testbench.Connection)(&conn).SendFrame(t, toSend)
// Build the expected ICMPv6 payload, which includes an index to the
// problematic byte and also the problematic packet as described in
@@ -61,18 +61,18 @@ func TestICMPv6ParamProblemTest(t *testing.T) {
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, header.IPv6NextHeaderOffset)
expectedPayload = append(b, expectedPayload...)
- expectedICMPv6 := tb.ICMPv6{
- Type: tb.ICMPv6Type(header.ICMPv6ParamProblem),
- NDPPayload: expectedPayload,
+ expectedICMPv6 := testbench.ICMPv6{
+ Type: testbench.ICMPv6Type(header.ICMPv6ParamProblem),
+ Payload: expectedPayload,
}
- paramProblem := tb.Layers{
- &tb.Ether{},
- &tb.IPv6{},
+ paramProblem := testbench.Layers{
+ &testbench.Ether{},
+ &testbench.IPv6{},
&expectedICMPv6,
}
timeout := time.Second
- if _, err := conn.ExpectFrame(paramProblem, timeout); err != nil {
+ if _, err := conn.ExpectFrame(t, paramProblem, timeout); err != nil {
t.Errorf("expected %s within %s but got none: %s", paramProblem, timeout, err)
}
}
diff --git a/test/packetimpact/tests/ipv4_id_uniqueness_test.go b/test/packetimpact/tests/ipv4_id_uniqueness_test.go
new file mode 100644
index 000000000..cf881418c
--- /dev/null
+++ b/test/packetimpact/tests/ipv4_id_uniqueness_test.go
@@ -0,0 +1,122 @@
+// Copyright 2020 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 ipv4_id_uniqueness_test
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "testing"
+ "time"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/abi/linux"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+func init() {
+ testbench.RegisterFlags(flag.CommandLine)
+}
+
+func recvTCPSegment(t *testing.T, conn *testbench.TCPIPv4, expect *testbench.TCP, expectPayload *testbench.Payload) (uint16, error) {
+ layers, err := conn.ExpectData(t, expect, expectPayload, time.Second)
+ if err != nil {
+ return 0, fmt.Errorf("failed to receive TCP segment: %s", err)
+ }
+ if len(layers) < 2 {
+ return 0, fmt.Errorf("got packet with layers: %v, expected to have at least 2 layers (link and network)", layers)
+ }
+ ipv4, ok := layers[1].(*testbench.IPv4)
+ if !ok {
+ return 0, fmt.Errorf("got network layer: %T, expected: *IPv4", layers[1])
+ }
+ if *ipv4.Flags&header.IPv4FlagDontFragment != 0 {
+ return 0, fmt.Errorf("got IPv4 DF=1, expected DF=0")
+ }
+ return *ipv4.ID, nil
+}
+
+// RFC 6864 section 4.2 states: "The IPv4 ID of non-atomic datagrams MUST NOT
+// be reused when sending a copy of an earlier non-atomic datagram."
+//
+// This test creates a TCP connection, uses the IP_MTU_DISCOVER socket option
+// to force the DF bit to be 0, and checks that a retransmitted segment has a
+// different IPv4 Identification value than the original segment.
+func TestIPv4RetransmitIdentificationUniqueness(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ payload []byte
+ }{
+ {"SmallPayload", []byte("sample data")},
+ // 512 bytes is chosen because sending more than this in a single segment
+ // causes the retransmission to send less than the original amount.
+ {"512BytePayload", testbench.GenerateRandomPayload(t, 512)},
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+
+ listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(t, listenFD)
+
+ conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ defer conn.Close(t)
+
+ conn.Connect(t)
+ remoteFD, _ := dut.Accept(t, listenFD)
+ defer dut.Close(t, remoteFD)
+
+ dut.SetSockOptInt(t, remoteFD, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
+
+ // TODO(b/129291778) The following socket option clears the DF bit on
+ // IP packets sent over the socket, and is currently not supported by
+ // gVisor. gVisor by default sends packets with DF=0 anyway, so the
+ // socket option being not supported does not affect the operation of
+ // this test. Once the socket option is supported, the following call
+ // can be changed to simply assert success.
+ ret, errno := dut.SetSockOptIntWithErrno(context.Background(), t, remoteFD, unix.IPPROTO_IP, linux.IP_MTU_DISCOVER, linux.IP_PMTUDISC_DONT)
+ if ret == -1 && errno != unix.ENOTSUP {
+ t.Fatalf("failed to set IP_MTU_DISCOVER socket option to IP_PMTUDISC_DONT: %s", errno)
+ }
+
+ samplePayload := &testbench.Payload{Bytes: tc.payload}
+
+ dut.Send(t, remoteFD, tc.payload, 0)
+ if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
+ t.Fatalf("failed to receive TCP segment sent for RTT calculation: %s", err)
+ }
+ // Let the DUT estimate RTO with RTT from the DATA-ACK.
+ // TODO(gvisor.dev/issue/2685) Estimate RTO during handshake, after which
+ // we can skip sending this ACK.
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)})
+
+ dut.Send(t, remoteFD, tc.payload, 0)
+ expectTCP := &testbench.TCP{SeqNum: testbench.Uint32(uint32(*conn.RemoteSeqNum(t)))}
+ originalID, err := recvTCPSegment(t, &conn, expectTCP, samplePayload)
+ if err != nil {
+ t.Fatalf("failed to receive TCP segment: %s", err)
+ }
+
+ retransmitID, err := recvTCPSegment(t, &conn, expectTCP, samplePayload)
+ if err != nil {
+ t.Fatalf("failed to receive retransmitted TCP segment: %s", err)
+ }
+ if originalID == retransmitID {
+ t.Fatalf("unexpectedly got retransmitted TCP segment with same IPv4 ID field=%d", originalID)
+ }
+ })
+ }
+}
diff --git a/test/packetimpact/tests/ipv6_fragment_reassembly_test.go b/test/packetimpact/tests/ipv6_fragment_reassembly_test.go
new file mode 100644
index 000000000..b5f94ad4b
--- /dev/null
+++ b/test/packetimpact/tests/ipv6_fragment_reassembly_test.go
@@ -0,0 +1,168 @@
+// Copyright 2020 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 ipv6_fragment_reassembly_test
+
+import (
+ "bytes"
+ "encoding/binary"
+ "encoding/hex"
+ "flag"
+ "net"
+ "testing"
+ "time"
+
+ "gvisor.dev/gvisor/pkg/tcpip"
+ "gvisor.dev/gvisor/pkg/tcpip/buffer"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+const (
+ // The payload length for the first fragment we send. This number
+ // is a multiple of 8 near 750 (half of 1500).
+ firstPayloadLength = 752
+ // The ID field for our outgoing fragments.
+ fragmentID = 1
+ // A node must be able to accept a fragmented packet that,
+ // after reassembly, is as large as 1500 octets.
+ reassemblyCap = 1500
+)
+
+func init() {
+ testbench.RegisterFlags(flag.CommandLine)
+}
+
+func TestIPv6FragmentReassembly(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
+ defer conn.Close(t)
+
+ firstPayloadToSend := make([]byte, firstPayloadLength)
+ for i := range firstPayloadToSend {
+ firstPayloadToSend[i] = 'A'
+ }
+
+ secondPayloadLength := reassemblyCap - firstPayloadLength - header.ICMPv6EchoMinimumSize
+ secondPayloadToSend := firstPayloadToSend[:secondPayloadLength]
+
+ icmpv6EchoPayload := make([]byte, 4)
+ binary.BigEndian.PutUint16(icmpv6EchoPayload[0:], 0)
+ binary.BigEndian.PutUint16(icmpv6EchoPayload[2:], 0)
+ icmpv6EchoPayload = append(icmpv6EchoPayload, firstPayloadToSend...)
+
+ lIP := tcpip.Address(net.ParseIP(testbench.LocalIPv6).To16())
+ rIP := tcpip.Address(net.ParseIP(testbench.RemoteIPv6).To16())
+ icmpv6 := testbench.ICMPv6{
+ Type: testbench.ICMPv6Type(header.ICMPv6EchoRequest),
+ Code: testbench.Byte(0),
+ Payload: icmpv6EchoPayload,
+ }
+ icmpv6Bytes, err := icmpv6.ToBytes()
+ if err != nil {
+ t.Fatalf("failed to serialize ICMPv6: %s", err)
+ }
+ cksum := header.ICMPv6Checksum(
+ header.ICMPv6(icmpv6Bytes),
+ lIP,
+ rIP,
+ buffer.NewVectorisedView(len(secondPayloadToSend), []buffer.View{secondPayloadToSend}),
+ )
+
+ conn.Send(t, testbench.IPv6{},
+ &testbench.IPv6FragmentExtHdr{
+ FragmentOffset: testbench.Uint16(0),
+ MoreFragments: testbench.Bool(true),
+ Identification: testbench.Uint32(fragmentID),
+ },
+ &testbench.ICMPv6{
+ Type: testbench.ICMPv6Type(header.ICMPv6EchoRequest),
+ Code: testbench.Byte(0),
+ Payload: icmpv6EchoPayload,
+ Checksum: &cksum,
+ })
+
+ icmpv6ProtoNum := header.IPv6ExtensionHeaderIdentifier(header.ICMPv6ProtocolNumber)
+
+ conn.Send(t, testbench.IPv6{},
+ &testbench.IPv6FragmentExtHdr{
+ NextHeader: &icmpv6ProtoNum,
+ FragmentOffset: testbench.Uint16((firstPayloadLength + header.ICMPv6EchoMinimumSize) / 8),
+ MoreFragments: testbench.Bool(false),
+ Identification: testbench.Uint32(fragmentID),
+ },
+ &testbench.Payload{
+ Bytes: secondPayloadToSend,
+ })
+
+ gotEchoReplyFirstPart, err := conn.ExpectFrame(t, testbench.Layers{
+ &testbench.Ether{},
+ &testbench.IPv6{},
+ &testbench.IPv6FragmentExtHdr{
+ FragmentOffset: testbench.Uint16(0),
+ MoreFragments: testbench.Bool(true),
+ },
+ &testbench.ICMPv6{
+ Type: testbench.ICMPv6Type(header.ICMPv6EchoReply),
+ Code: testbench.Byte(0),
+ },
+ }, time.Second)
+ if err != nil {
+ t.Fatalf("expected a fragmented ICMPv6 Echo Reply, but got none: %s", err)
+ }
+
+ id := *gotEchoReplyFirstPart[2].(*testbench.IPv6FragmentExtHdr).Identification
+ gotFirstPayload, err := gotEchoReplyFirstPart[len(gotEchoReplyFirstPart)-1].ToBytes()
+ if err != nil {
+ t.Fatalf("failed to serialize ICMPv6: %s", err)
+ }
+ icmpPayload := gotFirstPayload[header.ICMPv6EchoMinimumSize:]
+ receivedLen := len(icmpPayload)
+ wantSecondPayloadLen := reassemblyCap - header.ICMPv6EchoMinimumSize - receivedLen
+ wantFirstPayload := make([]byte, receivedLen)
+ for i := range wantFirstPayload {
+ wantFirstPayload[i] = 'A'
+ }
+ wantSecondPayload := wantFirstPayload[:wantSecondPayloadLen]
+ if !bytes.Equal(icmpPayload, wantFirstPayload) {
+ t.Fatalf("received unexpected payload, got: %s, want: %s",
+ hex.Dump(icmpPayload),
+ hex.Dump(wantFirstPayload))
+ }
+
+ gotEchoReplySecondPart, err := conn.ExpectFrame(t, testbench.Layers{
+ &testbench.Ether{},
+ &testbench.IPv6{},
+ &testbench.IPv6FragmentExtHdr{
+ NextHeader: &icmpv6ProtoNum,
+ FragmentOffset: testbench.Uint16(uint16((receivedLen + header.ICMPv6EchoMinimumSize) / 8)),
+ MoreFragments: testbench.Bool(false),
+ Identification: &id,
+ },
+ &testbench.ICMPv6{},
+ }, time.Second)
+ if err != nil {
+ t.Fatalf("expected the rest of ICMPv6 Echo Reply, but got none: %s", err)
+ }
+ secondPayload, err := gotEchoReplySecondPart[len(gotEchoReplySecondPart)-1].ToBytes()
+ if err != nil {
+ t.Fatalf("failed to serialize ICMPv6 Echo Reply: %s", err)
+ }
+ if !bytes.Equal(secondPayload, wantSecondPayload) {
+ t.Fatalf("received unexpected payload, got: %s, want: %s",
+ hex.Dump(secondPayload),
+ hex.Dump(wantSecondPayload))
+ }
+}
diff --git a/test/packetimpact/tests/ipv6_unknown_options_action_test.go b/test/packetimpact/tests/ipv6_unknown_options_action_test.go
new file mode 100644
index 000000000..d7d63cbd2
--- /dev/null
+++ b/test/packetimpact/tests/ipv6_unknown_options_action_test.go
@@ -0,0 +1,187 @@
+// Copyright 2020 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 ipv6_unknown_options_action_test
+
+import (
+ "encoding/binary"
+ "flag"
+ "net"
+ "testing"
+ "time"
+
+ "gvisor.dev/gvisor/pkg/tcpip"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+func init() {
+ testbench.RegisterFlags(flag.CommandLine)
+}
+
+func mkHopByHopOptionsExtHdr(optType byte) testbench.Layer {
+ return &testbench.IPv6HopByHopOptionsExtHdr{
+ Options: []byte{optType, 0x04, 0x00, 0x00, 0x00, 0x00},
+ }
+}
+
+func mkDestinationOptionsExtHdr(optType byte) testbench.Layer {
+ return &testbench.IPv6DestinationOptionsExtHdr{
+ Options: []byte{optType, 0x04, 0x00, 0x00, 0x00, 0x00},
+ }
+}
+
+func optionTypeFromAction(action header.IPv6OptionUnknownAction) byte {
+ return byte(action << 6)
+}
+
+func TestIPv6UnknownOptionAction(t *testing.T) {
+ for _, tt := range []struct {
+ description string
+ mkExtHdr func(optType byte) testbench.Layer
+ action header.IPv6OptionUnknownAction
+ multicastDst bool
+ wantICMPv6 bool
+ }{
+ {
+ description: "0b00/hbh",
+ mkExtHdr: mkHopByHopOptionsExtHdr,
+ action: header.IPv6OptionUnknownActionSkip,
+ multicastDst: false,
+ wantICMPv6: false,
+ },
+ {
+ description: "0b01/hbh",
+ mkExtHdr: mkHopByHopOptionsExtHdr,
+ action: header.IPv6OptionUnknownActionDiscard,
+ multicastDst: false,
+ wantICMPv6: false,
+ },
+ {
+ description: "0b10/hbh/unicast",
+ mkExtHdr: mkHopByHopOptionsExtHdr,
+ action: header.IPv6OptionUnknownActionDiscardSendICMP,
+ multicastDst: false,
+ wantICMPv6: true,
+ },
+ {
+ description: "0b10/hbh/multicast",
+ mkExtHdr: mkHopByHopOptionsExtHdr,
+ action: header.IPv6OptionUnknownActionDiscardSendICMP,
+ multicastDst: true,
+ wantICMPv6: true,
+ },
+ {
+ description: "0b11/hbh/unicast",
+ mkExtHdr: mkHopByHopOptionsExtHdr,
+ action: header.IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest,
+ multicastDst: false,
+ wantICMPv6: true,
+ },
+ {
+ description: "0b11/hbh/multicast",
+ mkExtHdr: mkHopByHopOptionsExtHdr,
+ action: header.IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest,
+ multicastDst: true,
+ wantICMPv6: false,
+ },
+ {
+ description: "0b00/destination",
+ mkExtHdr: mkDestinationOptionsExtHdr,
+ action: header.IPv6OptionUnknownActionSkip,
+ multicastDst: false,
+ wantICMPv6: false,
+ },
+ {
+ description: "0b01/destination",
+ mkExtHdr: mkDestinationOptionsExtHdr,
+ action: header.IPv6OptionUnknownActionDiscard,
+ multicastDst: false,
+ wantICMPv6: false,
+ },
+ {
+ description: "0b10/destination/unicast",
+ mkExtHdr: mkDestinationOptionsExtHdr,
+ action: header.IPv6OptionUnknownActionDiscardSendICMP,
+ multicastDst: false,
+ wantICMPv6: true,
+ },
+ {
+ description: "0b10/destination/multicast",
+ mkExtHdr: mkDestinationOptionsExtHdr,
+ action: header.IPv6OptionUnknownActionDiscardSendICMP,
+ multicastDst: true,
+ wantICMPv6: true,
+ },
+ {
+ description: "0b11/destination/unicast",
+ mkExtHdr: mkDestinationOptionsExtHdr,
+ action: header.IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest,
+ multicastDst: false,
+ wantICMPv6: true,
+ },
+ {
+ description: "0b11/destination/multicast",
+ mkExtHdr: mkDestinationOptionsExtHdr,
+ action: header.IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest,
+ multicastDst: true,
+ wantICMPv6: false,
+ },
+ } {
+ t.Run(tt.description, func(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ ipv6Conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
+ conn := (*testbench.Connection)(&ipv6Conn)
+ defer ipv6Conn.Close(t)
+
+ outgoingOverride := testbench.Layers{}
+ if tt.multicastDst {
+ outgoingOverride = testbench.Layers{&testbench.IPv6{
+ DstAddr: testbench.Address(tcpip.Address(net.ParseIP("ff02::1"))),
+ }}
+ }
+
+ outgoing := conn.CreateFrame(t, outgoingOverride, tt.mkExtHdr(optionTypeFromAction(tt.action)))
+ conn.SendFrame(t, outgoing)
+ ipv6Sent := outgoing[1:]
+ invokingPacket, err := ipv6Sent.ToBytes()
+ if err != nil {
+ t.Fatalf("failed to serialize the outgoing packet: %s", err)
+ }
+ icmpv6Payload := make([]byte, 4)
+ // The pointer in the ICMPv6 parameter problem message should point to
+ // the option type of the unknown option. In our test case, it is the
+ // first option in the extension header whose option type is 2 bytes
+ // after the IPv6 header (after NextHeader and ExtHdrLen).
+ binary.BigEndian.PutUint32(icmpv6Payload, header.IPv6MinimumSize+2)
+ icmpv6Payload = append(icmpv6Payload, invokingPacket...)
+ gotICMPv6, err := ipv6Conn.ExpectFrame(t, testbench.Layers{
+ &testbench.Ether{},
+ &testbench.IPv6{},
+ &testbench.ICMPv6{
+ Type: testbench.ICMPv6Type(header.ICMPv6ParamProblem),
+ Code: testbench.Byte(2),
+ Payload: icmpv6Payload,
+ },
+ }, time.Second)
+ if tt.wantICMPv6 && err != nil {
+ t.Fatalf("expected ICMPv6 Parameter Problem but got none: %s", err)
+ }
+ if !tt.wantICMPv6 && gotICMPv6 != nil {
+ t.Fatalf("expected no ICMPv6 Parameter Problem but got one: %s", gotICMPv6)
+ }
+ })
+ }
+}
diff --git a/test/packetimpact/tests/tcp_close_wait_ack_test.go b/test/packetimpact/tests/tcp_close_wait_ack_test.go
index 70a22a2db..e6a96f214 100644
--- a/test/packetimpact/tests/tcp_close_wait_ack_test.go
+++ b/test/packetimpact/tests/tcp_close_wait_ack_test.go
@@ -23,49 +23,49 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/seqnum"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
)
func init() {
- tb.RegisterFlags(flag.CommandLine)
+ testbench.RegisterFlags(flag.CommandLine)
}
func TestCloseWaitAck(t *testing.T) {
for _, tt := range []struct {
description string
- makeTestingTCP func(conn *tb.TCPIPv4, seqNumOffset seqnum.Size, windowSize seqnum.Size) tb.TCP
+ makeTestingTCP func(t *testing.T, conn *testbench.TCPIPv4, seqNumOffset, windowSize seqnum.Size) testbench.TCP
seqNumOffset seqnum.Size
expectAck bool
}{
- {"OTW", GenerateOTWSeqSegment, 0, false},
- {"OTW", GenerateOTWSeqSegment, 1, true},
- {"OTW", GenerateOTWSeqSegment, 2, true},
- {"ACK", GenerateUnaccACKSegment, 0, false},
- {"ACK", GenerateUnaccACKSegment, 1, true},
- {"ACK", GenerateUnaccACKSegment, 2, true},
+ {"OTW", generateOTWSeqSegment, 0, false},
+ {"OTW", generateOTWSeqSegment, 1, true},
+ {"OTW", generateOTWSeqSegment, 2, true},
+ {"ACK", generateUnaccACKSegment, 0, false},
+ {"ACK", generateUnaccACKSegment, 1, true},
+ {"ACK", generateUnaccACKSegment, 2, true},
} {
t.Run(fmt.Sprintf("%s%d", tt.description, tt.seqNumOffset), func(t *testing.T) {
- dut := tb.NewDUT(t)
+ dut := testbench.NewDUT(t)
defer dut.TearDown()
- listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(listenFd)
- conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
- defer conn.Close()
+ listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(t, listenFd)
+ conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ defer conn.Close(t)
- conn.Handshake()
- acceptFd, _ := dut.Accept(listenFd)
+ conn.Connect(t)
+ acceptFd, _ := dut.Accept(t, listenFd)
// Send a FIN to DUT to intiate the active close
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagFin)})
- gotTCP, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, time.Second)
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagFin)})
+ gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second)
if err != nil {
t.Fatalf("expected an ACK for our fin and DUT should enter CLOSE_WAIT: %s", err)
}
windowSize := seqnum.Size(*gotTCP.WindowSize)
// Send a segment with OTW Seq / unacc ACK and expect an ACK back
- conn.Send(tt.makeTestingTCP(&conn, tt.seqNumOffset, windowSize), &tb.Payload{Bytes: []byte("Sample Data")})
- gotAck, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, time.Second)
+ conn.Send(t, tt.makeTestingTCP(t, &conn, tt.seqNumOffset, windowSize), &testbench.Payload{Bytes: []byte("Sample Data")})
+ gotAck, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second)
if tt.expectAck && err != nil {
t.Fatalf("expected an ack but got none: %s", err)
}
@@ -74,35 +74,36 @@ func TestCloseWaitAck(t *testing.T) {
}
// Now let's verify DUT is indeed in CLOSE_WAIT
- dut.Close(acceptFd)
- if _, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagFin)}, time.Second); err != nil {
+ dut.Close(t, acceptFd)
+ if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagFin)}, time.Second); err != nil {
t.Fatalf("expected DUT to send a FIN: %s", err)
}
// Ack the FIN from DUT
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)})
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)})
// Send some extra data to DUT
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, &tb.Payload{Bytes: []byte("Sample Data")})
- if _, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)}, time.Second); err != nil {
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, &testbench.Payload{Bytes: []byte("Sample Data")})
+ if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)}, time.Second); err != nil {
t.Fatalf("expected DUT to send an RST: %s", err)
}
})
}
}
-// This generates an segment with seqnum = RCV.NXT + RCV.WND + seqNumOffset, the
-// generated segment is only acceptable when seqNumOffset is 0, otherwise an ACK
-// is expected from the receiver.
-func GenerateOTWSeqSegment(conn *tb.TCPIPv4, seqNumOffset seqnum.Size, windowSize seqnum.Size) tb.TCP {
- lastAcceptable := conn.LocalSeqNum().Add(windowSize)
+// generateOTWSeqSegment generates an segment with
+// seqnum = RCV.NXT + RCV.WND + seqNumOffset, the generated segment is only
+// acceptable when seqNumOffset is 0, otherwise an ACK is expected from the
+// receiver.
+func generateOTWSeqSegment(t *testing.T, conn *testbench.TCPIPv4, seqNumOffset seqnum.Size, windowSize seqnum.Size) testbench.TCP {
+ lastAcceptable := conn.LocalSeqNum(t).Add(windowSize)
otwSeq := uint32(lastAcceptable.Add(seqNumOffset))
- return tb.TCP{SeqNum: tb.Uint32(otwSeq), Flags: tb.Uint8(header.TCPFlagAck)}
+ return testbench.TCP{SeqNum: testbench.Uint32(otwSeq), Flags: testbench.Uint8(header.TCPFlagAck)}
}
-// This generates an segment with acknum = SND.NXT + seqNumOffset, the generated
-// segment is only acceptable when seqNumOffset is 0, otherwise an ACK is
-// expected from the receiver.
-func GenerateUnaccACKSegment(conn *tb.TCPIPv4, seqNumOffset seqnum.Size, windowSize seqnum.Size) tb.TCP {
- lastAcceptable := conn.RemoteSeqNum()
+// generateUnaccACKSegment generates an segment with
+// acknum = SND.NXT + seqNumOffset, the generated segment is only acceptable
+// when seqNumOffset is 0, otherwise an ACK is expected from the receiver.
+func generateUnaccACKSegment(t *testing.T, conn *testbench.TCPIPv4, seqNumOffset seqnum.Size, windowSize seqnum.Size) testbench.TCP {
+ lastAcceptable := conn.RemoteSeqNum(t)
unaccAck := uint32(lastAcceptable.Add(seqNumOffset))
- return tb.TCP{AckNum: tb.Uint32(unaccAck), Flags: tb.Uint8(header.TCPFlagAck)}
+ return testbench.TCP{AckNum: testbench.Uint32(unaccAck), Flags: testbench.Uint8(header.TCPFlagAck)}
}
diff --git a/test/packetimpact/tests/tcp_cork_mss_test.go b/test/packetimpact/tests/tcp_cork_mss_test.go
new file mode 100644
index 000000000..8feea4a82
--- /dev/null
+++ b/test/packetimpact/tests/tcp_cork_mss_test.go
@@ -0,0 +1,84 @@
+// Copyright 2020 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 tcp_cork_mss_test
+
+import (
+ "flag"
+ "testing"
+ "time"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+func init() {
+ testbench.RegisterFlags(flag.CommandLine)
+}
+
+// TestTCPCorkMSS tests for segment coalesce and split as per MSS.
+func TestTCPCorkMSS(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(t, listenFD)
+ conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ defer conn.Close(t)
+
+ const mss = uint32(header.TCPDefaultMSS)
+ options := make([]byte, header.TCPOptionMSSLength)
+ header.EncodeMSSOption(mss, options)
+ conn.ConnectWithOptions(t, options)
+
+ acceptFD, _ := dut.Accept(t, listenFD)
+ defer dut.Close(t, acceptFD)
+
+ dut.SetSockOptInt(t, acceptFD, unix.IPPROTO_TCP, unix.TCP_CORK, 1)
+
+ // Let the dut application send 2 small segments to be held up and coalesced
+ // until the application sends a larger segment to fill up to > MSS.
+ sampleData := []byte("Sample Data")
+ dut.Send(t, acceptFD, sampleData, 0)
+ dut.Send(t, acceptFD, sampleData, 0)
+
+ expectedData := sampleData
+ expectedData = append(expectedData, sampleData...)
+ largeData := make([]byte, mss+1)
+ expectedData = append(expectedData, largeData...)
+ dut.Send(t, acceptFD, largeData, 0)
+
+ // Expect the segments to be coalesced and sent and capped to MSS.
+ expectedPayload := testbench.Payload{Bytes: expectedData[:mss]}
+ if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, &expectedPayload, time.Second); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
+ }
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)})
+ // Expect the coalesced segment to be split and transmitted.
+ expectedPayload = testbench.Payload{Bytes: expectedData[mss:]}
+ if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, &expectedPayload, time.Second); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
+ }
+
+ // Check for segments to *not* be held up because of TCP_CORK when
+ // the current send window is less than MSS.
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), WindowSize: testbench.Uint16(uint16(2 * len(sampleData)))})
+ dut.Send(t, acceptFD, sampleData, 0)
+ dut.Send(t, acceptFD, sampleData, 0)
+ expectedPayload = testbench.Payload{Bytes: append(sampleData, sampleData...)}
+ if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, &expectedPayload, time.Second); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
+ }
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)})
+}
diff --git a/test/packetimpact/tests/tcp_handshake_window_size_test.go b/test/packetimpact/tests/tcp_handshake_window_size_test.go
new file mode 100644
index 000000000..22937d92f
--- /dev/null
+++ b/test/packetimpact/tests/tcp_handshake_window_size_test.go
@@ -0,0 +1,66 @@
+// Copyright 2020 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 tcp_handshake_window_size_test
+
+import (
+ "flag"
+ "testing"
+ "time"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+func init() {
+ testbench.RegisterFlags(flag.CommandLine)
+}
+
+// TestTCPHandshakeWindowSize tests if the stack is honoring the window size
+// communicated during handshake.
+func TestTCPHandshakeWindowSize(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(t, listenFD)
+ conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ defer conn.Close(t)
+
+ // Start handshake with zero window size.
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn), WindowSize: testbench.Uint16(uint16(0))})
+ if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil {
+ t.Fatalf("expected SYN-ACK: %s", err)
+ }
+ // Update the advertised window size to a non-zero value with the ACK that
+ // completes the handshake.
+ //
+ // Set the window size with MSB set and expect the dut to treat it as
+ // an unsigned value.
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), WindowSize: testbench.Uint16(uint16(1 << 15))})
+
+ acceptFd, _ := dut.Accept(t, listenFD)
+ defer dut.Close(t, acceptFd)
+
+ sampleData := []byte("Sample Data")
+ samplePayload := &testbench.Payload{Bytes: sampleData}
+
+ // Since we advertised a zero window followed by a non-zero window,
+ // expect the dut to honor the recently advertised non-zero window
+ // and actually send out the data instead of probing for zero window.
+ dut.Send(t, acceptFd, sampleData, 0)
+ if _, err := conn.ExpectNextData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload, time.Second); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
+ }
+}
diff --git a/test/packetimpact/tests/tcp_network_unreachable_test.go b/test/packetimpact/tests/tcp_network_unreachable_test.go
new file mode 100644
index 000000000..900352fa1
--- /dev/null
+++ b/test/packetimpact/tests/tcp_network_unreachable_test.go
@@ -0,0 +1,139 @@
+// Copyright 2020 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 tcp_synsent_reset_test
+
+import (
+ "context"
+ "flag"
+ "net"
+ "syscall"
+ "testing"
+ "time"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+func init() {
+ testbench.RegisterFlags(flag.CommandLine)
+}
+
+// TestTCPSynSentUnreachable verifies that TCP connections fail immediately when
+// an ICMP destination unreachable message is sent in response to the inital
+// SYN.
+func TestTCPSynSentUnreachable(t *testing.T) {
+ // Create the DUT and connection.
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, net.ParseIP(testbench.RemoteIPv4))
+ port := uint16(9001)
+ conn := testbench.NewTCPIPv4(t, testbench.TCP{SrcPort: &port, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &port})
+ defer conn.Close(t)
+
+ // Bring the DUT to SYN-SENT state with a non-blocking connect.
+ ctx, cancel := context.WithTimeout(context.Background(), testbench.RPCTimeout)
+ defer cancel()
+ sa := unix.SockaddrInet4{Port: int(port)}
+ copy(sa.Addr[:], net.IP(net.ParseIP(testbench.LocalIPv4)).To4())
+ if _, err := dut.ConnectWithErrno(ctx, t, clientFD, &sa); err != syscall.Errno(unix.EINPROGRESS) {
+ t.Errorf("expected connect to fail with EINPROGRESS, but got %v", err)
+ }
+
+ // Get the SYN.
+ tcpLayers, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}, nil, time.Second)
+ if err != nil {
+ t.Fatalf("expected SYN: %s", err)
+ }
+
+ // Send a host unreachable message.
+ rawConn := (*testbench.Connection)(&conn)
+ layers := rawConn.CreateFrame(t, nil)
+ layers = layers[:len(layers)-1]
+ const ipLayer = 1
+ const tcpLayer = ipLayer + 1
+ ip, ok := tcpLayers[ipLayer].(*testbench.IPv4)
+ if !ok {
+ t.Fatalf("expected %s to be IPv4", tcpLayers[ipLayer])
+ }
+ tcp, ok := tcpLayers[tcpLayer].(*testbench.TCP)
+ if !ok {
+ t.Fatalf("expected %s to be TCP", tcpLayers[tcpLayer])
+ }
+ var icmpv4 testbench.ICMPv4 = testbench.ICMPv4{Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable), Code: testbench.Uint8(header.ICMPv4HostUnreachable)}
+ layers = append(layers, &icmpv4, ip, tcp)
+ rawConn.SendFrameStateless(t, layers)
+
+ if _, err = dut.ConnectWithErrno(ctx, t, clientFD, &sa); err != syscall.Errno(unix.EHOSTUNREACH) {
+ t.Errorf("expected connect to fail with EHOSTUNREACH, but got %v", err)
+ }
+}
+
+// TestTCPSynSentUnreachable6 verifies that TCP connections fail immediately when
+// an ICMP destination unreachable message is sent in response to the inital
+// SYN.
+func TestTCPSynSentUnreachable6(t *testing.T) {
+ // Create the DUT and connection.
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, net.ParseIP(testbench.RemoteIPv6))
+ conn := testbench.NewTCPIPv6(t, testbench.TCP{DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort})
+ defer conn.Close(t)
+
+ // Bring the DUT to SYN-SENT state with a non-blocking connect.
+ ctx, cancel := context.WithTimeout(context.Background(), testbench.RPCTimeout)
+ defer cancel()
+ sa := unix.SockaddrInet6{
+ Port: int(conn.SrcPort()),
+ ZoneId: uint32(testbench.RemoteInterfaceID),
+ }
+ copy(sa.Addr[:], net.IP(net.ParseIP(testbench.LocalIPv6)).To16())
+ if _, err := dut.ConnectWithErrno(ctx, t, clientFD, &sa); err != syscall.Errno(unix.EINPROGRESS) {
+ t.Errorf("expected connect to fail with EINPROGRESS, but got %v", err)
+ }
+
+ // Get the SYN.
+ tcpLayers, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}, nil, time.Second)
+ if err != nil {
+ t.Fatalf("expected SYN: %s", err)
+ }
+
+ // Send a host unreachable message.
+ rawConn := (*testbench.Connection)(&conn)
+ layers := rawConn.CreateFrame(t, nil)
+ layers = layers[:len(layers)-1]
+ const ipLayer = 1
+ const tcpLayer = ipLayer + 1
+ ip, ok := tcpLayers[ipLayer].(*testbench.IPv6)
+ if !ok {
+ t.Fatalf("expected %s to be IPv6", tcpLayers[ipLayer])
+ }
+ tcp, ok := tcpLayers[tcpLayer].(*testbench.TCP)
+ if !ok {
+ t.Fatalf("expected %s to be TCP", tcpLayers[tcpLayer])
+ }
+ var icmpv6 testbench.ICMPv6 = testbench.ICMPv6{
+ Type: testbench.ICMPv6Type(header.ICMPv6DstUnreachable),
+ Code: testbench.Uint8(header.ICMPv6NetworkUnreachable),
+ // Per RFC 4443 3.1, the payload contains 4 zeroed bytes.
+ Payload: []byte{0, 0, 0, 0},
+ }
+ layers = append(layers, &icmpv6, ip, tcp)
+ rawConn.SendFrameStateless(t, layers)
+
+ if _, err = dut.ConnectWithErrno(ctx, t, clientFD, &sa); err != syscall.Errno(unix.ENETUNREACH) {
+ t.Errorf("expected connect to fail with ENETUNREACH, but got %v", err)
+ }
+}
diff --git a/test/packetimpact/tests/tcp_noaccept_close_rst_test.go b/test/packetimpact/tests/tcp_noaccept_close_rst_test.go
index 2c1ec27d3..82b7a85ff 100644
--- a/test/packetimpact/tests/tcp_noaccept_close_rst_test.go
+++ b/test/packetimpact/tests/tcp_noaccept_close_rst_test.go
@@ -21,22 +21,22 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/header"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
)
func init() {
- tb.RegisterFlags(flag.CommandLine)
+ testbench.RegisterFlags(flag.CommandLine)
}
func TestTcpNoAcceptCloseReset(t *testing.T) {
- dut := tb.NewDUT(t)
+ dut := testbench.NewDUT(t)
defer dut.TearDown()
- listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
- conn.Handshake()
- defer conn.Close()
- dut.Close(listenFd)
- if _, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagRst | header.TCPFlagAck)}, 1*time.Second); err != nil {
+ listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ conn.Connect(t)
+ defer conn.Close(t)
+ dut.Close(t, listenFd)
+ if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst | header.TCPFlagAck)}, 1*time.Second); err != nil {
t.Fatalf("expected a RST-ACK packet but got none: %s", err)
}
}
diff --git a/test/packetimpact/tests/tcp_outside_the_window_test.go b/test/packetimpact/tests/tcp_outside_the_window_test.go
index 351df193e..08f759f7c 100644
--- a/test/packetimpact/tests/tcp_outside_the_window_test.go
+++ b/test/packetimpact/tests/tcp_outside_the_window_test.go
@@ -23,11 +23,11 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/seqnum"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
)
func init() {
- tb.RegisterFlags(flag.CommandLine)
+ testbench.RegisterFlags(flag.CommandLine)
}
// TestTCPOutsideTheWindows tests the behavior of the DUT when packets arrive
@@ -38,7 +38,7 @@ func TestTCPOutsideTheWindow(t *testing.T) {
for _, tt := range []struct {
description string
tcpFlags uint8
- payload []tb.Layer
+ payload []testbench.Layer
seqNumOffset seqnum.Size
expectACK bool
}{
@@ -46,42 +46,42 @@ func TestTCPOutsideTheWindow(t *testing.T) {
{"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil, 0, true},
{"ACK", header.TCPFlagAck, nil, 0, false},
{"FIN", header.TCPFlagFin, nil, 0, false},
- {"Data", header.TCPFlagAck, []tb.Layer{&tb.Payload{Bytes: []byte("abc123")}}, 0, true},
+ {"Data", header.TCPFlagAck, []testbench.Layer{&testbench.Payload{Bytes: []byte("abc123")}}, 0, true},
{"SYN", header.TCPFlagSyn, nil, 1, true},
{"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil, 1, true},
{"ACK", header.TCPFlagAck, nil, 1, true},
{"FIN", header.TCPFlagFin, nil, 1, false},
- {"Data", header.TCPFlagAck, []tb.Layer{&tb.Payload{Bytes: []byte("abc123")}}, 1, true},
+ {"Data", header.TCPFlagAck, []testbench.Layer{&testbench.Payload{Bytes: []byte("abc123")}}, 1, true},
{"SYN", header.TCPFlagSyn, nil, 2, true},
{"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil, 2, true},
{"ACK", header.TCPFlagAck, nil, 2, true},
{"FIN", header.TCPFlagFin, nil, 2, false},
- {"Data", header.TCPFlagAck, []tb.Layer{&tb.Payload{Bytes: []byte("abc123")}}, 2, true},
+ {"Data", header.TCPFlagAck, []testbench.Layer{&testbench.Payload{Bytes: []byte("abc123")}}, 2, true},
} {
t.Run(fmt.Sprintf("%s%d", tt.description, tt.seqNumOffset), func(t *testing.T) {
- dut := tb.NewDUT(t)
+ dut := testbench.NewDUT(t)
defer dut.TearDown()
- listenFD, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(listenFD)
- conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
- defer conn.Close()
- conn.Handshake()
- acceptFD, _ := dut.Accept(listenFD)
- defer dut.Close(acceptFD)
+ listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(t, listenFD)
+ conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ defer conn.Close(t)
+ conn.Connect(t)
+ acceptFD, _ := dut.Accept(t, listenFD)
+ defer dut.Close(t, acceptFD)
- windowSize := seqnum.Size(*conn.SynAck().WindowSize) + tt.seqNumOffset
- conn.Drain()
+ windowSize := seqnum.Size(*conn.SynAck(t).WindowSize) + tt.seqNumOffset
+ conn.Drain(t)
// Ignore whatever incrementing that this out-of-order packet might cause
// to the AckNum.
- localSeqNum := tb.Uint32(uint32(*conn.LocalSeqNum()))
- conn.Send(tb.TCP{
- Flags: tb.Uint8(tt.tcpFlags),
- SeqNum: tb.Uint32(uint32(conn.LocalSeqNum().Add(windowSize))),
+ localSeqNum := testbench.Uint32(uint32(*conn.LocalSeqNum(t)))
+ conn.Send(t, testbench.TCP{
+ Flags: testbench.Uint8(tt.tcpFlags),
+ SeqNum: testbench.Uint32(uint32(conn.LocalSeqNum(t).Add(windowSize))),
}, tt.payload...)
timeout := 3 * time.Second
- gotACK, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), AckNum: localSeqNum}, timeout)
+ gotACK, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: localSeqNum}, timeout)
if tt.expectACK && err != nil {
t.Fatalf("expected an ACK packet within %s but got none: %s", timeout, err)
}
diff --git a/test/packetimpact/tests/tcp_paws_mechanism_test.go b/test/packetimpact/tests/tcp_paws_mechanism_test.go
index 0a668adcf..37f3b56dd 100644
--- a/test/packetimpact/tests/tcp_paws_mechanism_test.go
+++ b/test/packetimpact/tests/tcp_paws_mechanism_test.go
@@ -22,25 +22,25 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/header"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
)
func init() {
- tb.RegisterFlags(flag.CommandLine)
+ testbench.RegisterFlags(flag.CommandLine)
}
func TestPAWSMechanism(t *testing.T) {
- dut := tb.NewDUT(t)
+ dut := testbench.NewDUT(t)
defer dut.TearDown()
- listenFD, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(listenFD)
- conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
- defer conn.Close()
+ listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(t, listenFD)
+ conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ defer conn.Close(t)
options := make([]byte, header.TCPOptionTSLength)
header.EncodeTSOption(currentTS(), 0, options)
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagSyn), Options: options})
- synAck, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second)
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn), Options: options})
+ synAck, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second)
if err != nil {
t.Fatalf("didn't get synack during handshake: %s", err)
}
@@ -50,9 +50,9 @@ func TestPAWSMechanism(t *testing.T) {
}
tsecr := parsedSynOpts.TSVal
header.EncodeTSOption(currentTS(), tsecr, options)
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), Options: options})
- acceptFD, _ := dut.Accept(listenFD)
- defer dut.Close(acceptFD)
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), Options: options})
+ acceptFD, _ := dut.Accept(t, listenFD)
+ defer dut.Close(t, acceptFD)
sampleData := []byte("Sample Data")
sentTSVal := currentTS()
@@ -61,9 +61,9 @@ func TestPAWSMechanism(t *testing.T) {
// every time we send one, it should not cause any flakiness because timestamps
// only need to be non-decreasing.
time.Sleep(3 * time.Millisecond)
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), Options: options}, &tb.Payload{Bytes: sampleData})
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), Options: options}, &testbench.Payload{Bytes: sampleData})
- gotTCP, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, time.Second)
+ gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second)
if err != nil {
t.Fatalf("expected an ACK but got none: %s", err)
}
@@ -86,9 +86,9 @@ func TestPAWSMechanism(t *testing.T) {
// 3ms here is chosen arbitrarily and this time.Sleep() should not cause flakiness
// due to the exact same reasoning discussed above.
time.Sleep(3 * time.Millisecond)
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), Options: options}, &tb.Payload{Bytes: sampleData})
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), Options: options}, &testbench.Payload{Bytes: sampleData})
- gotTCP, err = conn.Expect(tb.TCP{AckNum: lastAckNum, Flags: tb.Uint8(header.TCPFlagAck)}, time.Second)
+ gotTCP, err = conn.Expect(t, testbench.TCP{AckNum: lastAckNum, Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second)
if err != nil {
t.Fatalf("expected segment with AckNum %d but got none: %s", lastAckNum, err)
}
diff --git a/test/packetimpact/tests/tcp_queue_receive_in_syn_sent_test.go b/test/packetimpact/tests/tcp_queue_receive_in_syn_sent_test.go
new file mode 100644
index 000000000..d9f3ea0f2
--- /dev/null
+++ b/test/packetimpact/tests/tcp_queue_receive_in_syn_sent_test.go
@@ -0,0 +1,132 @@
+// Copyright 2020 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 tcp_queue_receive_in_syn_sent_test
+
+import (
+ "bytes"
+ "context"
+ "encoding/hex"
+ "errors"
+ "flag"
+ "net"
+ "sync"
+ "syscall"
+ "testing"
+ "time"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+func init() {
+ testbench.RegisterFlags(flag.CommandLine)
+}
+
+// TestQueueReceiveInSynSent tests receive behavior when the TCP state
+// is SYN-SENT.
+// It tests for 2 variants where the receive is blocked and:
+// (1) we complete handshake and send sample data.
+// (2) we send a TCP RST.
+func TestQueueReceiveInSynSent(t *testing.T) {
+ for _, tt := range []struct {
+ description string
+ reset bool
+ }{
+ {description: "Send DATA", reset: false},
+ {description: "Send RST", reset: true},
+ } {
+ t.Run(tt.description, func(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+
+ socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, net.ParseIP(testbench.RemoteIPv4))
+ conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ defer conn.Close(t)
+
+ sampleData := []byte("Sample Data")
+
+ dut.SetNonBlocking(t, socket, true)
+ if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, syscall.EINPROGRESS) {
+ t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err)
+ }
+ if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}, time.Second); err != nil {
+ t.Fatalf("expected a SYN from DUT, but got none: %s", err)
+ }
+
+ if _, _, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0); err != syscall.Errno(unix.EWOULDBLOCK) {
+ t.Fatalf("expected error %s, got %s", syscall.Errno(unix.EWOULDBLOCK), err)
+ }
+
+ // Test blocking read.
+ dut.SetNonBlocking(t, socket, false)
+
+ var wg sync.WaitGroup
+ defer wg.Wait()
+ wg.Add(1)
+ var block sync.WaitGroup
+ block.Add(1)
+ go func() {
+ defer wg.Done()
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
+ defer cancel()
+
+ block.Done()
+ // Issue RECEIVE call in SYN-SENT, this should be queued for
+ // process until the connection is established.
+ n, buff, err := dut.RecvWithErrno(ctx, t, socket, int32(len(sampleData)), 0)
+ if tt.reset {
+ if err != syscall.Errno(unix.ECONNREFUSED) {
+ t.Errorf("expected error %s, got %s", syscall.Errno(unix.ECONNREFUSED), err)
+ }
+ if n != -1 {
+ t.Errorf("expected return value %d, got %d", -1, n)
+ }
+ return
+ }
+ if n == -1 {
+ t.Errorf("failed to recv on DUT: %s", err)
+ }
+ if got := buff[:n]; !bytes.Equal(got, sampleData) {
+ t.Errorf("received data doesn't match, got:\n%s, want:\n%s", hex.Dump(got), hex.Dump(sampleData))
+ }
+ }()
+
+ // Wait for the goroutine to be scheduled and before it
+ // blocks on endpoint receive.
+ block.Wait()
+ // The following sleep is used to prevent the connection
+ // from being established before we are blocked on Recv.
+ time.Sleep(100 * time.Millisecond)
+
+ if tt.reset {
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst | header.TCPFlagAck)})
+ return
+ }
+
+ // Bring the connection to Established.
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn | header.TCPFlagAck)})
+ if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second); err != nil {
+ t.Fatalf("expected an ACK from DUT, but got none: %s", err)
+ }
+
+ // Send sample payload and expect an ACK.
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData})
+ if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second); err != nil {
+ t.Fatalf("expected an ACK from DUT, but got none: %s", err)
+ }
+ })
+ }
+}
diff --git a/test/packetimpact/tests/tcp_reordering_test.go b/test/packetimpact/tests/tcp_reordering_test.go
new file mode 100644
index 000000000..8742819ca
--- /dev/null
+++ b/test/packetimpact/tests/tcp_reordering_test.go
@@ -0,0 +1,174 @@
+// Copyright 2020 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 reordering_test
+
+import (
+ "flag"
+ "testing"
+ "time"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ "gvisor.dev/gvisor/pkg/tcpip/seqnum"
+ tb "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+func init() {
+ tb.RegisterFlags(flag.CommandLine)
+}
+
+func TestReorderingWindow(t *testing.T) {
+ dut := tb.NewDUT(t)
+ defer dut.TearDown()
+ listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(t, listenFd)
+ conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
+ defer conn.Close(t)
+
+ // Enable SACK.
+ opts := make([]byte, 40)
+ optsOff := 0
+ optsOff += header.EncodeNOP(opts[optsOff:])
+ optsOff += header.EncodeNOP(opts[optsOff:])
+ optsOff += header.EncodeSACKPermittedOption(opts[optsOff:])
+
+ // Ethernet guarantees that the MTU is at least 1500 bytes.
+ const minMTU = 1500
+ const mss = minMTU - header.IPv4MinimumSize - header.TCPMinimumSize
+ optsOff += header.EncodeMSSOption(mss, opts[optsOff:])
+
+ conn.ConnectWithOptions(t, opts[:optsOff])
+
+ acceptFd, _ := dut.Accept(t, listenFd)
+ defer dut.Close(t, acceptFd)
+
+ if tb.DUTType == "linux" {
+ // Linux has changed its handling of reordering, force the old behavior.
+ dut.SetSockOpt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_CONGESTION, []byte("reno"))
+ }
+
+ pls := dut.GetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_MAXSEG)
+ if tb.DUTType == "netstack" {
+ // netstack does not impliment TCP_MAXSEG correctly. Fake it
+ // here. Netstack uses the max SACK size which is 32. The MSS
+ // option is 8 bytes, making the total 36 bytes.
+ pls = mss - 36
+ }
+
+ payload := make([]byte, pls)
+
+ seqNum1 := *conn.RemoteSeqNum(t)
+ const numPkts = 10
+ // Send some packets, checking that we receive each.
+ for i, sn := 0, seqNum1; i < numPkts; i++ {
+ dut.Send(t, acceptFd, payload, 0)
+
+ gotOne, err := conn.Expect(t, tb.TCP{SeqNum: tb.Uint32(uint32(sn))}, time.Second)
+ sn.UpdateForward(seqnum.Size(len(payload)))
+ if err != nil {
+ t.Errorf("Expect #%d: %s", i+1, err)
+ continue
+ }
+ if gotOne == nil {
+ t.Errorf("#%d: expected a packet within a second but got none", i+1)
+ }
+ }
+
+ seqNum2 := *conn.RemoteSeqNum(t)
+
+ // SACK packets #2-4.
+ sackBlock := make([]byte, 40)
+ sbOff := 0
+ sbOff += header.EncodeNOP(sackBlock[sbOff:])
+ sbOff += header.EncodeNOP(sackBlock[sbOff:])
+ sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{
+ seqNum1.Add(seqnum.Size(len(payload))),
+ seqNum1.Add(seqnum.Size(4 * len(payload))),
+ }}, sackBlock[sbOff:])
+ conn.Send(t, tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), AckNum: tb.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]})
+
+ // ACK first packet.
+ conn.Send(t, tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), AckNum: tb.Uint32(uint32(seqNum1) + uint32(len(payload)))})
+
+ // Check for retransmit.
+ gotOne, err := conn.Expect(t, tb.TCP{SeqNum: tb.Uint32(uint32(seqNum1))}, time.Second)
+ if err != nil {
+ t.Error("Expect for retransmit:", err)
+ }
+ if gotOne == nil {
+ t.Error("expected a retransmitted packet within a second but got none")
+ }
+
+ // ACK all send packets with a DSACK block for packet #1. This tells
+ // the other end that we got both the original and retransmit for
+ // packet #1.
+ dsackBlock := make([]byte, 40)
+ dsbOff := 0
+ dsbOff += header.EncodeNOP(dsackBlock[dsbOff:])
+ dsbOff += header.EncodeNOP(dsackBlock[dsbOff:])
+ dsbOff += header.EncodeSACKBlocks([]header.SACKBlock{{
+ seqNum1.Add(seqnum.Size(len(payload))),
+ seqNum1.Add(seqnum.Size(4 * len(payload))),
+ }}, dsackBlock[dsbOff:])
+
+ conn.Send(t, tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), AckNum: tb.Uint32(uint32(seqNum2)), Options: dsackBlock[:dsbOff]})
+
+ // Send half of the original window of packets, checking that we
+ // received each.
+ for i, sn := 0, seqNum2; i < numPkts/2; i++ {
+ dut.Send(t, acceptFd, payload, 0)
+
+ gotOne, err := conn.Expect(t, tb.TCP{SeqNum: tb.Uint32(uint32(sn))}, time.Second)
+ sn.UpdateForward(seqnum.Size(len(payload)))
+ if err != nil {
+ t.Errorf("Expect #%d: %s", i+1, err)
+ continue
+ }
+ if gotOne == nil {
+ t.Errorf("#%d: expected a packet within a second but got none", i+1)
+ }
+ }
+
+ if tb.DUTType == "netstack" {
+ // The window should now be halved, so we should receive any
+ // more, even if we send them.
+ dut.Send(t, acceptFd, payload, 0)
+ if got, err := conn.Expect(t, tb.TCP{}, 100*time.Millisecond); got != nil || err == nil {
+ t.Fatalf("expected no packets within 100 millisecond, but got one: %s", got)
+ }
+ return
+ }
+
+ // Linux reduces the window by three. Check that we can receive the rest.
+ for i, sn := 0, seqNum2.Add(seqnum.Size(numPkts/2*len(payload))); i < 2; i++ {
+ dut.Send(t, acceptFd, payload, 0)
+
+ gotOne, err := conn.Expect(t, tb.TCP{SeqNum: tb.Uint32(uint32(sn))}, time.Second)
+ sn.UpdateForward(seqnum.Size(len(payload)))
+ if err != nil {
+ t.Errorf("Expect #%d: %s", i+1, err)
+ continue
+ }
+ if gotOne == nil {
+ t.Errorf("#%d: expected a packet within a second but got none", i+1)
+ }
+ }
+
+ // The window should now be full.
+ dut.Send(t, acceptFd, payload, 0)
+ if got, err := conn.Expect(t, tb.TCP{}, 100*time.Millisecond); got != nil || err == nil {
+ t.Fatalf("expected no packets within 100 millisecond, but got one: %s", got)
+ }
+}
diff --git a/test/packetimpact/tests/tcp_retransmits_test.go b/test/packetimpact/tests/tcp_retransmits_test.go
index c043ad881..072014ff8 100644
--- a/test/packetimpact/tests/tcp_retransmits_test.go
+++ b/test/packetimpact/tests/tcp_retransmits_test.go
@@ -21,54 +21,54 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/header"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
)
func init() {
- tb.RegisterFlags(flag.CommandLine)
+ testbench.RegisterFlags(flag.CommandLine)
}
// TestRetransmits tests retransmits occur at exponentially increasing
// time intervals.
func TestRetransmits(t *testing.T) {
- dut := tb.NewDUT(t)
+ dut := testbench.NewDUT(t)
defer dut.TearDown()
- listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(listenFd)
- conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
- defer conn.Close()
+ listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(t, listenFd)
+ conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ defer conn.Close(t)
- conn.Handshake()
- acceptFd, _ := dut.Accept(listenFd)
- defer dut.Close(acceptFd)
+ conn.Connect(t)
+ acceptFd, _ := dut.Accept(t, listenFd)
+ defer dut.Close(t, acceptFd)
- dut.SetSockOptInt(acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
+ dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
sampleData := []byte("Sample Data")
- samplePayload := &tb.Payload{Bytes: sampleData}
+ samplePayload := &testbench.Payload{Bytes: sampleData}
- dut.Send(acceptFd, sampleData, 0)
- if _, err := conn.ExpectData(&tb.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
+ dut.Send(t, acceptFd, sampleData, 0)
+ if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
}
// Give a chance for the dut to estimate RTO with RTT from the DATA-ACK.
// TODO(gvisor.dev/issue/2685) Estimate RTO during handshake, after which
// we can skip sending this ACK.
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)})
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)})
startRTO := time.Second
current := startRTO
first := time.Now()
- dut.Send(acceptFd, sampleData, 0)
- seq := tb.Uint32(uint32(*conn.RemoteSeqNum()))
- if _, err := conn.ExpectData(&tb.TCP{SeqNum: seq}, samplePayload, startRTO); err != nil {
- t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
+ dut.Send(t, acceptFd, sampleData, 0)
+ seq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)))
+ if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: seq}, samplePayload, startRTO); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
}
// Expect retransmits of the same segment.
for i := 0; i < 5; i++ {
start := time.Now()
- if _, err := conn.ExpectData(&tb.TCP{SeqNum: seq}, samplePayload, 2*current); err != nil {
- t.Fatalf("expected a packet with payload %v: %s loop %d", samplePayload, err, i)
+ if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: seq}, samplePayload, 2*current); err != nil {
+ t.Fatalf("expected payload was not received: %s loop %d", err, i)
}
if i == 0 {
startRTO = time.Now().Sub(first)
@@ -77,7 +77,7 @@ func TestRetransmits(t *testing.T) {
}
// Check if the probes came at exponentially increasing intervals.
if p := time.Since(start); p < current-startRTO {
- t.Fatalf("retransmit came sooner interval %d probe %d\n", p, i)
+ t.Fatalf("retransmit came sooner interval %d probe %d", p, i)
}
current *= 2
}
diff --git a/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go b/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go
new file mode 100644
index 000000000..f91b06ba1
--- /dev/null
+++ b/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go
@@ -0,0 +1,105 @@
+// Copyright 2020 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 tcp_send_window_sizes_piggyback_test
+
+import (
+ "flag"
+ "fmt"
+ "testing"
+ "time"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+func init() {
+ testbench.RegisterFlags(flag.CommandLine)
+}
+
+// TestSendWindowSizesPiggyback tests cases where segment sizes are close to
+// sender window size and checks for ACK piggybacking for each of those case.
+func TestSendWindowSizesPiggyback(t *testing.T) {
+ sampleData := []byte("Sample Data")
+ segmentSize := uint16(len(sampleData))
+ // Advertise receive window sizes that are lesser, equal to or greater than
+ // enqueued segment size and check for segment transmits. The test attempts
+ // to enqueue a segment on the dut before acknowledging previous segment and
+ // lets the dut piggyback any ACKs along with the enqueued segment.
+ for _, tt := range []struct {
+ description string
+ windowSize uint16
+ expectedPayload1 []byte
+ expectedPayload2 []byte
+ enqueue bool
+ }{
+ // Expect the first segment to be split as it cannot be accomodated in
+ // the sender window. This means we need not enqueue a new segment after
+ // the first segment.
+ {"WindowSmallerThanSegment", segmentSize - 1, sampleData[:(segmentSize - 1)], sampleData[(segmentSize - 1):], false /* enqueue */},
+
+ {"WindowEqualToSegment", segmentSize, sampleData, sampleData, true /* enqueue */},
+
+ // Expect the second segment to not be split as its size is greater than
+ // the available sender window size. The segments should not be split
+ // when there is pending unacknowledged data and the segment-size is
+ // greater than available sender window.
+ {"WindowGreaterThanSegment", segmentSize + 1, sampleData, sampleData, true /* enqueue */},
+ } {
+ t.Run(fmt.Sprintf("%s%d", tt.description, tt.windowSize), func(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(t, listenFd)
+
+ conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort, WindowSize: testbench.Uint16(tt.windowSize)}, testbench.TCP{SrcPort: &remotePort})
+ defer conn.Close(t)
+
+ conn.Connect(t)
+ acceptFd, _ := dut.Accept(t, listenFd)
+ defer dut.Close(t, acceptFd)
+
+ dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
+
+ expectedTCP := testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}
+
+ dut.Send(t, acceptFd, sampleData, 0)
+ expectedPayload := testbench.Payload{Bytes: tt.expectedPayload1}
+ if _, err := conn.ExpectData(t, &expectedTCP, &expectedPayload, time.Second); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
+ }
+
+ // Expect any enqueued segment to be transmitted by the dut along with
+ // piggybacked ACK for our data.
+
+ if tt.enqueue {
+ // Enqueue a segment for the dut to transmit.
+ dut.Send(t, acceptFd, sampleData, 0)
+ }
+
+ // Send ACK for the previous segment along with data for the dut to
+ // receive and ACK back. Sending this ACK would make room for the dut
+ // to transmit any enqueued segment.
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh), WindowSize: testbench.Uint16(tt.windowSize)}, &testbench.Payload{Bytes: sampleData})
+
+ // Expect the dut to piggyback the ACK for received data along with
+ // the segment enqueued for transmit.
+ expectedPayload = testbench.Payload{Bytes: tt.expectedPayload2}
+ if _, err := conn.ExpectData(t, &expectedTCP, &expectedPayload, time.Second); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
+ }
+ })
+ }
+}
diff --git a/test/packetimpact/tests/tcp_should_piggyback_test.go b/test/packetimpact/tests/tcp_should_piggyback_test.go
deleted file mode 100644
index 0240dc2f9..000000000
--- a/test/packetimpact/tests/tcp_should_piggyback_test.go
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2020 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 tcp_should_piggyback_test
-
-import (
- "flag"
- "testing"
- "time"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- tb.RegisterFlags(flag.CommandLine)
-}
-
-func TestPiggyback(t *testing.T) {
- dut := tb.NewDUT(t)
- defer dut.TearDown()
- listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(listenFd)
- conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort, WindowSize: tb.Uint16(12)}, tb.TCP{SrcPort: &remotePort})
- defer conn.Close()
-
- conn.Handshake()
- acceptFd, _ := dut.Accept(listenFd)
- defer dut.Close(acceptFd)
-
- dut.SetSockOptInt(acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
-
- sampleData := []byte("Sample Data")
-
- dut.Send(acceptFd, sampleData, 0)
- expectedTCP := tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}
- expectedPayload := tb.Payload{Bytes: sampleData}
- if _, err := conn.ExpectData(&expectedTCP, &expectedPayload, time.Second); err != nil {
- t.Fatalf("Expected %v but didn't get one: %s", tb.Layers{&expectedTCP, &expectedPayload}, err)
- }
-
- // Cause DUT to send us more data as soon as we ACK their first data segment because we have
- // a small window.
- dut.Send(acceptFd, sampleData, 0)
-
- // DUT should ACK our segment by piggybacking ACK to their outstanding data segment instead of
- // sending a separate ACK packet.
- conn.Send(expectedTCP, &expectedPayload)
- if _, err := conn.ExpectData(&expectedTCP, &expectedPayload, time.Second); err != nil {
- t.Fatalf("Expected %v but didn't get one: %s", tb.Layers{&expectedTCP, &expectedPayload}, err)
- }
-}
diff --git a/test/packetimpact/tests/tcp_synrcvd_reset_test.go b/test/packetimpact/tests/tcp_synrcvd_reset_test.go
new file mode 100644
index 000000000..57d034dd1
--- /dev/null
+++ b/test/packetimpact/tests/tcp_synrcvd_reset_test.go
@@ -0,0 +1,52 @@
+// Copyright 2020 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 tcp_syn_reset_test
+
+import (
+ "flag"
+ "testing"
+ "time"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+func init() {
+ testbench.RegisterFlags(flag.CommandLine)
+}
+
+// TestTCPSynRcvdReset tests transition from SYN-RCVD to CLOSED.
+func TestTCPSynRcvdReset(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(t, listenFD)
+ conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ defer conn.Close(t)
+
+ // Expect dut connection to have transitioned to SYN-RCVD state.
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)})
+ if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil {
+ t.Fatalf("expected SYN-ACK %s", err)
+ }
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)})
+ // Expect the connection to have transitioned SYN-RCVD to CLOSED.
+ // TODO(gvisor.dev/issue/478): Check for TCP_INFO on the dut side.
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)})
+ if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)}, nil, time.Second); err != nil {
+ t.Fatalf("expected a TCP RST %s", err)
+ }
+}
diff --git a/test/packetimpact/tests/tcp_synsent_reset_test.go b/test/packetimpact/tests/tcp_synsent_reset_test.go
new file mode 100644
index 000000000..eac8eb19d
--- /dev/null
+++ b/test/packetimpact/tests/tcp_synsent_reset_test.go
@@ -0,0 +1,90 @@
+// Copyright 2020 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 tcp_synsent_reset_test
+
+import (
+ "flag"
+ "net"
+ "testing"
+ "time"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ tb "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+func init() {
+ tb.RegisterFlags(flag.CommandLine)
+}
+
+// dutSynSentState sets up the dut connection in SYN-SENT state.
+func dutSynSentState(t *testing.T) (*tb.DUT, *tb.TCPIPv4, uint16, uint16) {
+ t.Helper()
+
+ dut := tb.NewDUT(t)
+
+ clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, net.ParseIP(tb.RemoteIPv4))
+ port := uint16(9001)
+ conn := tb.NewTCPIPv4(t, tb.TCP{SrcPort: &port, DstPort: &clientPort}, tb.TCP{SrcPort: &clientPort, DstPort: &port})
+
+ sa := unix.SockaddrInet4{Port: int(port)}
+ copy(sa.Addr[:], net.IP(net.ParseIP(tb.LocalIPv4)).To4())
+ // Bring the dut to SYN-SENT state with a non-blocking connect.
+ dut.Connect(t, clientFD, &sa)
+ if _, err := conn.ExpectData(t, &tb.TCP{Flags: tb.Uint8(header.TCPFlagSyn)}, nil, time.Second); err != nil {
+ t.Fatalf("expected SYN\n")
+ }
+
+ return &dut, &conn, port, clientPort
+}
+
+// TestTCPSynSentReset tests RFC793, p67: SYN-SENT to CLOSED transition.
+func TestTCPSynSentReset(t *testing.T) {
+ dut, conn, _, _ := dutSynSentState(t)
+ defer conn.Close(t)
+ defer dut.TearDown()
+ conn.Send(t, tb.TCP{Flags: tb.Uint8(header.TCPFlagRst | header.TCPFlagAck)})
+ // Expect the connection to have closed.
+ // TODO(gvisor.dev/issue/478): Check for TCP_INFO on the dut side.
+ conn.Send(t, tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)})
+ if _, err := conn.ExpectData(t, &tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)}, nil, time.Second); err != nil {
+ t.Fatalf("expected a TCP RST")
+ }
+}
+
+// TestTCPSynSentRcvdReset tests RFC793, p70, SYN-SENT to SYN-RCVD to CLOSED
+// transitions.
+func TestTCPSynSentRcvdReset(t *testing.T) {
+ dut, c, remotePort, clientPort := dutSynSentState(t)
+ defer dut.TearDown()
+ defer c.Close(t)
+
+ conn := tb.NewTCPIPv4(t, tb.TCP{SrcPort: &remotePort, DstPort: &clientPort}, tb.TCP{SrcPort: &clientPort, DstPort: &remotePort})
+ defer conn.Close(t)
+ // Initiate new SYN connection with the same port pair
+ // (simultaneous open case), expect the dut connection to move to
+ // SYN-RCVD state
+ conn.Send(t, tb.TCP{Flags: tb.Uint8(header.TCPFlagSyn)})
+ if _, err := conn.ExpectData(t, &tb.TCP{Flags: tb.Uint8(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil {
+ t.Fatalf("expected SYN-ACK %s\n", err)
+ }
+ conn.Send(t, tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)})
+ // Expect the connection to have transitioned SYN-RCVD to CLOSED.
+ // TODO(gvisor.dev/issue/478): Check for TCP_INFO on the dut side.
+ conn.Send(t, tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)})
+ if _, err := conn.ExpectData(t, &tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)}, nil, time.Second); err != nil {
+ t.Fatalf("expected a TCP RST")
+ }
+}
diff --git a/test/packetimpact/tests/tcp_user_timeout_test.go b/test/packetimpact/tests/tcp_user_timeout_test.go
index ce31917ee..551dc78e7 100644
--- a/test/packetimpact/tests/tcp_user_timeout_test.go
+++ b/test/packetimpact/tests/tcp_user_timeout_test.go
@@ -16,35 +16,32 @@ package tcp_user_timeout_test
import (
"flag"
- "fmt"
"testing"
"time"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/header"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
)
func init() {
- tb.RegisterFlags(flag.CommandLine)
+ testbench.RegisterFlags(flag.CommandLine)
}
-func sendPayload(conn *tb.TCPIPv4, dut *tb.DUT, fd int32) error {
+func sendPayload(t *testing.T, conn *testbench.TCPIPv4, dut *testbench.DUT, fd int32) {
sampleData := make([]byte, 100)
for i := range sampleData {
sampleData[i] = uint8(i)
}
- conn.Drain()
- dut.Send(fd, sampleData, 0)
- if _, err := conn.ExpectData(&tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, &tb.Payload{Bytes: sampleData}, time.Second); err != nil {
- return fmt.Errorf("expected data but got none: %w", err)
+ conn.Drain(t)
+ dut.Send(t, fd, sampleData, 0)
+ if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, &testbench.Payload{Bytes: sampleData}, time.Second); err != nil {
+ t.Fatalf("expected data but got none: %w", err)
}
- return nil
}
-func sendFIN(conn *tb.TCPIPv4, dut *tb.DUT, fd int32) error {
- dut.Close(fd)
- return nil
+func sendFIN(t *testing.T, conn *testbench.TCPIPv4, dut *testbench.DUT, fd int32) {
+ dut.Close(t, fd)
}
func TestTCPUserTimeout(t *testing.T) {
@@ -59,40 +56,38 @@ func TestTCPUserTimeout(t *testing.T) {
} {
for _, ttf := range []struct {
description string
- f func(conn *tb.TCPIPv4, dut *tb.DUT, fd int32) error
+ f func(_ *testing.T, _ *testbench.TCPIPv4, _ *testbench.DUT, fd int32)
}{
{"AfterPayload", sendPayload},
{"AfterFIN", sendFIN},
} {
t.Run(tt.description+ttf.description, func(t *testing.T) {
// Create a socket, listen, TCP handshake, and accept.
- dut := tb.NewDUT(t)
+ dut := testbench.NewDUT(t)
defer dut.TearDown()
- listenFD, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(listenFD)
- conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
- defer conn.Close()
- conn.Handshake()
- acceptFD, _ := dut.Accept(listenFD)
+ listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(t, listenFD)
+ conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ defer conn.Close(t)
+ conn.Connect(t)
+ acceptFD, _ := dut.Accept(t, listenFD)
if tt.userTimeout != 0 {
- dut.SetSockOptInt(acceptFD, unix.SOL_TCP, unix.TCP_USER_TIMEOUT, int32(tt.userTimeout.Milliseconds()))
+ dut.SetSockOptInt(t, acceptFD, unix.SOL_TCP, unix.TCP_USER_TIMEOUT, int32(tt.userTimeout.Milliseconds()))
}
- if err := ttf.f(&conn, &dut, acceptFD); err != nil {
- t.Fatal(err)
- }
+ ttf.f(t, &conn, &dut, acceptFD)
time.Sleep(tt.sendDelay)
- conn.Drain()
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)})
+ conn.Drain(t)
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)})
// If TCP_USER_TIMEOUT was set and the above delay was longer than the
// TCP_USER_TIMEOUT then the DUT should send a RST in response to the
// testbench's packet.
expectRST := tt.userTimeout != 0 && tt.sendDelay > tt.userTimeout
expectTimeout := 5 * time.Second
- got, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)}, expectTimeout)
+ got, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)}, expectTimeout)
if expectRST && err != nil {
t.Errorf("expected RST packet within %s but got none: %s", expectTimeout, err)
}
diff --git a/test/packetimpact/tests/tcp_window_shrink_test.go b/test/packetimpact/tests/tcp_window_shrink_test.go
index 58ec1d740..5b001fbec 100644
--- a/test/packetimpact/tests/tcp_window_shrink_test.go
+++ b/test/packetimpact/tests/tcp_window_shrink_test.go
@@ -21,53 +21,53 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/header"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
)
func init() {
- tb.RegisterFlags(flag.CommandLine)
+ testbench.RegisterFlags(flag.CommandLine)
}
func TestWindowShrink(t *testing.T) {
- dut := tb.NewDUT(t)
+ dut := testbench.NewDUT(t)
defer dut.TearDown()
- listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(listenFd)
- conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
- defer conn.Close()
+ listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(t, listenFd)
+ conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ defer conn.Close(t)
- conn.Handshake()
- acceptFd, _ := dut.Accept(listenFd)
- defer dut.Close(acceptFd)
+ conn.Connect(t)
+ acceptFd, _ := dut.Accept(t, listenFd)
+ defer dut.Close(t, acceptFd)
- dut.SetSockOptInt(acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
+ dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
sampleData := []byte("Sample Data")
- samplePayload := &tb.Payload{Bytes: sampleData}
+ samplePayload := &testbench.Payload{Bytes: sampleData}
- dut.Send(acceptFd, sampleData, 0)
- if _, err := conn.ExpectData(&tb.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
+ dut.Send(t, acceptFd, sampleData, 0)
+ if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
}
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)})
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)})
- dut.Send(acceptFd, sampleData, 0)
- dut.Send(acceptFd, sampleData, 0)
- if _, err := conn.ExpectData(&tb.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
+ dut.Send(t, acceptFd, sampleData, 0)
+ dut.Send(t, acceptFd, sampleData, 0)
+ if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
}
- if _, err := conn.ExpectData(&tb.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
+ if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
}
// We close our receiving window here
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), WindowSize: tb.Uint16(0)})
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), WindowSize: testbench.Uint16(0)})
- dut.Send(acceptFd, []byte("Sample Data"), 0)
+ dut.Send(t, acceptFd, []byte("Sample Data"), 0)
// Note: There is another kind of zero-window probing which Windows uses (by sending one
// new byte at `RemoteSeqNum`), if netstack wants to go that way, we may want to change
// the following lines.
- expectedRemoteSeqNum := *conn.RemoteSeqNum() - 1
- if _, err := conn.ExpectData(&tb.TCP{SeqNum: tb.Uint32(uint32(expectedRemoteSeqNum))}, nil, time.Second); err != nil {
- t.Fatalf("expected a packet with sequence number %v: %s", expectedRemoteSeqNum, err)
+ expectedRemoteSeqNum := *conn.RemoteSeqNum(t) - 1
+ if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: testbench.Uint32(uint32(expectedRemoteSeqNum))}, nil, time.Second); err != nil {
+ t.Fatalf("expected a packet with sequence number %d: %s", expectedRemoteSeqNum, err)
}
}
diff --git a/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go b/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go
index dd43a24db..da93267d6 100644
--- a/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go
+++ b/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go
@@ -21,40 +21,40 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/header"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
)
func init() {
- tb.RegisterFlags(flag.CommandLine)
+ testbench.RegisterFlags(flag.CommandLine)
}
// TestZeroWindowProbeRetransmit tests retransmits of zero window probes
// to be sent at exponentially inreasing time intervals.
func TestZeroWindowProbeRetransmit(t *testing.T) {
- dut := tb.NewDUT(t)
+ dut := testbench.NewDUT(t)
defer dut.TearDown()
- listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(listenFd)
- conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
- defer conn.Close()
+ listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(t, listenFd)
+ conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ defer conn.Close(t)
- conn.Handshake()
- acceptFd, _ := dut.Accept(listenFd)
- defer dut.Close(acceptFd)
+ conn.Connect(t)
+ acceptFd, _ := dut.Accept(t, listenFd)
+ defer dut.Close(t, acceptFd)
- dut.SetSockOptInt(acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
+ dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
sampleData := []byte("Sample Data")
- samplePayload := &tb.Payload{Bytes: sampleData}
+ samplePayload := &testbench.Payload{Bytes: sampleData}
// Send and receive sample data to the dut.
- dut.Send(acceptFd, sampleData, 0)
- if _, err := conn.ExpectData(&tb.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
+ dut.Send(t, acceptFd, sampleData, 0)
+ if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
}
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload)
- if _, err := conn.ExpectData(&tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, nil, time.Second); err != nil {
- t.Fatalf("expected a packet with sequence number %s", err)
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload)
+ if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, nil, time.Second); err != nil {
+ t.Fatalf("expected packet was not received: %s", err)
}
// Check for the dut to keep the connection alive as long as the zero window
@@ -63,15 +63,15 @@ func TestZeroWindowProbeRetransmit(t *testing.T) {
// of the recorded first zero probe transmission duration.
//
// Advertize zero receive window again.
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), WindowSize: tb.Uint16(0)})
- probeSeq := tb.Uint32(uint32(*conn.RemoteSeqNum() - 1))
- ackProbe := tb.Uint32(uint32(*conn.RemoteSeqNum()))
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), WindowSize: testbench.Uint16(0)})
+ probeSeq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t) - 1))
+ ackProbe := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)))
startProbeDuration := time.Second
current := startProbeDuration
first := time.Now()
// Ask the dut to send out data.
- dut.Send(acceptFd, sampleData, 0)
+ dut.Send(t, acceptFd, sampleData, 0)
// Expect the dut to keep the connection alive as long as the remote is
// acknowledging the zero-window probes.
for i := 0; i < 5; i++ {
@@ -79,8 +79,8 @@ func TestZeroWindowProbeRetransmit(t *testing.T) {
// Expect zero-window probe with a timeout which is a function of the typical
// first retransmission time. The retransmission times is supposed to
// exponentially increase.
- if _, err := conn.ExpectData(&tb.TCP{SeqNum: probeSeq}, nil, 2*current); err != nil {
- t.Fatalf("expected a probe with sequence number %v: loop %d", probeSeq, i)
+ if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: probeSeq}, nil, 2*current); err != nil {
+ t.Fatalf("expected a probe with sequence number %d: loop %d", probeSeq, i)
}
if i == 0 {
startProbeDuration = time.Now().Sub(first)
@@ -88,17 +88,17 @@ func TestZeroWindowProbeRetransmit(t *testing.T) {
continue
}
// Check if the probes came at exponentially increasing intervals.
- if p := time.Since(start); p < current-startProbeDuration {
- t.Fatalf("zero probe came sooner interval %d probe %d\n", p, i)
+ if got, want := time.Since(start), current-startProbeDuration; got < want {
+ t.Errorf("got zero probe %d after %s, want >= %s", i, got, want)
}
// Acknowledge the zero-window probes from the dut.
- conn.Send(tb.TCP{AckNum: ackProbe, Flags: tb.Uint8(header.TCPFlagAck), WindowSize: tb.Uint16(0)})
+ conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.Uint8(header.TCPFlagAck), WindowSize: testbench.Uint16(0)})
current *= 2
}
// Advertize non-zero window.
- conn.Send(tb.TCP{AckNum: ackProbe, Flags: tb.Uint8(header.TCPFlagAck)})
+ conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.Uint8(header.TCPFlagAck)})
// Expect the dut to recover and transmit data.
- if _, err := conn.ExpectData(&tb.TCP{SeqNum: ackProbe}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
+ if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: ackProbe}, samplePayload, time.Second); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
}
}
diff --git a/test/packetimpact/tests/tcp_zero_window_probe_test.go b/test/packetimpact/tests/tcp_zero_window_probe_test.go
index 6c453505d..44cac42f8 100644
--- a/test/packetimpact/tests/tcp_zero_window_probe_test.go
+++ b/test/packetimpact/tests/tcp_zero_window_probe_test.go
@@ -21,92 +21,92 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/header"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
)
func init() {
- tb.RegisterFlags(flag.CommandLine)
+ testbench.RegisterFlags(flag.CommandLine)
}
// TestZeroWindowProbe tests few cases of zero window probing over the
// same connection.
func TestZeroWindowProbe(t *testing.T) {
- dut := tb.NewDUT(t)
+ dut := testbench.NewDUT(t)
defer dut.TearDown()
- listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(listenFd)
- conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
- defer conn.Close()
+ listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(t, listenFd)
+ conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ defer conn.Close(t)
- conn.Handshake()
- acceptFd, _ := dut.Accept(listenFd)
- defer dut.Close(acceptFd)
+ conn.Connect(t)
+ acceptFd, _ := dut.Accept(t, listenFd)
+ defer dut.Close(t, acceptFd)
- dut.SetSockOptInt(acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
+ dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
sampleData := []byte("Sample Data")
- samplePayload := &tb.Payload{Bytes: sampleData}
+ samplePayload := &testbench.Payload{Bytes: sampleData}
start := time.Now()
// Send and receive sample data to the dut.
- dut.Send(acceptFd, sampleData, 0)
- if _, err := conn.ExpectData(&tb.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
+ dut.Send(t, acceptFd, sampleData, 0)
+ if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
}
sendTime := time.Now().Sub(start)
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload)
- if _, err := conn.ExpectData(&tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, nil, time.Second); err != nil {
- t.Fatalf("expected a packet with sequence number %s", err)
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload)
+ if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, nil, time.Second); err != nil {
+ t.Fatalf("expected packet was not received: %s", err)
}
// Test 1: Check for receive of a zero window probe, record the duration for
// probe to be sent.
//
// Advertize zero window to the dut.
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), WindowSize: tb.Uint16(0)})
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), WindowSize: testbench.Uint16(0)})
// Expected sequence number of the zero window probe.
- probeSeq := tb.Uint32(uint32(*conn.RemoteSeqNum() - 1))
+ probeSeq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t) - 1))
// Expected ack number of the ACK for the probe.
- ackProbe := tb.Uint32(uint32(*conn.RemoteSeqNum()))
+ ackProbe := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)))
// Expect there are no zero-window probes sent until there is data to be sent out
// from the dut.
- if _, err := conn.ExpectData(&tb.TCP{SeqNum: probeSeq}, nil, 2*time.Second); err == nil {
- t.Fatalf("unexpected a packet with sequence number %v: %s", probeSeq, err)
+ if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: probeSeq}, nil, 2*time.Second); err == nil {
+ t.Fatalf("unexpected packet with sequence number %d: %s", probeSeq, err)
}
start = time.Now()
// Ask the dut to send out data.
- dut.Send(acceptFd, sampleData, 0)
+ dut.Send(t, acceptFd, sampleData, 0)
// Expect zero-window probe from the dut.
- if _, err := conn.ExpectData(&tb.TCP{SeqNum: probeSeq}, nil, time.Second); err != nil {
- t.Fatalf("expected a packet with sequence number %v: %s", probeSeq, err)
+ if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: probeSeq}, nil, time.Second); err != nil {
+ t.Fatalf("expected a packet with sequence number %d: %s", probeSeq, err)
}
// Expect the probe to be sent after some time. Compare against the previous
// time recorded when the dut immediately sends out data on receiving the
// send command.
if startProbeDuration := time.Now().Sub(start); startProbeDuration <= sendTime {
- t.Fatalf("expected the first probe to be sent out after retransmission interval, got %v want > %v\n", startProbeDuration, sendTime)
+ t.Fatalf("expected the first probe to be sent out after retransmission interval, got %s want > %s", startProbeDuration, sendTime)
}
// Test 2: Check if the dut recovers on advertizing non-zero receive window.
// and sends out the sample payload after the send window opens.
//
// Advertize non-zero window to the dut and ack the zero window probe.
- conn.Send(tb.TCP{AckNum: ackProbe, Flags: tb.Uint8(header.TCPFlagAck)})
+ conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.Uint8(header.TCPFlagAck)})
// Expect the dut to recover and transmit data.
- if _, err := conn.ExpectData(&tb.TCP{SeqNum: ackProbe}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
+ if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: ackProbe}, samplePayload, time.Second); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
}
// Test 3: Sanity check for dut's processing of a similar probe it sent.
// Check if the dut responds as we do for a similar probe sent to it.
// Basically with sequence number to one byte behind the unacknowledged
// sequence number.
- p := tb.Uint32(uint32(*conn.LocalSeqNum()))
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), SeqNum: tb.Uint32(uint32(*conn.LocalSeqNum() - 1))})
- if _, err := conn.ExpectData(&tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), AckNum: p}, nil, time.Second); err != nil {
+ p := testbench.Uint32(uint32(*conn.LocalSeqNum(t)))
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), SeqNum: testbench.Uint32(uint32(*conn.LocalSeqNum(t) - 1))})
+ if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: p}, nil, time.Second); err != nil {
t.Fatalf("expected a packet with ack number: %d: %s", p, err)
}
}
diff --git a/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go b/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go
index 193427fb9..09a1c653f 100644
--- a/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go
+++ b/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go
@@ -21,56 +21,56 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/header"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
)
func init() {
- tb.RegisterFlags(flag.CommandLine)
+ testbench.RegisterFlags(flag.CommandLine)
}
// TestZeroWindowProbeUserTimeout sanity tests user timeout when we are
// retransmitting zero window probes.
func TestZeroWindowProbeUserTimeout(t *testing.T) {
- dut := tb.NewDUT(t)
+ dut := testbench.NewDUT(t)
defer dut.TearDown()
- listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
- defer dut.Close(listenFd)
- conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
- defer conn.Close()
+ listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(t, listenFd)
+ conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ defer conn.Close(t)
- conn.Handshake()
- acceptFd, _ := dut.Accept(listenFd)
- defer dut.Close(acceptFd)
+ conn.Connect(t)
+ acceptFd, _ := dut.Accept(t, listenFd)
+ defer dut.Close(t, acceptFd)
- dut.SetSockOptInt(acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
+ dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
sampleData := []byte("Sample Data")
- samplePayload := &tb.Payload{Bytes: sampleData}
+ samplePayload := &testbench.Payload{Bytes: sampleData}
// Send and receive sample data to the dut.
- dut.Send(acceptFd, sampleData, 0)
- if _, err := conn.ExpectData(&tb.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
+ dut.Send(t, acceptFd, sampleData, 0)
+ if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
}
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload)
- if _, err := conn.ExpectData(&tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, nil, time.Second); err != nil {
- t.Fatalf("expected a packet with sequence number %s", err)
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload)
+ if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, nil, time.Second); err != nil {
+ t.Fatalf("expected packet was not received: %s", err)
}
// Test 1: Check for receive of a zero window probe, record the duration for
// probe to be sent.
//
// Advertize zero window to the dut.
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), WindowSize: tb.Uint16(0)})
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), WindowSize: testbench.Uint16(0)})
// Expected sequence number of the zero window probe.
- probeSeq := tb.Uint32(uint32(*conn.RemoteSeqNum() - 1))
+ probeSeq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t) - 1))
start := time.Now()
// Ask the dut to send out data.
- dut.Send(acceptFd, sampleData, 0)
+ dut.Send(t, acceptFd, sampleData, 0)
// Expect zero-window probe from the dut.
- if _, err := conn.ExpectData(&tb.TCP{SeqNum: probeSeq}, nil, time.Second); err != nil {
- t.Fatalf("expected a packet with sequence number %v: %s", probeSeq, err)
+ if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: probeSeq}, nil, time.Second); err != nil {
+ t.Fatalf("expected a packet with sequence number %d: %s", probeSeq, err)
}
// Record the duration for first probe, the dut sends the zero window probe after
// a retransmission time interval.
@@ -80,19 +80,19 @@ func TestZeroWindowProbeUserTimeout(t *testing.T) {
// when the dut is sending zero-window probes.
//
// Reduce the retransmit timeout.
- dut.SetSockOptInt(acceptFd, unix.IPPROTO_TCP, unix.TCP_USER_TIMEOUT, int32(startProbeDuration.Milliseconds()))
+ dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_USER_TIMEOUT, int32(startProbeDuration.Milliseconds()))
// Advertize zero window again.
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), WindowSize: tb.Uint16(0)})
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), WindowSize: testbench.Uint16(0)})
// Ask the dut to send out data that would trigger zero window probe retransmissions.
- dut.Send(acceptFd, sampleData, 0)
+ dut.Send(t, acceptFd, sampleData, 0)
// Wait for the connection to timeout after multiple zero-window probe retransmissions.
time.Sleep(8 * startProbeDuration)
// Expect the connection to have timed out and closed which would cause the dut
// to reply with a RST to the ACK we send.
- conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)})
- if _, err := conn.ExpectData(&tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)}, nil, time.Second); err != nil {
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)})
+ if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)}, nil, time.Second); err != nil {
t.Fatalf("expected a TCP RST")
}
}
diff --git a/test/packetimpact/tests/test_runner.sh b/test/packetimpact/tests/test_runner.sh
deleted file mode 100755
index 706441cce..000000000
--- a/test/packetimpact/tests/test_runner.sh
+++ /dev/null
@@ -1,325 +0,0 @@
-#!/bin/bash
-
-# Copyright 2020 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.
-
-# Run a packetimpact test. Two docker containers are made, one for the
-# Device-Under-Test (DUT) and one for the test bench. Each is attached with
-# two networks, one for control packets that aid the test and one for test
-# packets which are sent as part of the test and observed for correctness.
-
-set -euxo pipefail
-
-function failure() {
- local lineno=$1
- local msg=$2
- local filename="$0"
- echo "FAIL: $filename:$lineno: $msg"
-}
-trap 'failure ${LINENO} "$BASH_COMMAND"' ERR
-
-declare -r LONGOPTS="dut_platform:,posix_server_binary:,testbench_binary:,runtime:,tshark,extra_test_arg:,expect_failure"
-
-# Don't use declare below so that the error from getopt will end the script.
-PARSED=$(getopt --options "" --longoptions=$LONGOPTS --name "$0" -- "$@")
-
-eval set -- "$PARSED"
-
-declare -a EXTRA_TEST_ARGS
-
-while true; do
- case "$1" in
- --dut_platform)
- # Either "linux" or "netstack".
- declare -r DUT_PLATFORM="$2"
- shift 2
- ;;
- --posix_server_binary)
- declare -r POSIX_SERVER_BINARY="$2"
- shift 2
- ;;
- --testbench_binary)
- declare -r TESTBENCH_BINARY="$2"
- shift 2
- ;;
- --runtime)
- # Not readonly because there might be multiple --runtime arguments and we
- # want to use just the last one. Only used if --dut_platform is
- # "netstack".
- declare RUNTIME="$2"
- shift 2
- ;;
- --tshark)
- declare -r TSHARK="1"
- shift 1
- ;;
- --extra_test_arg)
- EXTRA_TEST_ARGS+="$2"
- shift 2
- ;;
- --expect_failure)
- declare -r EXPECT_FAILURE="1"
- shift 1
- ;;
- --)
- shift
- break
- ;;
- *)
- echo "Programming error"
- exit 3
- esac
-done
-
-# All the other arguments are scripts.
-declare -r scripts="$@"
-
-# Check that the required flags are defined in a way that is safe for "set -u".
-if [[ "${DUT_PLATFORM-}" == "netstack" ]]; then
- if [[ -z "${RUNTIME-}" ]]; then
- echo "FAIL: Missing --runtime argument: ${RUNTIME-}"
- exit 2
- fi
- declare -r RUNTIME_ARG="--runtime ${RUNTIME}"
-elif [[ "${DUT_PLATFORM-}" == "linux" ]]; then
- declare -r RUNTIME_ARG=""
-else
- echo "FAIL: Bad or missing --dut_platform argument: ${DUT_PLATFORM-}"
- exit 2
-fi
-if [[ ! -f "${POSIX_SERVER_BINARY-}" ]]; then
- echo "FAIL: Bad or missing --posix_server_binary: ${POSIX_SERVER-}"
- exit 2
-fi
-if [[ ! -f "${TESTBENCH_BINARY-}" ]]; then
- echo "FAIL: Bad or missing --testbench_binary: ${TESTBENCH_BINARY-}"
- exit 2
-fi
-
-function new_net_prefix() {
- # Class C, 192.0.0.0 to 223.255.255.255, transitionally has mask 24.
- echo "$(shuf -i 192-223 -n 1).$(shuf -i 0-255 -n 1).$(shuf -i 0-255 -n 1)"
-}
-
-# Variables specific to the control network and interface start with CTRL_.
-# Variables specific to the test network and interface start with TEST_.
-# Variables specific to the DUT start with DUT_.
-# Variables specific to the test bench start with TESTBENCH_.
-# Use random numbers so that test networks don't collide.
-declare CTRL_NET="ctrl_net-${RANDOM}${RANDOM}"
-declare CTRL_NET_PREFIX=$(new_net_prefix)
-declare TEST_NET="test_net-${RANDOM}${RANDOM}"
-declare TEST_NET_PREFIX=$(new_net_prefix)
-# On both DUT and test bench, testing packets are on the eth2 interface.
-declare -r TEST_DEVICE="eth2"
-# Number of bits in the *_NET_PREFIX variables.
-declare -r NET_MASK="24"
-# Last bits of the DUT's IP address.
-declare -r DUT_NET_SUFFIX=".10"
-# Control port.
-declare -r CTRL_PORT="40000"
-# Last bits of the test bench's IP address.
-declare -r TESTBENCH_NET_SUFFIX=".20"
-declare -r TIMEOUT="60"
-declare -r IMAGE_TAG="gcr.io/gvisor-presubmit/packetimpact"
-
-# Make sure that docker is installed.
-docker --version
-
-function finish {
- local cleanup_success=1
-
- if [[ -z "${TSHARK-}" ]]; then
- # Kill tcpdump so that it will flush output.
- docker exec -t "${TESTBENCH}" \
- killall tcpdump || \
- cleanup_success=0
- else
- # Kill tshark so that it will flush output.
- docker exec -t "${TESTBENCH}" \
- killall tshark || \
- cleanup_success=0
- fi
-
- for net in "${CTRL_NET}" "${TEST_NET}"; do
- # Kill all processes attached to ${net}.
- for docker_command in "kill" "rm"; do
- (docker network inspect "${net}" \
- --format '{{range $key, $value := .Containers}}{{$key}} {{end}}' \
- | xargs -r docker "${docker_command}") || \
- cleanup_success=0
- done
- # Remove the network.
- docker network rm "${net}" || \
- cleanup_success=0
- done
-
- if ((!$cleanup_success)); then
- echo "FAIL: Cleanup command failed"
- exit 4
- fi
-}
-trap finish EXIT
-
-# Subnet for control packets between test bench and DUT.
-while ! docker network create \
- "--subnet=${CTRL_NET_PREFIX}.0/${NET_MASK}" "${CTRL_NET}"; do
- sleep 0.1
- CTRL_NET_PREFIX=$(new_net_prefix)
- CTRL_NET="ctrl_net-${RANDOM}${RANDOM}"
-done
-
-# Subnet for the packets that are part of the test.
-while ! docker network create \
- "--subnet=${TEST_NET_PREFIX}.0/${NET_MASK}" "${TEST_NET}"; do
- sleep 0.1
- TEST_NET_PREFIX=$(new_net_prefix)
- TEST_NET="test_net-${RANDOM}${RANDOM}"
-done
-
-docker pull "${IMAGE_TAG}"
-
-# Create the DUT container and connect to network.
-DUT=$(docker create ${RUNTIME_ARG} --privileged --rm \
- --cap-add NET_ADMIN \
- --sysctl net.ipv6.conf.all.disable_ipv6=0 \
- --stop-timeout ${TIMEOUT} -it ${IMAGE_TAG})
-docker network connect "${CTRL_NET}" \
- --ip "${CTRL_NET_PREFIX}${DUT_NET_SUFFIX}" "${DUT}" \
- || (docker kill ${DUT}; docker rm ${DUT}; false)
-docker network connect "${TEST_NET}" \
- --ip "${TEST_NET_PREFIX}${DUT_NET_SUFFIX}" "${DUT}" \
- || (docker kill ${DUT}; docker rm ${DUT}; false)
-docker start "${DUT}"
-
-# Create the test bench container and connect to network.
-TESTBENCH=$(docker create --privileged --rm \
- --cap-add NET_ADMIN \
- --sysctl net.ipv6.conf.all.disable_ipv6=0 \
- --stop-timeout ${TIMEOUT} -it ${IMAGE_TAG})
-docker network connect "${CTRL_NET}" \
- --ip "${CTRL_NET_PREFIX}${TESTBENCH_NET_SUFFIX}" "${TESTBENCH}" \
- || (docker kill ${TESTBENCH}; docker rm ${TESTBENCH}; false)
-docker network connect "${TEST_NET}" \
- --ip "${TEST_NET_PREFIX}${TESTBENCH_NET_SUFFIX}" "${TESTBENCH}" \
- || (docker kill ${TESTBENCH}; docker rm ${TESTBENCH}; false)
-docker start "${TESTBENCH}"
-
-# Start the posix_server in the DUT.
-declare -r DOCKER_POSIX_SERVER_BINARY="/$(basename ${POSIX_SERVER_BINARY})"
-docker cp -L ${POSIX_SERVER_BINARY} "${DUT}:${DOCKER_POSIX_SERVER_BINARY}"
-
-docker exec -t "${DUT}" \
- /bin/bash -c "${DOCKER_POSIX_SERVER_BINARY} \
- --ip ${CTRL_NET_PREFIX}${DUT_NET_SUFFIX} \
- --port ${CTRL_PORT}" &
-
-# Because the Linux kernel receives the SYN-ACK but didn't send the SYN it will
-# issue a RST. To prevent this IPtables can be used to filter those out.
-docker exec "${TESTBENCH}" \
- iptables -A INPUT -i ${TEST_DEVICE} -j DROP
-
-# Wait for the DUT server to come up. Attempt to connect to it from the test
-# bench every 100 milliseconds until success.
-while ! docker exec "${TESTBENCH}" \
- nc -zv "${CTRL_NET_PREFIX}${DUT_NET_SUFFIX}" "${CTRL_PORT}"; do
- sleep 0.1
-done
-
-declare -r REMOTE_MAC=$(docker exec -t "${DUT}" ip link show \
- "${TEST_DEVICE}" | tail -1 | cut -d' ' -f6)
-declare -r LOCAL_MAC=$(docker exec -t "${TESTBENCH}" ip link show \
- "${TEST_DEVICE}" | tail -1 | cut -d' ' -f6)
-declare REMOTE_IPV6=$(docker exec -t "${DUT}" ip addr show scope link \
- "${TEST_DEVICE}" | grep inet6 | cut -d' ' -f6 | cut -d'/' -f1)
-declare -r LOCAL_IPV6=$(docker exec -t "${TESTBENCH}" ip addr show scope link \
- "${TEST_DEVICE}" | grep inet6 | cut -d' ' -f6 | cut -d'/' -f1)
-
-# Netstack as DUT doesn't assign IPv6 addresses automatically so do it if
-# needed. Convert the MAC address to an IPv6 link local address as described in
-# RFC 4291 page 20: https://tools.ietf.org/html/rfc4291#page-20
-if [[ -z "${REMOTE_IPV6}" ]]; then
- # Split the octets of the MAC into an array of strings.
- IFS=":" read -a REMOTE_OCTETS <<< "${REMOTE_MAC}"
- # Flip the global bit.
- REMOTE_OCTETS[0]=$(printf '%x' "$((0x${REMOTE_OCTETS[0]} ^ 2))")
- # Add the IPv6 address.
- docker exec "${DUT}" \
- ip addr add $(printf 'fe80::%02x%02x:%02xff:fe%02x:%02x%02x/64' \
- "0x${REMOTE_OCTETS[0]}" "0x${REMOTE_OCTETS[1]}" "0x${REMOTE_OCTETS[2]}" \
- "0x${REMOTE_OCTETS[3]}" "0x${REMOTE_OCTETS[4]}" "0x${REMOTE_OCTETS[5]}") \
- scope link \
- dev "${TEST_DEVICE}"
- # Re-extract the IPv6 address.
- # TODO(eyalsoha): Add "scope link" below when netstack supports correctly
- # creating link-local IPv6 addresses.
- REMOTE_IPV6=$(docker exec -t "${DUT}" ip addr show \
- "${TEST_DEVICE}" | grep inet6 | cut -d' ' -f6 | cut -d'/' -f1)
-fi
-
-declare -r DOCKER_TESTBENCH_BINARY="/$(basename ${TESTBENCH_BINARY})"
-docker cp -L "${TESTBENCH_BINARY}" "${TESTBENCH}:${DOCKER_TESTBENCH_BINARY}"
-
-if [[ -z "${TSHARK-}" ]]; then
- # Run tcpdump in the test bench unbuffered, without dns resolution, just on
- # the interface with the test packets.
- docker exec -t "${TESTBENCH}" \
- tcpdump -S -vvv -U -n -i "${TEST_DEVICE}" \
- net "${TEST_NET_PREFIX}/24" or \
- host "${REMOTE_IPV6}" or \
- host "${LOCAL_IPV6}" &
-else
- # Run tshark in the test bench unbuffered, without dns resolution, just on the
- # interface with the test packets.
- docker exec -t "${TESTBENCH}" \
- tshark -V -l -n -i "${TEST_DEVICE}" \
- -o tcp.check_checksum:TRUE \
- -o udp.check_checksum:TRUE \
- net "${TEST_NET_PREFIX}/24" or \
- host "${REMOTE_IPV6}" or \
- host "${LOCAL_IPV6}" &
-fi
-
-# tcpdump and tshark take time to startup
-sleep 3
-
-# Start a packetimpact test on the test bench. The packetimpact test sends and
-# receives packets and also sends POSIX socket commands to the posix_server to
-# be executed on the DUT.
-docker exec \
- -e XML_OUTPUT_FILE="/test.xml" \
- -e TEST_TARGET \
- -t "${TESTBENCH}" \
- /bin/bash -c "${DOCKER_TESTBENCH_BINARY} \
- ${EXTRA_TEST_ARGS[@]-} \
- --posix_server_ip=${CTRL_NET_PREFIX}${DUT_NET_SUFFIX} \
- --posix_server_port=${CTRL_PORT} \
- --remote_ipv4=${TEST_NET_PREFIX}${DUT_NET_SUFFIX} \
- --local_ipv4=${TEST_NET_PREFIX}${TESTBENCH_NET_SUFFIX} \
- --remote_ipv6=${REMOTE_IPV6} \
- --local_ipv6=${LOCAL_IPV6} \
- --remote_mac=${REMOTE_MAC} \
- --local_mac=${LOCAL_MAC} \
- --device=${TEST_DEVICE}" && true
-declare -r TEST_RESULT="${?}"
-if [[ -z "${EXPECT_FAILURE-}" && "${TEST_RESULT}" != 0 ]]; then
- echo 'FAIL: This test was expected to pass.'
- exit ${TEST_RESULT}
-fi
-if [[ ! -z "${EXPECT_FAILURE-}" && "${TEST_RESULT}" == 0 ]]; then
- echo 'FAIL: This test was expected to fail but passed. Enable the test and' \
- 'mark the corresponding bug as fixed.'
- exit 1
-fi
-echo PASS: No errors.
diff --git a/test/packetimpact/tests/udp_any_addr_recv_unicast_test.go b/test/packetimpact/tests/udp_any_addr_recv_unicast_test.go
new file mode 100644
index 000000000..17f32ef65
--- /dev/null
+++ b/test/packetimpact/tests/udp_any_addr_recv_unicast_test.go
@@ -0,0 +1,51 @@
+// Copyright 2020 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 udp_any_addr_recv_unicast_test
+
+import (
+ "flag"
+ "net"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+func init() {
+ testbench.RegisterFlags(flag.CommandLine)
+}
+
+func TestAnyRecvUnicastUDP(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero)
+ defer dut.Close(t, boundFD)
+ conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
+ defer conn.Close(t)
+
+ payload := testbench.GenerateRandomPayload(t, 1<<10 /* 1 KiB */)
+ conn.SendIP(
+ t,
+ testbench.IPv4{DstAddr: testbench.Address(tcpip.Address(net.ParseIP(testbench.RemoteIPv4).To4()))},
+ testbench.UDP{},
+ &testbench.Payload{Bytes: payload},
+ )
+ got, want := dut.Recv(t, boundFD, int32(len(payload)+1), 0), payload
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff)
+ }
+}
diff --git a/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go b/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go
new file mode 100644
index 000000000..d30177e64
--- /dev/null
+++ b/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go
@@ -0,0 +1,94 @@
+// Copyright 2020 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 udp_discard_mcast_source_addr_test
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "net"
+ "syscall"
+ "testing"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+var oneSecond = unix.Timeval{Sec: 1, Usec: 0}
+
+func init() {
+ testbench.RegisterFlags(flag.CommandLine)
+}
+
+func TestDiscardsUDPPacketsWithMcastSourceAddressV4(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP(testbench.RemoteIPv4))
+ defer dut.Close(t, remoteFD)
+ dut.SetSockOptTimeval(t, remoteFD, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &oneSecond)
+ conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
+ defer conn.Close(t)
+
+ for _, mcastAddr := range []net.IP{
+ net.IPv4allsys,
+ net.IPv4allrouter,
+ net.IPv4(224, 0, 1, 42),
+ net.IPv4(232, 1, 2, 3),
+ } {
+ t.Run(fmt.Sprintf("srcaddr=%s", mcastAddr), func(t *testing.T) {
+ conn.SendIP(
+ t,
+ testbench.IPv4{SrcAddr: testbench.Address(tcpip.Address(mcastAddr.To4()))},
+ testbench.UDP{},
+ )
+
+ ret, payload, errno := dut.RecvWithErrno(context.Background(), t, remoteFD, 100, 0)
+ if errno != syscall.EAGAIN || errno != syscall.EWOULDBLOCK {
+ t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, payload, errno)
+ }
+ })
+ }
+}
+
+func TestDiscardsUDPPacketsWithMcastSourceAddressV6(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP(testbench.RemoteIPv6))
+ defer dut.Close(t, remoteFD)
+ dut.SetSockOptTimeval(t, remoteFD, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &oneSecond)
+ conn := testbench.NewUDPIPv6(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
+ defer conn.Close(t)
+
+ for _, mcastAddr := range []net.IP{
+ net.IPv6interfacelocalallnodes,
+ net.IPv6linklocalallnodes,
+ net.IPv6linklocalallrouters,
+ net.ParseIP("fe01::42"),
+ net.ParseIP("fe02::4242"),
+ } {
+ t.Run(fmt.Sprintf("srcaddr=%s", mcastAddr), func(t *testing.T) {
+ conn.SendIPv6(
+ t,
+ testbench.IPv6{SrcAddr: testbench.Address(tcpip.Address(mcastAddr.To16()))},
+ testbench.UDP{},
+ )
+ ret, payload, errno := dut.RecvWithErrno(context.Background(), t, remoteFD, 100, 0)
+ if errno != syscall.EAGAIN || errno != syscall.EWOULDBLOCK {
+ t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, payload, errno)
+ }
+ })
+ }
+}
diff --git a/test/packetimpact/tests/udp_icmp_error_propagation_test.go b/test/packetimpact/tests/udp_icmp_error_propagation_test.go
index ca4df2ab0..b47ddb6c3 100644
--- a/test/packetimpact/tests/udp_icmp_error_propagation_test.go
+++ b/test/packetimpact/tests/udp_icmp_error_propagation_test.go
@@ -26,11 +26,11 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/header"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
)
func init() {
- tb.RegisterFlags(flag.CommandLine)
+ testbench.RegisterFlags(flag.CommandLine)
}
type connectionMode bool
@@ -59,12 +59,12 @@ func (e icmpError) String() string {
return "Unknown ICMP error"
}
-func (e icmpError) ToICMPv4() *tb.ICMPv4 {
+func (e icmpError) ToICMPv4() *testbench.ICMPv4 {
switch e {
case portUnreachable:
- return &tb.ICMPv4{Type: tb.ICMPv4Type(header.ICMPv4DstUnreachable), Code: tb.Uint8(header.ICMPv4PortUnreachable)}
+ return &testbench.ICMPv4{Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable), Code: testbench.Uint8(header.ICMPv4PortUnreachable)}
case timeToLiveExceeded:
- return &tb.ICMPv4{Type: tb.ICMPv4Type(header.ICMPv4TimeExceeded), Code: tb.Uint8(header.ICMPv4TTLExceeded)}
+ return &testbench.ICMPv4{Type: testbench.ICMPv4Type(header.ICMPv4TimeExceeded), Code: testbench.Uint8(header.ICMPv4TTLExceeded)}
}
return nil
}
@@ -72,12 +72,12 @@ func (e icmpError) ToICMPv4() *tb.ICMPv4 {
type errorDetection struct {
name string
useValidConn bool
- f func(context.Context, testData) error
+ f func(context.Context, *testing.T, testData)
}
type testData struct {
- dut *tb.DUT
- conn *tb.UDPIPv4
+ dut *testbench.DUT
+ conn *testbench.UDPIPv4
remoteFD int32
remotePort uint16
cleanFD int32
@@ -95,102 +95,103 @@ func wantErrno(c connectionMode, icmpErr icmpError) syscall.Errno {
}
// sendICMPError sends an ICMP error message in response to a UDP datagram.
-func sendICMPError(conn *tb.UDPIPv4, icmpErr icmpError, udp *tb.UDP) error {
+func sendICMPError(t *testing.T, conn *testbench.UDPIPv4, icmpErr icmpError, udp *testbench.UDP) {
+ t.Helper()
+
+ layers := (*testbench.Connection)(conn).CreateFrame(t, nil)
+ layers = layers[:len(layers)-1]
+ ip, ok := udp.Prev().(*testbench.IPv4)
+ if !ok {
+ t.Fatalf("expected %s to be IPv4", udp.Prev())
+ }
if icmpErr == timeToLiveExceeded {
- ip, ok := udp.Prev().(*tb.IPv4)
- if !ok {
- return fmt.Errorf("expected %s to be IPv4", udp.Prev())
- }
*ip.TTL = 1
// Let serialization recalculate the checksum since we set the TTL
// to 1.
ip.Checksum = nil
-
- // Note that the ICMP payload is valid in this case because the UDP
- // payload is empty. If the UDP payload were not empty, the packet
- // length during serialization may not be calculated correctly,
- // resulting in a mal-formed packet.
- conn.SendIP(icmpErr.ToICMPv4(), ip, udp)
- } else {
- conn.SendIP(icmpErr.ToICMPv4(), udp.Prev(), udp)
}
- return nil
+ // Note that the ICMP payload is valid in this case because the UDP
+ // payload is empty. If the UDP payload were not empty, the packet
+ // length during serialization may not be calculated correctly,
+ // resulting in a mal-formed packet.
+ layers = append(layers, icmpErr.ToICMPv4(), ip, udp)
+
+ (*testbench.Connection)(conn).SendFrameStateless(t, layers)
}
// testRecv tests observing the ICMP error through the recv syscall. A packet
// is sent to the DUT, and if wantErrno is non-zero, then the first recv should
// fail and the second should succeed. Otherwise if wantErrno is zero then the
// first recv should succeed immediately.
-func testRecv(ctx context.Context, d testData) error {
+func testRecv(ctx context.Context, t *testing.T, d testData) {
+ t.Helper()
+
// Check that receiving on the clean socket works.
- d.conn.Send(tb.UDP{DstPort: &d.cleanPort})
- d.dut.Recv(d.cleanFD, 100, 0)
+ d.conn.Send(t, testbench.UDP{DstPort: &d.cleanPort})
+ d.dut.Recv(t, d.cleanFD, 100, 0)
- d.conn.Send(tb.UDP{})
+ d.conn.Send(t, testbench.UDP{})
if d.wantErrno != syscall.Errno(0) {
ctx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
- ret, _, err := d.dut.RecvWithErrno(ctx, d.remoteFD, 100, 0)
+ ret, _, err := d.dut.RecvWithErrno(ctx, t, d.remoteFD, 100, 0)
if ret != -1 {
- return fmt.Errorf("recv after ICMP error succeeded unexpectedly, expected (%[1]d) %[1]v", d.wantErrno)
+ t.Fatalf("recv after ICMP error succeeded unexpectedly, expected (%[1]d) %[1]v", d.wantErrno)
}
if err != d.wantErrno {
- return fmt.Errorf("recv after ICMP error resulted in error (%[1]d) %[1]v, expected (%[2]d) %[2]v", err, d.wantErrno)
+ t.Fatalf("recv after ICMP error resulted in error (%[1]d) %[1]v, expected (%[2]d) %[2]v", err, d.wantErrno)
}
}
- d.dut.Recv(d.remoteFD, 100, 0)
- return nil
+ d.dut.Recv(t, d.remoteFD, 100, 0)
}
// testSendTo tests observing the ICMP error through the send syscall. If
// wantErrno is non-zero, the first send should fail and a subsequent send
// should suceed; while if wantErrno is zero then the first send should just
// succeed.
-func testSendTo(ctx context.Context, d testData) error {
+func testSendTo(ctx context.Context, t *testing.T, d testData) {
// Check that sending on the clean socket works.
- d.dut.SendTo(d.cleanFD, nil, 0, d.conn.LocalAddr())
- if _, err := d.conn.Expect(tb.UDP{SrcPort: &d.cleanPort}, time.Second); err != nil {
- return fmt.Errorf("did not receive UDP packet from clean socket on DUT: %s", err)
+ d.dut.SendTo(t, d.cleanFD, nil, 0, d.conn.LocalAddr(t))
+ if _, err := d.conn.Expect(t, testbench.UDP{SrcPort: &d.cleanPort}, time.Second); err != nil {
+ t.Fatalf("did not receive UDP packet from clean socket on DUT: %s", err)
}
if d.wantErrno != syscall.Errno(0) {
ctx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
- ret, err := d.dut.SendToWithErrno(ctx, d.remoteFD, nil, 0, d.conn.LocalAddr())
+ ret, err := d.dut.SendToWithErrno(ctx, t, d.remoteFD, nil, 0, d.conn.LocalAddr(t))
if ret != -1 {
- return fmt.Errorf("sendto after ICMP error succeeded unexpectedly, expected (%[1]d) %[1]v", d.wantErrno)
+ t.Fatalf("sendto after ICMP error succeeded unexpectedly, expected (%[1]d) %[1]v", d.wantErrno)
}
if err != d.wantErrno {
- return fmt.Errorf("sendto after ICMP error resulted in error (%[1]d) %[1]v, expected (%[2]d) %[2]v", err, d.wantErrno)
+ t.Fatalf("sendto after ICMP error resulted in error (%[1]d) %[1]v, expected (%[2]d) %[2]v", err, d.wantErrno)
}
}
- d.dut.SendTo(d.remoteFD, nil, 0, d.conn.LocalAddr())
- if _, err := d.conn.Expect(tb.UDP{}, time.Second); err != nil {
- return fmt.Errorf("did not receive UDP packet as expected: %s", err)
+ d.dut.SendTo(t, d.remoteFD, nil, 0, d.conn.LocalAddr(t))
+ if _, err := d.conn.Expect(t, testbench.UDP{}, time.Second); err != nil {
+ t.Fatalf("did not receive UDP packet as expected: %s", err)
}
- return nil
}
-func testSockOpt(_ context.Context, d testData) error {
+func testSockOpt(_ context.Context, t *testing.T, d testData) {
// Check that there's no pending error on the clean socket.
- if errno := syscall.Errno(d.dut.GetSockOptInt(d.cleanFD, unix.SOL_SOCKET, unix.SO_ERROR)); errno != syscall.Errno(0) {
- return fmt.Errorf("unexpected error (%[1]d) %[1]v on clean socket", errno)
+ if errno := syscall.Errno(d.dut.GetSockOptInt(t, d.cleanFD, unix.SOL_SOCKET, unix.SO_ERROR)); errno != syscall.Errno(0) {
+ t.Fatalf("unexpected error (%[1]d) %[1]v on clean socket", errno)
}
- if errno := syscall.Errno(d.dut.GetSockOptInt(d.remoteFD, unix.SOL_SOCKET, unix.SO_ERROR)); errno != d.wantErrno {
- return fmt.Errorf("SO_ERROR sockopt after ICMP error is (%[1]d) %[1]v, expected (%[2]d) %[2]v", errno, d.wantErrno)
+ if errno := syscall.Errno(d.dut.GetSockOptInt(t, d.remoteFD, unix.SOL_SOCKET, unix.SO_ERROR)); errno != d.wantErrno {
+ t.Fatalf("SO_ERROR sockopt after ICMP error is (%[1]d) %[1]v, expected (%[2]d) %[2]v", errno, d.wantErrno)
}
// Check that after clearing socket error, sending doesn't fail.
- d.dut.SendTo(d.remoteFD, nil, 0, d.conn.LocalAddr())
- if _, err := d.conn.Expect(tb.UDP{}, time.Second); err != nil {
- return fmt.Errorf("did not receive UDP packet as expected: %s", err)
+ d.dut.SendTo(t, d.remoteFD, nil, 0, d.conn.LocalAddr(t))
+ if _, err := d.conn.Expect(t, testbench.UDP{}, time.Second); err != nil {
+ t.Fatalf("did not receive UDP packet as expected: %s", err)
}
- return nil
}
// TestUDPICMPErrorPropagation tests that ICMP error messages in response to
@@ -223,34 +224,32 @@ func TestUDPICMPErrorPropagation(t *testing.T) {
errorDetection{"SockOpt", false, testSockOpt},
} {
t.Run(fmt.Sprintf("%s/%s/%s", connect, icmpErr, errDetect.name), func(t *testing.T) {
- dut := tb.NewDUT(t)
+ dut := testbench.NewDUT(t)
defer dut.TearDown()
- remoteFD, remotePort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP("0.0.0.0"))
- defer dut.Close(remoteFD)
+ remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero)
+ defer dut.Close(t, remoteFD)
// Create a second, clean socket on the DUT to ensure that the ICMP
// error messages only affect the sockets they are intended for.
- cleanFD, cleanPort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP("0.0.0.0"))
- defer dut.Close(cleanFD)
+ cleanFD, cleanPort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero)
+ defer dut.Close(t, cleanFD)
- conn := tb.NewUDPIPv4(t, tb.UDP{DstPort: &remotePort}, tb.UDP{SrcPort: &remotePort})
- defer conn.Close()
+ conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
+ defer conn.Close(t)
if connect {
- dut.Connect(remoteFD, conn.LocalAddr())
- dut.Connect(cleanFD, conn.LocalAddr())
+ dut.Connect(t, remoteFD, conn.LocalAddr(t))
+ dut.Connect(t, cleanFD, conn.LocalAddr(t))
}
- dut.SendTo(remoteFD, nil, 0, conn.LocalAddr())
- udp, err := conn.Expect(tb.UDP{}, time.Second)
+ dut.SendTo(t, remoteFD, nil, 0, conn.LocalAddr(t))
+ udp, err := conn.Expect(t, testbench.UDP{}, time.Second)
if err != nil {
t.Fatalf("did not receive message from DUT: %s", err)
}
- if err := sendICMPError(&conn, icmpErr, udp); err != nil {
- t.Fatal(err)
- }
+ sendICMPError(t, &conn, icmpErr, udp)
errDetectConn := &conn
if errDetect.useValidConn {
@@ -258,15 +257,13 @@ func TestUDPICMPErrorPropagation(t *testing.T) {
// involved in the generation of the ICMP error. As such,
// interactions between it and the the DUT should be independent of
// the ICMP error at least at the port level.
- connClean := tb.NewUDPIPv4(t, tb.UDP{DstPort: &remotePort}, tb.UDP{SrcPort: &remotePort})
- defer connClean.Close()
+ connClean := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
+ defer connClean.Close(t)
errDetectConn = &connClean
}
- if err := errDetect.f(context.Background(), testData{&dut, errDetectConn, remoteFD, remotePort, cleanFD, cleanPort, wantErrno}); err != nil {
- t.Fatal(err)
- }
+ errDetect.f(context.Background(), t, testData{&dut, errDetectConn, remoteFD, remotePort, cleanFD, cleanPort, wantErrno})
})
}
}
@@ -281,27 +278,27 @@ func TestICMPErrorDuringUDPRecv(t *testing.T) {
wantErrno := wantErrno(connect, icmpErr)
t.Run(fmt.Sprintf("%s/%s", connect, icmpErr), func(t *testing.T) {
- dut := tb.NewDUT(t)
+ dut := testbench.NewDUT(t)
defer dut.TearDown()
- remoteFD, remotePort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP("0.0.0.0"))
- defer dut.Close(remoteFD)
+ remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero)
+ defer dut.Close(t, remoteFD)
// Create a second, clean socket on the DUT to ensure that the ICMP
// error messages only affect the sockets they are intended for.
- cleanFD, cleanPort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP("0.0.0.0"))
- defer dut.Close(cleanFD)
+ cleanFD, cleanPort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero)
+ defer dut.Close(t, cleanFD)
- conn := tb.NewUDPIPv4(t, tb.UDP{DstPort: &remotePort}, tb.UDP{SrcPort: &remotePort})
- defer conn.Close()
+ conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
+ defer conn.Close(t)
if connect {
- dut.Connect(remoteFD, conn.LocalAddr())
- dut.Connect(cleanFD, conn.LocalAddr())
+ dut.Connect(t, remoteFD, conn.LocalAddr(t))
+ dut.Connect(t, cleanFD, conn.LocalAddr(t))
}
- dut.SendTo(remoteFD, nil, 0, conn.LocalAddr())
- udp, err := conn.Expect(tb.UDP{}, time.Second)
+ dut.SendTo(t, remoteFD, nil, 0, conn.LocalAddr(t))
+ udp, err := conn.Expect(t, testbench.UDP{}, time.Second)
if err != nil {
t.Fatalf("did not receive message from DUT: %s", err)
}
@@ -315,7 +312,7 @@ func TestICMPErrorDuringUDPRecv(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
- ret, _, err := dut.RecvWithErrno(ctx, remoteFD, 100, 0)
+ ret, _, err := dut.RecvWithErrno(ctx, t, remoteFD, 100, 0)
if ret != -1 {
t.Errorf("recv during ICMP error succeeded unexpectedly, expected (%[1]d) %[1]v", wantErrno)
return
@@ -329,7 +326,7 @@ func TestICMPErrorDuringUDPRecv(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
- if ret, _, err := dut.RecvWithErrno(ctx, remoteFD, 100, 0); ret == -1 {
+ if ret, _, err := dut.RecvWithErrno(ctx, t, remoteFD, 100, 0); ret == -1 {
t.Errorf("recv after ICMP error failed with (%[1]d) %[1]", err)
}
}()
@@ -340,7 +337,7 @@ func TestICMPErrorDuringUDPRecv(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
- if ret, _, err := dut.RecvWithErrno(ctx, cleanFD, 100, 0); ret == -1 {
+ if ret, _, err := dut.RecvWithErrno(ctx, t, cleanFD, 100, 0); ret == -1 {
t.Errorf("recv on clean socket failed with (%[1]d) %[1]", err)
}
}()
@@ -351,12 +348,10 @@ func TestICMPErrorDuringUDPRecv(t *testing.T) {
// alternative is available.
time.Sleep(2 * time.Second)
- if err := sendICMPError(&conn, icmpErr, udp); err != nil {
- t.Fatal(err)
- }
+ sendICMPError(t, &conn, icmpErr, udp)
- conn.Send(tb.UDP{DstPort: &cleanPort})
- conn.Send(tb.UDP{})
+ conn.Send(t, testbench.UDP{DstPort: &cleanPort})
+ conn.Send(t, testbench.UDP{})
wg.Wait()
})
}
diff --git a/test/packetimpact/tests/udp_recv_mcast_bcast_test.go b/test/packetimpact/tests/udp_recv_mcast_bcast_test.go
new file mode 100644
index 000000000..526173969
--- /dev/null
+++ b/test/packetimpact/tests/udp_recv_mcast_bcast_test.go
@@ -0,0 +1,110 @@
+// Copyright 2020 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 udp_recv_mcast_bcast_test
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "net"
+ "syscall"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+func init() {
+ testbench.RegisterFlags(flag.CommandLine)
+}
+
+func TestUDPRecvMcastBcast(t *testing.T) {
+ subnetBcastAddr := broadcastAddr(net.ParseIP(testbench.RemoteIPv4), net.CIDRMask(testbench.IPv4PrefixLength, 32))
+
+ for _, v := range []struct {
+ bound, to net.IP
+ }{
+ {bound: net.IPv4zero, to: subnetBcastAddr},
+ {bound: net.IPv4zero, to: net.IPv4bcast},
+ {bound: net.IPv4zero, to: net.IPv4allsys},
+
+ {bound: subnetBcastAddr, to: subnetBcastAddr},
+ {bound: subnetBcastAddr, to: net.IPv4bcast},
+
+ {bound: net.IPv4bcast, to: net.IPv4bcast},
+ {bound: net.IPv4allsys, to: net.IPv4allsys},
+ } {
+ t.Run(fmt.Sprintf("bound=%s,to=%s", v.bound, v.to), func(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, v.bound)
+ defer dut.Close(t, boundFD)
+ conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
+ defer conn.Close(t)
+
+ payload := testbench.GenerateRandomPayload(t, 1<<10 /* 1 KiB */)
+ conn.SendIP(
+ t,
+ testbench.IPv4{DstAddr: testbench.Address(tcpip.Address(v.to.To4()))},
+ testbench.UDP{},
+ &testbench.Payload{Bytes: payload},
+ )
+ got, want := dut.Recv(t, boundFD, int32(len(payload)+1), 0), payload
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff)
+ }
+ })
+ }
+}
+
+func TestUDPDoesntRecvMcastBcastOnUnicastAddr(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP(testbench.RemoteIPv4))
+ dut.SetSockOptTimeval(t, boundFD, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &unix.Timeval{Sec: 1, Usec: 0})
+ defer dut.Close(t, boundFD)
+ conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
+ defer conn.Close(t)
+
+ for _, to := range []net.IP{
+ broadcastAddr(net.ParseIP(testbench.RemoteIPv4), net.CIDRMask(testbench.IPv4PrefixLength, 32)),
+ net.IPv4(255, 255, 255, 255),
+ net.IPv4(224, 0, 0, 1),
+ } {
+ t.Run(fmt.Sprint("to=%s", to), func(t *testing.T) {
+ payload := testbench.GenerateRandomPayload(t, 1<<10 /* 1 KiB */)
+ conn.SendIP(
+ t,
+ testbench.IPv4{DstAddr: testbench.Address(tcpip.Address(to.To4()))},
+ testbench.UDP{},
+ &testbench.Payload{Bytes: payload},
+ )
+ ret, payload, errno := dut.RecvWithErrno(context.Background(), t, boundFD, 100, 0)
+ if errno != syscall.EAGAIN || errno != syscall.EWOULDBLOCK {
+ t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, payload, errno)
+ }
+ })
+ }
+}
+
+func broadcastAddr(ip net.IP, mask net.IPMask) net.IP {
+ ip4 := ip.To4()
+ for i := range ip4 {
+ ip4[i] |= ^mask[i]
+ }
+ return ip4
+}
diff --git a/test/packetimpact/tests/udp_recv_multicast_test.go b/test/packetimpact/tests/udp_recv_multicast_test.go
deleted file mode 100644
index 0bae18ba3..000000000
--- a/test/packetimpact/tests/udp_recv_multicast_test.go
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2020 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 udp_recv_multicast_test
-
-import (
- "flag"
- "net"
- "testing"
-
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- tb.RegisterFlags(flag.CommandLine)
-}
-
-func TestUDPRecvMulticast(t *testing.T) {
- dut := tb.NewDUT(t)
- defer dut.TearDown()
- boundFD, remotePort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP("0.0.0.0"))
- defer dut.Close(boundFD)
- conn := tb.NewUDPIPv4(t, tb.UDP{DstPort: &remotePort}, tb.UDP{SrcPort: &remotePort})
- defer conn.Close()
- frame := conn.CreateFrame(&tb.UDP{}, &tb.Payload{Bytes: []byte("hello world")})
- frame[1].(*tb.IPv4).DstAddr = tb.Address(tcpip.Address(net.ParseIP("224.0.0.1").To4()))
- conn.SendFrame(frame)
- dut.Recv(boundFD, 100, 0)
-}
diff --git a/test/packetimpact/tests/udp_send_recv_dgram_test.go b/test/packetimpact/tests/udp_send_recv_dgram_test.go
index 350875a6f..91b967400 100644
--- a/test/packetimpact/tests/udp_send_recv_dgram_test.go
+++ b/test/packetimpact/tests/udp_send_recv_dgram_test.go
@@ -16,85 +16,88 @@ package udp_send_recv_dgram_test
import (
"flag"
- "math/rand"
"net"
"testing"
"time"
+ "github.com/google/go-cmp/cmp"
"golang.org/x/sys/unix"
- tb "gvisor.dev/gvisor/test/packetimpact/testbench"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
)
func init() {
- tb.RegisterFlags(flag.CommandLine)
+ testbench.RegisterFlags(flag.CommandLine)
}
-func generateRandomPayload(t *testing.T, n int) string {
- t.Helper()
- buf := make([]byte, n)
- if _, err := rand.Read(buf); err != nil {
- t.Fatalf("rand.Read(buf) failed: %s", err)
- }
- return string(buf)
+type udpConn interface {
+ Send(*testing.T, testbench.UDP, ...testbench.Layer)
+ ExpectData(*testing.T, testbench.UDP, testbench.Payload, time.Duration) (testbench.Layers, error)
+ Drain(*testing.T)
+ Close(*testing.T)
}
-func TestUDPRecv(t *testing.T) {
- dut := tb.NewDUT(t)
+func TestUDP(t *testing.T) {
+ dut := testbench.NewDUT(t)
defer dut.TearDown()
- boundFD, remotePort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP("0.0.0.0"))
- defer dut.Close(boundFD)
- conn := tb.NewUDPIPv4(t, tb.UDP{DstPort: &remotePort}, tb.UDP{SrcPort: &remotePort})
- defer conn.Close()
- testCases := []struct {
- name string
- payload string
- }{
- {"emptypayload", ""},
- {"small payload", "hello world"},
- {"1kPayload", generateRandomPayload(t, 1<<10)},
- // Even though UDP allows larger dgrams we don't test it here as
- // they need to be fragmented and written out as individual
- // frames.
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- frame := conn.CreateFrame(&tb.UDP{}, &tb.Payload{Bytes: []byte(tc.payload)})
- conn.SendFrame(frame)
- if got, want := string(dut.Recv(boundFD, int32(len(tc.payload)), 0)), tc.payload; got != want {
- t.Fatalf("received payload does not match sent payload got: %s, want: %s", got, want)
+ for _, isIPv4 := range []bool{true, false} {
+ ipVersionName := "IPv6"
+ if isIPv4 {
+ ipVersionName = "IPv4"
+ }
+ t.Run(ipVersionName, func(t *testing.T) {
+ var addr string
+ if isIPv4 {
+ addr = testbench.RemoteIPv4
+ } else {
+ addr = testbench.RemoteIPv6
}
- })
- }
-}
+ boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP(addr))
+ defer dut.Close(t, boundFD)
-func TestUDPSend(t *testing.T) {
- dut := tb.NewDUT(t)
- defer dut.TearDown()
- boundFD, remotePort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP("0.0.0.0"))
- defer dut.Close(boundFD)
- conn := tb.NewUDPIPv4(t, tb.UDP{DstPort: &remotePort}, tb.UDP{SrcPort: &remotePort})
- defer conn.Close()
+ var conn udpConn
+ var localAddr unix.Sockaddr
+ if isIPv4 {
+ v4Conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
+ localAddr = v4Conn.LocalAddr(t)
+ conn = &v4Conn
+ } else {
+ v6Conn := testbench.NewUDPIPv6(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
+ localAddr = v6Conn.LocalAddr(t)
+ conn = &v6Conn
+ }
+ defer conn.Close(t)
- testCases := []struct {
- name string
- payload string
- }{
- {"emptypayload", ""},
- {"small payload", "hello world"},
- {"1kPayload", generateRandomPayload(t, 1<<10)},
- // Even though UDP allows larger dgrams we don't test it here as
- // they need to be fragmented and written out as individual
- // frames.
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- conn.Drain()
- if got, want := int(dut.SendTo(boundFD, []byte(tc.payload), 0, conn.LocalAddr())), len(tc.payload); got != want {
- t.Fatalf("short write got: %d, want: %d", got, want)
+ testCases := []struct {
+ name string
+ payload []byte
+ }{
+ {"emptypayload", nil},
+ {"small payload", []byte("hello world")},
+ {"1kPayload", testbench.GenerateRandomPayload(t, 1<<10)},
+ // Even though UDP allows larger dgrams we don't test it here as
+ // they need to be fragmented and written out as individual
+ // frames.
}
- if _, err := conn.ExpectData(tb.UDP{SrcPort: &remotePort}, tb.Payload{Bytes: []byte(tc.payload)}, 1*time.Second); err != nil {
- t.Fatal(err)
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Run("Send", func(t *testing.T) {
+ conn.Send(t, testbench.UDP{}, &testbench.Payload{Bytes: tc.payload})
+ got, want := dut.Recv(t, boundFD, int32(len(tc.payload)+1), 0), tc.payload
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Fatalf("received payload does not match sent payload, diff (-want, +got):\n%s", diff)
+ }
+ })
+ t.Run("Recv", func(t *testing.T) {
+ conn.Drain(t)
+ if got, want := int(dut.SendTo(t, boundFD, tc.payload, 0, localAddr)), len(tc.payload); got != want {
+ t.Fatalf("short write got: %d, want: %d", got, want)
+ }
+ if _, err := conn.ExpectData(t, testbench.UDP{SrcPort: &remotePort}, testbench.Payload{Bytes: tc.payload}, time.Second); err != nil {
+ t.Fatal(err)
+ }
+ })
+ })
}
})
}
diff --git a/test/perf/linux/futex_benchmark.cc b/test/perf/linux/futex_benchmark.cc
index 241f39896..e686041c9 100644
--- a/test/perf/linux/futex_benchmark.cc
+++ b/test/perf/linux/futex_benchmark.cc
@@ -84,9 +84,9 @@ BENCHMARK(BM_FutexWaitNop)->MinTime(5);
// changes, such that it always times out. Timeout overhead can be estimated by
// timer overruns for short timeouts.
void BM_FutexWaitMonotonicTimeout(benchmark::State& state) {
- const int timeout_ns = state.range(0);
+ const absl::Duration timeout = absl::Nanoseconds(state.range(0));
std::atomic<int32_t> v(0);
- auto ts = absl::ToTimespec(absl::Nanoseconds(timeout_ns));
+ auto ts = absl::ToTimespec(timeout);
for (auto _ : state) {
TEST_PCHECK(FutexWaitMonotonicTimeout(&v, 0, &ts) == -1 &&
diff --git a/test/root/BUILD b/test/root/BUILD
index 639e293e3..a9130b34f 100644
--- a/test/root/BUILD
+++ b/test/root/BUILD
@@ -33,6 +33,7 @@ go_test(
],
visibility = ["//:sandbox"],
deps = [
+ "//pkg/cleanup",
"//pkg/test/criutil",
"//pkg/test/dockerutil",
"//pkg/test/testutil",
@@ -40,7 +41,7 @@ go_test(
"//runsc/container",
"//runsc/specutils",
"@com_github_cenkalti_backoff//:go_default_library",
- "@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
+ "@com_github_opencontainers_runtime_spec//specs-go:go_default_library",
"@com_github_syndtr_gocapability//capability:go_default_library",
"@org_golang_x_sys//unix:go_default_library",
],
@@ -50,8 +51,5 @@ vm_test(
name = "root_vm_test",
size = "large",
shard_count = 1,
- targets = [
- "//tools/installers:shim",
- ":root_test",
- ],
+ targets = [":root_test"],
)
diff --git a/test/root/cgroup_test.go b/test/root/cgroup_test.go
index d0634b5c3..a26b83081 100644
--- a/test/root/cgroup_test.go
+++ b/test/root/cgroup_test.go
@@ -16,6 +16,7 @@ package root
import (
"bufio"
+ "context"
"fmt"
"io/ioutil"
"os"
@@ -56,25 +57,24 @@ func verifyPid(pid int, path string) error {
return fmt.Errorf("got: %v, want: %d", gots, pid)
}
-func TestMemCGroup(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+func TestMemCgroup(t *testing.T) {
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// Start a new container and allocate the specified about of memory.
allocMemSize := 128 << 20
allocMemLimit := 2 * allocMemSize
- if err := d.Spawn(dockerutil.RunOpts{
- Image: "basic/python",
- Memory: allocMemLimit / 1024, // Must be in Kb.
- }, "python", "-c", fmt.Sprintf("import time; s = 'a' * %d; time.sleep(100)", allocMemSize)); err != nil {
+
+ if err := d.Spawn(ctx, dockerutil.RunOpts{
+ Image: "basic/ubuntu",
+ Memory: allocMemLimit, // Must be in bytes.
+ }, "python3", "-c", fmt.Sprintf("import time; s = 'a' * %d; time.sleep(100)", allocMemSize)); err != nil {
t.Fatalf("docker run failed: %v", err)
}
// Extract the ID to lookup the cgroup.
- gid, err := d.ID()
- if err != nil {
- t.Fatalf("Docker.ID() failed: %v", err)
- }
+ gid := d.ID()
t.Logf("cgroup ID: %s", gid)
// Wait when the container will allocate memory.
@@ -127,8 +127,9 @@ func TestMemCGroup(t *testing.T) {
// TestCgroup sets cgroup options and checks that cgroup was properly configured.
func TestCgroup(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// This is not a comprehensive list of attributes.
//
@@ -137,94 +138,133 @@ func TestCgroup(t *testing.T) {
// are often run on a single core virtual machine, and there is only a single
// CPU available in our current set, and every container's set.
attrs := []struct {
- arg string
+ field string
+ value int64
ctrl string
file string
want string
skipIfNotFound bool
}{
{
- arg: "--cpu-shares=1000",
- ctrl: "cpu",
- file: "cpu.shares",
- want: "1000",
+ field: "cpu-shares",
+ value: 1000,
+ ctrl: "cpu",
+ file: "cpu.shares",
+ want: "1000",
},
{
- arg: "--cpu-period=2000",
- ctrl: "cpu",
- file: "cpu.cfs_period_us",
- want: "2000",
+ field: "cpu-period",
+ value: 2000,
+ ctrl: "cpu",
+ file: "cpu.cfs_period_us",
+ want: "2000",
},
{
- arg: "--cpu-quota=3000",
- ctrl: "cpu",
- file: "cpu.cfs_quota_us",
- want: "3000",
+ field: "cpu-quota",
+ value: 3000,
+ ctrl: "cpu",
+ file: "cpu.cfs_quota_us",
+ want: "3000",
},
{
- arg: "--kernel-memory=100MB",
- ctrl: "memory",
- file: "memory.kmem.limit_in_bytes",
- want: "104857600",
+ field: "kernel-memory",
+ value: 100 << 20,
+ ctrl: "memory",
+ file: "memory.kmem.limit_in_bytes",
+ want: "104857600",
},
{
- arg: "--memory=1GB",
- ctrl: "memory",
- file: "memory.limit_in_bytes",
- want: "1073741824",
+ field: "memory",
+ value: 1 << 30,
+ ctrl: "memory",
+ file: "memory.limit_in_bytes",
+ want: "1073741824",
},
{
- arg: "--memory-reservation=500MB",
- ctrl: "memory",
- file: "memory.soft_limit_in_bytes",
- want: "524288000",
+ field: "memory-reservation",
+ value: 500 << 20,
+ ctrl: "memory",
+ file: "memory.soft_limit_in_bytes",
+ want: "524288000",
},
{
- arg: "--memory-swap=2GB",
+ field: "memory-swap",
+ value: 2 << 30,
ctrl: "memory",
file: "memory.memsw.limit_in_bytes",
want: "2147483648",
skipIfNotFound: true, // swap may be disabled on the machine.
},
{
- arg: "--memory-swappiness=5",
- ctrl: "memory",
- file: "memory.swappiness",
- want: "5",
+ field: "memory-swappiness",
+ value: 5,
+ ctrl: "memory",
+ file: "memory.swappiness",
+ want: "5",
},
{
- arg: "--blkio-weight=750",
+ field: "blkio-weight",
+ value: 750,
ctrl: "blkio",
file: "blkio.weight",
want: "750",
skipIfNotFound: true, // blkio groups may not be available.
},
{
- arg: "--pids-limit=1000",
- ctrl: "pids",
- file: "pids.max",
- want: "1000",
+ field: "pids-limit",
+ value: 1000,
+ ctrl: "pids",
+ file: "pids.max",
+ want: "1000",
},
}
- args := make([]string, 0, len(attrs))
+ // Make configs.
+ conf, hostconf, _ := d.ConfigsFrom(dockerutil.RunOpts{
+ Image: "basic/alpine",
+ }, "sleep", "10000")
+
+ // Add Cgroup arguments to configs.
for _, attr := range attrs {
- args = append(args, attr.arg)
+ switch attr.field {
+ case "cpu-shares":
+ hostconf.Resources.CPUShares = attr.value
+ case "cpu-period":
+ hostconf.Resources.CPUPeriod = attr.value
+ case "cpu-quota":
+ hostconf.Resources.CPUQuota = attr.value
+ case "kernel-memory":
+ hostconf.Resources.KernelMemory = attr.value
+ case "memory":
+ hostconf.Resources.Memory = attr.value
+ case "memory-reservation":
+ hostconf.Resources.MemoryReservation = attr.value
+ case "memory-swap":
+ hostconf.Resources.MemorySwap = attr.value
+ case "memory-swappiness":
+ val := attr.value
+ hostconf.Resources.MemorySwappiness = &val
+ case "blkio-weight":
+ hostconf.Resources.BlkioWeight = uint16(attr.value)
+ case "pids-limit":
+ val := attr.value
+ hostconf.Resources.PidsLimit = &val
+
+ }
}
- // Start the container.
- if err := d.Spawn(dockerutil.RunOpts{
- Image: "basic/alpine",
- Extra: args, // Cgroup arguments.
- }, "sleep", "10000"); err != nil {
- t.Fatalf("docker run failed: %v", err)
+ // Create container.
+ if err := d.CreateFrom(ctx, conf, hostconf, nil); err != nil {
+ t.Fatalf("create failed with: %v", err)
}
- // Lookup the relevant cgroup ID.
- gid, err := d.ID()
- if err != nil {
- t.Fatalf("Docker.ID() failed: %v", err)
+ // Start container.
+ if err := d.Start(ctx); err != nil {
+ t.Fatalf("start failed with: %v", err)
}
+
+ // Lookup the relevant cgroup ID.
+ gid := d.ID()
t.Logf("cgroup ID: %s", gid)
// Check list of attributes defined above.
@@ -239,7 +279,7 @@ func TestCgroup(t *testing.T) {
t.Fatalf("failed to read %q: %v", path, err)
}
if got := strings.TrimSpace(string(out)); got != attr.want {
- t.Errorf("arg: %q, cgroup attribute %s/%s, got: %q, want: %q", attr.arg, attr.ctrl, attr.file, got, attr.want)
+ t.Errorf("field: %q, cgroup attribute %s/%s, got: %q, want: %q", attr.field, attr.ctrl, attr.file, got, attr.want)
}
}
@@ -257,7 +297,7 @@ func TestCgroup(t *testing.T) {
"pids",
"systemd",
}
- pid, err := d.SandboxPid()
+ pid, err := d.SandboxPid(ctx)
if err != nil {
t.Fatalf("SandboxPid: %v", err)
}
@@ -269,29 +309,34 @@ func TestCgroup(t *testing.T) {
}
}
-// TestCgroup sets cgroup options and checks that cgroup was properly configured.
+// TestCgroupParent sets the "CgroupParent" option and checks that the child and parent's
+// cgroups are created correctly relative to each other.
func TestCgroupParent(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
// Construct a known cgroup name.
parent := testutil.RandomID("runsc-")
- if err := d.Spawn(dockerutil.RunOpts{
+ conf, hostconf, _ := d.ConfigsFrom(dockerutil.RunOpts{
Image: "basic/alpine",
- Extra: []string{fmt.Sprintf("--cgroup-parent=%s", parent)},
- }, "sleep", "10000"); err != nil {
- t.Fatalf("docker run failed: %v", err)
+ }, "sleep", "10000")
+ hostconf.Resources.CgroupParent = parent
+
+ if err := d.CreateFrom(ctx, conf, hostconf, nil); err != nil {
+ t.Fatalf("create failed with: %v", err)
}
- // Extract the ID to look up the cgroup.
- gid, err := d.ID()
- if err != nil {
- t.Fatalf("Docker.ID() failed: %v", err)
+ if err := d.Start(ctx); err != nil {
+ t.Fatalf("start failed with: %v", err)
}
+
+ // Extract the ID to look up the cgroup.
+ gid := d.ID()
t.Logf("cgroup ID: %s", gid)
// Check that sandbox is inside cgroup.
- pid, err := d.SandboxPid()
+ pid, err := d.SandboxPid(ctx)
if err != nil {
t.Fatalf("SandboxPid: %v", err)
}
diff --git a/test/root/chroot_test.go b/test/root/chroot_test.go
index a306132a4..58fcd6f08 100644
--- a/test/root/chroot_test.go
+++ b/test/root/chroot_test.go
@@ -16,6 +16,7 @@
package root
import (
+ "context"
"fmt"
"io/ioutil"
"os/exec"
@@ -30,16 +31,17 @@ import (
// TestChroot verifies that the sandbox is chroot'd and that mounts are cleaned
// up after the sandbox is destroyed.
func TestChroot(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
- if err := d.Spawn(dockerutil.RunOpts{
+ if err := d.Spawn(ctx, dockerutil.RunOpts{
Image: "basic/alpine",
}, "sleep", "10000"); err != nil {
t.Fatalf("docker run failed: %v", err)
}
- pid, err := d.SandboxPid()
+ pid, err := d.SandboxPid(ctx)
if err != nil {
t.Fatalf("Docker.SandboxPid(): %v", err)
}
@@ -75,14 +77,15 @@ func TestChroot(t *testing.T) {
t.Errorf("chroot got children %v, want %v", fi[0].Name(), "proc")
}
- d.CleanUp()
+ d.CleanUp(ctx)
}
func TestChrootGofer(t *testing.T) {
- d := dockerutil.MakeDocker(t)
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
- if err := d.Spawn(dockerutil.RunOpts{
+ if err := d.Spawn(ctx, dockerutil.RunOpts{
Image: "basic/alpine",
}, "sleep", "10000"); err != nil {
t.Fatalf("docker run failed: %v", err)
@@ -91,7 +94,7 @@ func TestChrootGofer(t *testing.T) {
// It's tricky to find gofers. Get sandbox PID first, then find parent. From
// parent get all immediate children, remove the sandbox, and everything else
// are gofers.
- sandPID, err := d.SandboxPid()
+ sandPID, err := d.SandboxPid(ctx)
if err != nil {
t.Fatalf("Docker.SandboxPid(): %v", err)
}
diff --git a/test/root/crictl_test.go b/test/root/crictl_test.go
index 85007dcce..df91fa0fe 100644
--- a/test/root/crictl_test.go
+++ b/test/root/crictl_test.go
@@ -20,25 +20,49 @@ import (
"fmt"
"io"
"io/ioutil"
- "log"
"net/http"
"os"
"os/exec"
"path"
- "path/filepath"
+ "regexp"
+ "strconv"
"strings"
+ "sync"
"testing"
"time"
+ "gvisor.dev/gvisor/pkg/cleanup"
"gvisor.dev/gvisor/pkg/test/criutil"
"gvisor.dev/gvisor/pkg/test/dockerutil"
"gvisor.dev/gvisor/pkg/test/testutil"
- "gvisor.dev/gvisor/runsc/specutils"
)
// Tests for crictl have to be run as root (rather than in a user namespace)
// because crictl creates named network namespaces in /var/run/netns/.
+// Sandbox returns a JSON config for a simple sandbox. Sandbox names must be
+// unique so different names should be used when running tests on the same
+// containerd instance.
+func Sandbox(name string) string {
+ // Sandbox is a default JSON config for a sandbox.
+ s := map[string]interface{}{
+ "metadata": map[string]string{
+ "name": name,
+ "namespace": "default",
+ "uid": testutil.RandomID(""),
+ },
+ "linux": map[string]string{},
+ "log_directory": "/tmp",
+ }
+
+ v, err := json.Marshal(s)
+ if err != nil {
+ // This shouldn't happen.
+ panic(err)
+ }
+ return string(v)
+}
+
// SimpleSpec returns a JSON config for a simple container that runs the
// specified command in the specified image.
func SimpleSpec(name, image string, cmd []string, extra map[string]interface{}) string {
@@ -49,7 +73,11 @@ func SimpleSpec(name, image string, cmd []string, extra map[string]interface{})
"image": map[string]string{
"image": testutil.ImageByName(image),
},
- "log_path": fmt.Sprintf("%s.log", name),
+ // Log files are not deleted after root tests are run. Log to random
+ // paths to ensure logs are fresh.
+ "log_path": fmt.Sprintf("%s.log", testutil.RandomID(name)),
+ "stdin": false,
+ "tty": false,
}
if len(cmd) > 0 { // Omit if empty.
s["command"] = cmd
@@ -65,44 +93,34 @@ func SimpleSpec(name, image string, cmd []string, extra map[string]interface{})
return string(v)
}
-// Sandbox is a default JSON config for a sandbox.
-var Sandbox = `{
- "metadata": {
- "name": "default-sandbox",
- "namespace": "default",
- "attempt": 1,
- "uid": "hdishd83djaidwnduwk28bcsb"
- },
- "linux": {
- },
- "log_directory": "/tmp"
-}
-`
-
// Httpd is a JSON config for an httpd container.
var Httpd = SimpleSpec("httpd", "basic/httpd", nil, nil)
// TestCrictlSanity refers to b/112433158.
func TestCrictlSanity(t *testing.T) {
- // Setup containerd and crictl.
- crictl, cleanup, err := setup(t)
- if err != nil {
- t.Fatalf("failed to setup crictl: %v", err)
- }
- defer cleanup()
- podID, contID, err := crictl.StartPodAndContainer("basic/httpd", Sandbox, Httpd)
- if err != nil {
- t.Fatalf("start failed: %v", err)
- }
-
- // Look for the httpd page.
- if err = httpGet(crictl, podID, "index.html"); err != nil {
- t.Fatalf("failed to get page: %v", err)
- }
-
- // Stop everything.
- if err := crictl.StopPodAndContainer(podID, contID); err != nil {
- t.Fatalf("stop failed: %v", err)
+ for _, version := range allVersions {
+ t.Run(version, func(t *testing.T) {
+ // Setup containerd and crictl.
+ crictl, cleanup, err := setup(t, version)
+ if err != nil {
+ t.Fatalf("failed to setup crictl: %v", err)
+ }
+ defer cleanup()
+ podID, contID, err := crictl.StartPodAndContainer(containerdRuntime, "basic/httpd", Sandbox("default"), Httpd)
+ if err != nil {
+ t.Fatalf("start failed: %v", err)
+ }
+
+ // Look for the httpd page.
+ if err = httpGet(crictl, podID, "index.html"); err != nil {
+ t.Fatalf("failed to get page: %v", err)
+ }
+
+ // Stop everything.
+ if err := crictl.StopPodAndContainer(podID, contID); err != nil {
+ t.Fatalf("stop failed: %v", err)
+ }
+ })
}
}
@@ -136,164 +154,224 @@ var HttpdMountPaths = SimpleSpec("httpd", "basic/httpd", nil, map[string]interfa
// TestMountPaths refers to b/117635704.
func TestMountPaths(t *testing.T) {
- // Setup containerd and crictl.
- crictl, cleanup, err := setup(t)
- if err != nil {
- t.Fatalf("failed to setup crictl: %v", err)
- }
- defer cleanup()
- podID, contID, err := crictl.StartPodAndContainer("basic/httpd", Sandbox, HttpdMountPaths)
- if err != nil {
- t.Fatalf("start failed: %v", err)
- }
-
- // Look for the directory available at /test.
- if err = httpGet(crictl, podID, "test"); err != nil {
- t.Fatalf("failed to get page: %v", err)
- }
-
- // Stop everything.
- if err := crictl.StopPodAndContainer(podID, contID); err != nil {
- t.Fatalf("stop failed: %v", err)
+ for _, version := range allVersions {
+ t.Run(version, func(t *testing.T) {
+ // Setup containerd and crictl.
+ crictl, cleanup, err := setup(t, version)
+ if err != nil {
+ t.Fatalf("failed to setup crictl: %v", err)
+ }
+ defer cleanup()
+ podID, contID, err := crictl.StartPodAndContainer(containerdRuntime, "basic/httpd", Sandbox("default"), HttpdMountPaths)
+ if err != nil {
+ t.Fatalf("start failed: %v", err)
+ }
+
+ // Look for the directory available at /test.
+ if err = httpGet(crictl, podID, "test"); err != nil {
+ t.Fatalf("failed to get page: %v", err)
+ }
+
+ // Stop everything.
+ if err := crictl.StopPodAndContainer(podID, contID); err != nil {
+ t.Fatalf("stop failed: %v", err)
+ }
+ })
}
}
// TestMountPaths refers to b/118728671.
func TestMountOverSymlinks(t *testing.T) {
- // Setup containerd and crictl.
- crictl, cleanup, err := setup(t)
- if err != nil {
- t.Fatalf("failed to setup crictl: %v", err)
- }
- defer cleanup()
-
- spec := SimpleSpec("busybox", "basic/resolv", []string{"sleep", "1000"}, nil)
- podID, contID, err := crictl.StartPodAndContainer("basic/resolv", Sandbox, spec)
- if err != nil {
- t.Fatalf("start failed: %v", err)
- }
-
- out, err := crictl.Exec(contID, "readlink", "/etc/resolv.conf")
- if err != nil {
- t.Fatalf("readlink failed: %v, out: %s", err, out)
- }
- if want := "/tmp/resolv.conf"; !strings.Contains(string(out), want) {
- t.Fatalf("/etc/resolv.conf is not pointing to %q: %q", want, string(out))
- }
-
- etc, err := crictl.Exec(contID, "cat", "/etc/resolv.conf")
- if err != nil {
- t.Fatalf("cat failed: %v, out: %s", err, etc)
- }
- tmp, err := crictl.Exec(contID, "cat", "/tmp/resolv.conf")
- if err != nil {
- t.Fatalf("cat failed: %v, out: %s", err, out)
- }
- if tmp != etc {
- t.Fatalf("file content doesn't match:\n\t/etc/resolv.conf: %s\n\t/tmp/resolv.conf: %s", string(etc), string(tmp))
- }
-
- // Stop everything.
- if err := crictl.StopPodAndContainer(podID, contID); err != nil {
- t.Fatalf("stop failed: %v", err)
+ for _, version := range allVersions {
+ t.Run(version, func(t *testing.T) {
+ // Setup containerd and crictl.
+ crictl, cleanup, err := setup(t, version)
+ if err != nil {
+ t.Fatalf("failed to setup crictl: %v", err)
+ }
+ defer cleanup()
+
+ spec := SimpleSpec("busybox", "basic/resolv", []string{"sleep", "1000"}, nil)
+ podID, contID, err := crictl.StartPodAndContainer(containerdRuntime, "basic/resolv", Sandbox("default"), spec)
+ if err != nil {
+ t.Fatalf("start failed: %v", err)
+ }
+
+ out, err := crictl.Exec(contID, "readlink", "/etc/resolv.conf")
+ if err != nil {
+ t.Fatalf("readlink failed: %v, out: %s", err, out)
+ }
+ if want := "/tmp/resolv.conf"; !strings.Contains(string(out), want) {
+ t.Fatalf("/etc/resolv.conf is not pointing to %q: %q", want, string(out))
+ }
+
+ etc, err := crictl.Exec(contID, "cat", "/etc/resolv.conf")
+ if err != nil {
+ t.Fatalf("cat failed: %v, out: %s", err, etc)
+ }
+ tmp, err := crictl.Exec(contID, "cat", "/tmp/resolv.conf")
+ if err != nil {
+ t.Fatalf("cat failed: %v, out: %s", err, out)
+ }
+ if tmp != etc {
+ t.Fatalf("file content doesn't match:\n\t/etc/resolv.conf: %s\n\t/tmp/resolv.conf: %s", string(etc), string(tmp))
+ }
+
+ // Stop everything.
+ if err := crictl.StopPodAndContainer(podID, contID); err != nil {
+ t.Fatalf("stop failed: %v", err)
+ }
+ })
}
}
// TestHomeDir tests that the HOME environment variable is set for
-// multi-containers.
+// Pod containers.
func TestHomeDir(t *testing.T) {
- // Setup containerd and crictl.
- crictl, cleanup, err := setup(t)
- if err != nil {
- t.Fatalf("failed to setup crictl: %v", err)
- }
- defer cleanup()
- contSpec := SimpleSpec("root", "basic/busybox", []string{"sleep", "1000"}, nil)
- podID, contID, err := crictl.StartPodAndContainer("basic/busybox", Sandbox, contSpec)
- if err != nil {
- t.Fatalf("start failed: %v", err)
- }
-
- t.Run("root container", func(t *testing.T) {
- out, err := crictl.Exec(contID, "sh", "-c", "echo $HOME")
- if err != nil {
- t.Fatalf("exec failed: %v, out: %s", err, out)
- }
- if got, want := strings.TrimSpace(string(out)), "/root"; got != want {
- t.Fatalf("Home directory invalid. Got %q, Want : %q", got, want)
- }
- })
-
- t.Run("sub-container", func(t *testing.T) {
- // Create a sub container in the same pod.
- subContSpec := SimpleSpec("subcontainer", "basic/busybox", []string{"sleep", "1000"}, nil)
- subContID, err := crictl.StartContainer(podID, "basic/busybox", Sandbox, subContSpec)
- if err != nil {
- t.Fatalf("start failed: %v", err)
- }
-
- out, err := crictl.Exec(subContID, "sh", "-c", "echo $HOME")
- if err != nil {
- t.Fatalf("exec failed: %v, out: %s", err, out)
- }
- if got, want := strings.TrimSpace(string(out)), "/root"; got != want {
- t.Fatalf("Home directory invalid. Got %q, Want: %q", got, want)
- }
-
- if err := crictl.StopContainer(subContID); err != nil {
- t.Fatalf("stop failed: %v", err)
- }
- })
-
- // Stop everything.
- if err := crictl.StopPodAndContainer(podID, contID); err != nil {
- t.Fatalf("stop failed: %v", err)
+ for _, version := range allVersions {
+ t.Run(version, func(t *testing.T) {
+ // Setup containerd and crictl.
+ crictl, cleanup, err := setup(t, version)
+ if err != nil {
+ t.Fatalf("failed to setup crictl: %v", err)
+ }
+ defer cleanup()
+
+ // Note that container ID returned here is a sub-container. All Pod
+ // containers are sub-containers. The root container of the sandbox is the
+ // pause container.
+ t.Run("sub-container", func(t *testing.T) {
+ contSpec := SimpleSpec("subcontainer", "basic/busybox", []string{"sh", "-c", "echo $HOME"}, nil)
+ podID, contID, err := crictl.StartPodAndContainer(containerdRuntime, "basic/busybox", Sandbox("subcont-sandbox"), contSpec)
+ if err != nil {
+ t.Fatalf("start failed: %v", err)
+ }
+
+ out, err := crictl.Logs(contID)
+ if err != nil {
+ t.Fatalf("failed retrieving container logs: %v, out: %s", err, out)
+ }
+ if got, want := strings.TrimSpace(string(out)), "/root"; got != want {
+ t.Fatalf("Home directory invalid. Got %q, Want : %q", got, want)
+ }
+
+ // Stop everything; note that the pod may have already stopped.
+ crictl.StopPodAndContainer(podID, contID)
+ })
+
+ // Tests that HOME is set for the exec process.
+ t.Run("exec", func(t *testing.T) {
+ contSpec := SimpleSpec("exec", "basic/busybox", []string{"sleep", "1000"}, nil)
+ podID, contID, err := crictl.StartPodAndContainer(containerdRuntime, "basic/busybox", Sandbox("exec-sandbox"), contSpec)
+ if err != nil {
+ t.Fatalf("start failed: %v", err)
+ }
+
+ out, err := crictl.Exec(contID, "sh", "-c", "echo $HOME")
+ if err != nil {
+ t.Fatalf("failed retrieving container logs: %v, out: %s", err, out)
+ }
+ if got, want := strings.TrimSpace(string(out)), "/root"; got != want {
+ t.Fatalf("Home directory invalid. Got %q, Want : %q", got, want)
+ }
+
+ // Stop everything.
+ if err := crictl.StopPodAndContainer(podID, contID); err != nil {
+ t.Fatalf("stop failed: %v", err)
+ }
+ })
+ })
}
-
}
-// containerdConfigTemplate is a .toml config for containerd. It contains a
-// formatting verb so the runtime field can be set via fmt.Sprintf.
-const containerdConfigTemplate = `
+const containerdRuntime = "runsc"
+
+const v1Template = `
disabled_plugins = ["restart"]
+[plugins.cri]
+ disable_tcp_service = true
[plugins.linux]
- runtime = "%s"
- runtime_root = "/tmp/test-containerd/runsc"
- shim = "/usr/local/bin/gvisor-containerd-shim"
+ shim = "%s"
shim_debug = true
-
-[plugins.cri.containerd.runtimes.runsc]
+[plugins.cri.containerd.runtimes.` + containerdRuntime + `]
runtime_type = "io.containerd.runtime.v1.linux"
runtime_engine = "%s"
+ runtime_root = "%s/root/runsc"
`
+const v2Template = `
+disabled_plugins = ["restart"]
+[plugins.cri]
+ disable_tcp_service = true
+[plugins.linux]
+ shim_debug = true
+[plugins.cri.containerd.runtimes.` + containerdRuntime + `]
+ runtime_type = "io.containerd.` + containerdRuntime + `.v1"
+[plugins.cri.containerd.runtimes.` + containerdRuntime + `.options]
+ TypeUrl = "io.containerd.` + containerdRuntime + `.v1.options"
+`
+
+const (
+ // v1 is the containerd API v1.
+ v1 string = "v1"
+
+ // v1 is the containerd API v21.
+ v2 string = "v2"
+)
+
+// allVersions is the set of known versions.
+var allVersions = []string{v1, v2}
+
// setup sets up before a test. Specifically it:
// * Creates directories and a socket for containerd to utilize.
// * Runs containerd and waits for it to reach a "ready" state for testing.
// * Returns a cleanup function that should be called at the end of the test.
-func setup(t *testing.T) (*criutil.Crictl, func(), error) {
- var cleanups []func()
- cleanupFunc := func() {
- for i := len(cleanups) - 1; i >= 0; i-- {
- cleanups[i]()
- }
- }
- cleanup := specutils.MakeCleanup(cleanupFunc)
- defer cleanup.Clean()
-
+func setup(t *testing.T, version string) (*criutil.Crictl, func(), error) {
// Create temporary containerd root and state directories, and a socket
// via which crictl and containerd communicate.
containerdRoot, err := ioutil.TempDir(testutil.TmpDir(), "containerd-root")
if err != nil {
t.Fatalf("failed to create containerd root: %v", err)
}
- cleanups = append(cleanups, func() { os.RemoveAll(containerdRoot) })
+ cu := cleanup.Make(func() { os.RemoveAll(containerdRoot) })
+ defer cu.Clean()
+ t.Logf("Using containerd root: %s", containerdRoot)
+
containerdState, err := ioutil.TempDir(testutil.TmpDir(), "containerd-state")
if err != nil {
t.Fatalf("failed to create containerd state: %v", err)
}
- cleanups = append(cleanups, func() { os.RemoveAll(containerdState) })
- sockAddr := filepath.Join(testutil.TmpDir(), "containerd-test.sock")
+ cu.Add(func() { os.RemoveAll(containerdState) })
+ t.Logf("Using containerd state: %s", containerdState)
+
+ sockDir, err := ioutil.TempDir(testutil.TmpDir(), "containerd-sock")
+ if err != nil {
+ t.Fatalf("failed to create containerd socket directory: %v", err)
+ }
+ cu.Add(func() { os.RemoveAll(sockDir) })
+ sockAddr := path.Join(sockDir, "test.sock")
+ t.Logf("Using containerd socket: %s", sockAddr)
+
+ // Extract the containerd version.
+ versionCmd := exec.Command(getContainerd(), "-v")
+ out, err := versionCmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("error extracting containerd version: %v (%s)", err, string(out))
+ }
+ r := regexp.MustCompile(" v([0-9]+)\\.([0-9]+)\\.([0-9+])")
+ vs := r.FindStringSubmatch(string(out))
+ if len(vs) != 4 {
+ t.Fatalf("error unexpected version string: %s", string(out))
+ }
+ major, err := strconv.ParseUint(vs[1], 10, 64)
+ if err != nil {
+ t.Fatalf("error parsing containerd major version: %v (%s)", err, string(out))
+ }
+ minor, err := strconv.ParseUint(vs[2], 10, 64)
+ if err != nil {
+ t.Fatalf("error parsing containerd minor version: %v (%s)", err, string(out))
+ }
+ t.Logf("Using containerd version: %d.%d", major, minor)
// We rewrite a configuration. This is based on the current docker
// configuration for the runtime under test.
@@ -301,27 +379,97 @@ func setup(t *testing.T) (*criutil.Crictl, func(), error) {
if err != nil {
t.Fatalf("error discovering runtime path: %v", err)
}
- config, configCleanup, err := testutil.WriteTmpFile("containerd-config", fmt.Sprintf(containerdConfigTemplate, runtime, runtime))
+ t.Logf("Using runtime: %v", runtime)
+
+ // Construct a PATH that includes the runtime directory. This is
+ // because the shims will be installed there, and containerd may infer
+ // the binary name and search the PATH.
+ runtimeDir := path.Dir(runtime)
+ modifiedPath := os.Getenv("PATH")
+ if modifiedPath != "" {
+ modifiedPath = ":" + modifiedPath // We prepend below.
+ }
+ modifiedPath = path.Dir(getContainerd()) + modifiedPath
+ modifiedPath = runtimeDir + ":" + modifiedPath
+ t.Logf("Using PATH: %v", modifiedPath)
+
+ var (
+ config string
+ runpArgs []string
+ )
+ switch version {
+ case v1:
+ // This is only supported less than 1.3.
+ if major > 1 || (major == 1 && minor >= 3) {
+ t.Skipf("skipping unsupported containerd (want less than 1.3, got %d.%d)", major, minor)
+ }
+
+ // We provide the shim, followed by the runtime, and then a
+ // temporary root directory.
+ config = fmt.Sprintf(v1Template, criutil.ResolvePath("gvisor-containerd-shim"), runtime, containerdRoot)
+ case v2:
+ // This is only supported past 1.2.
+ if major < 1 || (major == 1 && minor <= 1) {
+ t.Skipf("skipping incompatible containerd (want at least 1.2, got %d.%d)", major, minor)
+ }
+
+ // The runtime is provided via parameter. Note that the v2 shim
+ // binary name is always containerd-shim-* so we don't actually
+ // care about the docker runtime name.
+ config = v2Template
+ default:
+ t.Fatalf("unknown version: %d", version)
+ }
+ t.Logf("Using config: %s", config)
+
+ // Generate the configuration for the test.
+ configFile, configCleanup, err := testutil.WriteTmpFile("containerd-config", config)
if err != nil {
t.Fatalf("failed to write containerd config")
}
- cleanups = append(cleanups, configCleanup)
+ cu.Add(configCleanup)
// Start containerd.
- cmd := exec.Command(getContainerd(),
- "--config", config,
+ args := []string{
+ getContainerd(),
+ "--config", configFile,
"--log-level", "debug",
"--root", containerdRoot,
"--state", containerdState,
- "--address", sockAddr)
+ "--address", sockAddr,
+ }
+ t.Logf("Using args: %s", strings.Join(args, " "))
+ cmd := exec.Command(args[0], args[1:]...)
+ cmd.Env = append(os.Environ(), "PATH="+modifiedPath)
+
+ // Include output in logs.
+ stderrPipe, err := cmd.StderrPipe()
+ if err != nil {
+ t.Fatalf("failed to create stderr pipe: %v", err)
+ }
+ cu.Add(func() { stderrPipe.Close() })
+ stdoutPipe, err := cmd.StdoutPipe()
+ if err != nil {
+ t.Fatalf("failed to create stdout pipe: %v", err)
+ }
+ cu.Add(func() { stdoutPipe.Close() })
+ var (
+ wg sync.WaitGroup
+ stderr bytes.Buffer
+ stdout bytes.Buffer
+ )
startupR, startupW := io.Pipe()
- defer startupR.Close()
- defer startupW.Close()
- stderr := &bytes.Buffer{}
- stdout := &bytes.Buffer{}
- cmd.Stderr = io.MultiWriter(startupW, stderr)
- cmd.Stdout = io.MultiWriter(startupW, stdout)
- cleanups = append(cleanups, func() {
+ wg.Add(2)
+ go func() {
+ defer wg.Done()
+ io.Copy(io.MultiWriter(startupW, &stderr), stderrPipe)
+ }()
+ go func() {
+ defer wg.Done()
+ io.Copy(io.MultiWriter(startupW, &stdout), stdoutPipe)
+ }()
+ cu.Add(func() {
+ wg.Wait()
t.Logf("containerd stdout: %s", stdout.String())
t.Logf("containerd stderr: %s", stderr.String())
})
@@ -336,17 +484,20 @@ func setup(t *testing.T) (*criutil.Crictl, func(), error) {
t.Fatalf("failed to start containerd: %v", err)
}
+ // Discard all subsequent data.
+ go io.Copy(ioutil.Discard, startupR)
+
+ // Create the crictl interface.
+ cc := criutil.NewCrictl(t, sockAddr, runpArgs)
+ cu.Add(cc.CleanUp)
+
// Kill must be the last cleanup (as it will be executed first).
- cc := criutil.NewCrictl(t, sockAddr)
- cleanups = append(cleanups, func() {
- cc.CleanUp() // Remove tmp files, etc.
- if err := testutil.KillCommand(cmd); err != nil {
- log.Printf("error killing containerd: %v", err)
- }
+ cu.Add(func() {
+ // Best effort: ignore errors.
+ testutil.KillCommand(cmd)
})
- cleanup.Release()
- return cc, cleanupFunc, nil
+ return cc, cu.Release(), nil
}
// httpGet GETs the contents of a file served from a pod on port 80.
diff --git a/test/root/oom_score_adj_test.go b/test/root/oom_score_adj_test.go
index 9a3cecd97..4243eb59e 100644
--- a/test/root/oom_score_adj_test.go
+++ b/test/root/oom_score_adj_test.go
@@ -20,6 +20,7 @@ import (
"testing"
specs "github.com/opencontainers/runtime-spec/specs-go"
+ "gvisor.dev/gvisor/pkg/cleanup"
"gvisor.dev/gvisor/pkg/test/testutil"
"gvisor.dev/gvisor/runsc/container"
"gvisor.dev/gvisor/runsc/specutils"
@@ -324,40 +325,26 @@ func createSpecs(cmds ...[]string) ([]*specs.Spec, []string) {
}
func startContainers(t *testing.T, specs []*specs.Spec, ids []string) ([]*container.Container, func(), error) {
- var (
- containers []*container.Container
- cleanups []func()
- )
- cleanups = append(cleanups, func() {
- for _, c := range containers {
- c.Destroy()
- }
- })
- cleanupAll := func() {
- for _, c := range cleanups {
- c()
- }
- }
- localClean := specutils.MakeCleanup(cleanupAll)
- defer localClean.Clean()
+ var containers []*container.Container
// All containers must share the same root.
- rootDir, cleanup, err := testutil.SetupRootDir()
+ rootDir, clean, err := testutil.SetupRootDir()
if err != nil {
t.Fatalf("error creating root dir: %v", err)
}
- cleanups = append(cleanups, cleanup)
+ cu := cleanup.Make(clean)
+ defer cu.Clean()
// Point this to from the configuration.
conf := testutil.TestConfig(t)
conf.RootDir = rootDir
for i, spec := range specs {
- bundleDir, cleanup, err := testutil.SetupBundleDir(spec)
+ bundleDir, clean, err := testutil.SetupBundleDir(spec)
if err != nil {
return nil, nil, fmt.Errorf("error setting up bundle: %v", err)
}
- cleanups = append(cleanups, cleanup)
+ cu.Add(clean)
args := container.Args{
ID: ids[i],
@@ -375,6 +362,5 @@ func startContainers(t *testing.T, specs []*specs.Spec, ids []string) ([]*contai
}
}
- localClean.Release()
- return containers, cleanupAll, nil
+ return containers, cu.Release(), nil
}
diff --git a/test/runner/BUILD b/test/runner/BUILD
index 6833c9986..63c7ec83a 100644
--- a/test/runner/BUILD
+++ b/test/runner/BUILD
@@ -16,7 +16,8 @@ go_binary(
"//runsc/specutils",
"//test/runner/gtest",
"//test/uds",
- "@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
+ "@com_github_opencontainers_runtime_spec//specs-go:go_default_library",
+ "@com_github_syndtr_gocapability//capability:go_default_library",
"@org_golang_x_sys//unix:go_default_library",
],
)
diff --git a/test/runner/defs.bzl b/test/runner/defs.bzl
index 0a75b158f..c92392b35 100644
--- a/test/runner/defs.bzl
+++ b/test/runner/defs.bzl
@@ -60,7 +60,9 @@ def _syscall_test(
network = "none",
file_access = "exclusive",
overlay = False,
- add_uds_tree = False):
+ add_uds_tree = False,
+ vfs2 = False,
+ fuse = False):
# Prepend "runsc" to non-native platform names.
full_platform = platform if platform == "native" else "runsc_" + platform
@@ -70,6 +72,10 @@ def _syscall_test(
name += "_shared"
if overlay:
name += "_overlay"
+ if vfs2:
+ name += "_vfs2"
+ if fuse:
+ name += "_fuse"
if network != "none":
name += "_" + network + "net"
@@ -90,6 +96,7 @@ def _syscall_test(
# we figure out how to request ipv4 sockets on Guitar machines.
if network == "host":
tags.append("noguitar")
+ tags.append("block-network")
# Disable off-host networking.
tags.append("requires-net:loopback")
@@ -102,6 +109,8 @@ def _syscall_test(
"--file-access=" + file_access,
"--overlay=" + str(overlay),
"--add-uds-tree=" + str(add_uds_tree),
+ "--vfs2=" + str(vfs2),
+ "--fuse=" + str(fuse),
]
# Call the rule above.
@@ -123,6 +132,8 @@ def syscall_test(
add_overlay = False,
add_uds_tree = False,
add_hostinet = False,
+ vfs2 = False,
+ fuse = False,
tags = None):
"""syscall_test is a macro that will create targets for all platforms.
@@ -146,7 +157,7 @@ def syscall_test(
platform = "native",
use_tmpfs = False,
add_uds_tree = add_uds_tree,
- tags = tags,
+ tags = list(tags),
)
for (platform, platform_tags) in platforms.items():
@@ -160,18 +171,66 @@ def syscall_test(
tags = platform_tags + tags,
)
+ vfs2_tags = list(tags)
+ if vfs2:
+ # Add tag to easily run VFS2 tests with --test_tag_filters=vfs2
+ vfs2_tags.append("vfs2")
+
+ else:
+ # Don't automatically run tests tests not yet passing.
+ vfs2_tags.append("manual")
+ vfs2_tags.append("noguitar")
+ vfs2_tags.append("notap")
+
+ _syscall_test(
+ test = test,
+ shard_count = shard_count,
+ size = size,
+ platform = default_platform,
+ use_tmpfs = use_tmpfs,
+ add_uds_tree = add_uds_tree,
+ tags = platforms[default_platform] + vfs2_tags,
+ vfs2 = True,
+ )
+
+ if vfs2 and fuse:
+ _syscall_test(
+ test = test,
+ shard_count = shard_count,
+ size = size,
+ platform = default_platform,
+ use_tmpfs = use_tmpfs,
+ add_uds_tree = add_uds_tree,
+ tags = platforms[default_platform] + vfs2_tags,
+ vfs2 = True,
+ fuse = True,
+ )
+
+ # TODO(gvisor.dev/issue/1487): Enable VFS2 overlay tests.
if add_overlay:
_syscall_test(
test = test,
shard_count = shard_count,
size = size,
platform = default_platform,
- use_tmpfs = False, # overlay is adding a writable tmpfs on top of root.
+ use_tmpfs = use_tmpfs,
add_uds_tree = add_uds_tree,
tags = platforms[default_platform] + tags,
overlay = True,
)
+ if add_hostinet:
+ _syscall_test(
+ test = test,
+ shard_count = shard_count,
+ size = size,
+ platform = default_platform,
+ use_tmpfs = use_tmpfs,
+ network = "host",
+ add_uds_tree = add_uds_tree,
+ tags = platforms[default_platform] + tags,
+ )
+
if not use_tmpfs:
# Also test shared gofer access.
_syscall_test(
@@ -184,15 +243,14 @@ def syscall_test(
tags = platforms[default_platform] + tags,
file_access = "shared",
)
-
- if add_hostinet:
_syscall_test(
test = test,
shard_count = shard_count,
size = size,
platform = default_platform,
use_tmpfs = use_tmpfs,
- network = "host",
add_uds_tree = add_uds_tree,
- tags = platforms[default_platform] + tags,
+ tags = platforms[default_platform] + vfs2_tags,
+ file_access = "shared",
+ vfs2 = True,
)
diff --git a/test/runner/runner.go b/test/runner/runner.go
index e4f04cd2a..bc4b39cbb 100644
--- a/test/runner/runner.go
+++ b/test/runner/runner.go
@@ -30,6 +30,7 @@ import (
"time"
specs "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/syndtr/gocapability/capability"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/test/testutil"
@@ -46,6 +47,8 @@ var (
useTmpfs = flag.Bool("use-tmpfs", false, "mounts tmpfs for /tmp")
fileAccess = flag.String("file-access", "exclusive", "mounts root in exclusive or shared mode")
overlay = flag.Bool("overlay", false, "wrap filesystem mounts with writable tmpfs overlay")
+ vfs2 = flag.Bool("vfs2", false, "enable VFS2")
+ fuse = flag.Bool("fuse", false, "enable FUSE")
parallel = flag.Bool("parallel", false, "run tests in parallel")
runscPath = flag.String("runsc", "", "path to runsc binary")
@@ -103,6 +106,13 @@ func runTestCaseNative(testBin string, tc gtest.TestCase, t *testing.T) {
cmd.Env = env
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
+
+ if specutils.HasCapabilities(capability.CAP_NET_ADMIN) {
+ cmd.SysProcAttr = &syscall.SysProcAttr{
+ Cloneflags: syscall.CLONE_NEWNET,
+ }
+ }
+
if err := cmd.Run(); err != nil {
ws := err.(*exec.ExitError).Sys().(syscall.WaitStatus)
t.Errorf("test %q exited with status %d, want 0", tc.FullName(), ws.ExitStatus())
@@ -146,6 +156,12 @@ func runRunsc(tc gtest.TestCase, spec *specs.Spec) error {
if *overlay {
args = append(args, "-overlay")
}
+ if *vfs2 {
+ args = append(args, "-vfs2")
+ if *fuse {
+ args = append(args, "-fuse")
+ }
+ }
if *debug {
args = append(args, "-debug", "-log-packets=true")
}
@@ -156,8 +172,9 @@ func runRunsc(tc gtest.TestCase, spec *specs.Spec) error {
args = append(args, "-fsgofer-host-uds")
}
- if outDir, ok := syscall.Getenv("TEST_UNDECLARED_OUTPUTS_DIR"); ok {
- tdir := filepath.Join(outDir, strings.Replace(name, "/", "_", -1))
+ undeclaredOutputsDir, ok := syscall.Getenv("TEST_UNDECLARED_OUTPUTS_DIR")
+ if ok {
+ tdir := filepath.Join(undeclaredOutputsDir, strings.Replace(name, "/", "_", -1))
if err := os.MkdirAll(tdir, 0755); err != nil {
return fmt.Errorf("could not create test dir: %v", err)
}
@@ -197,14 +214,16 @@ func runRunsc(tc gtest.TestCase, spec *specs.Spec) error {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
sig := make(chan os.Signal, 1)
+ defer close(sig)
signal.Notify(sig, syscall.SIGTERM)
+ defer signal.Stop(sig)
go func() {
s, ok := <-sig
if !ok {
return
}
log.Warningf("%s: Got signal: %v", name, s)
- done := make(chan bool)
+ done := make(chan bool, 1)
dArgs := append([]string{}, args...)
dArgs = append(dArgs, "-alsologtostderr=true", "debug", "--stacks", id)
go func(dArgs []string) {
@@ -233,9 +252,11 @@ func runRunsc(tc gtest.TestCase, spec *specs.Spec) error {
}()
err = cmd.Run()
-
- signal.Stop(sig)
- close(sig)
+ if err == nil {
+ // If the test passed, then we erase the log directory. This speeds up
+ // uploading logs in continuous integration & saves on disk space.
+ os.RemoveAll(undeclaredOutputsDir)
+ }
return err
}
@@ -343,11 +364,21 @@ func runTestCaseRunsc(testBin string, tc gtest.TestCase, t *testing.T) {
// Set environment variables that indicate we are running in gVisor with
// the given platform, network, and filesystem stack.
- // TODO(gvisor.dev/issue/1487): Update this when the runner supports VFS2.
platformVar := "TEST_ON_GVISOR"
networkVar := "GVISOR_NETWORK"
+ env := append(os.Environ(), platformVar+"="+*platform, networkVar+"="+*network)
vfsVar := "GVISOR_VFS"
- env := append(os.Environ(), platformVar+"="+*platform, networkVar+"="+*network, vfsVar+"=VFS1")
+ if *vfs2 {
+ env = append(env, vfsVar+"=VFS2")
+ fuseVar := "FUSE_ENABLED"
+ if *fuse {
+ env = append(env, fuseVar+"=TRUE")
+ } else {
+ env = append(env, fuseVar+"=FALSE")
+ }
+ } else {
+ env = append(env, vfsVar+"=VFS1")
+ }
// Remove env variables that cause the gunit binary to write output
// files, since they will stomp on eachother, and on the output files
@@ -378,12 +409,12 @@ func runTestCaseRunsc(testBin string, tc gtest.TestCase, t *testing.T) {
}
}
-// filterEnv returns an environment with the blacklisted variables removed.
-func filterEnv(env, blacklist []string) []string {
+// filterEnv returns an environment with the excluded variables removed.
+func filterEnv(env, exclude []string) []string {
var out []string
for _, kv := range env {
ok := true
- for _, k := range blacklist {
+ for _, k := range exclude {
if strings.HasPrefix(kv, k+"=") {
ok = false
break
diff --git a/test/runtimes/BUILD b/test/runtimes/BUILD
index 4cd627222..3be123d94 100644
--- a/test/runtimes/BUILD
+++ b/test/runtimes/BUILD
@@ -4,30 +4,36 @@ package(licenses = ["notice"])
runtime_test(
name = "go1.12",
- blacklist_file = "blacklist_go1.12.csv",
+ exclude_file = "exclude_go1.12.csv",
lang = "go",
+ shard_count = 5,
)
runtime_test(
name = "java11",
- blacklist_file = "blacklist_java11.csv",
+ batch = 100,
+ exclude_file = "exclude_java11.csv",
lang = "java",
+ shard_count = 20,
)
runtime_test(
name = "nodejs12.4.0",
- blacklist_file = "blacklist_nodejs12.4.0.csv",
+ exclude_file = "exclude_nodejs12.4.0.csv",
lang = "nodejs",
+ shard_count = 10,
)
runtime_test(
name = "php7.3.6",
- blacklist_file = "blacklist_php7.3.6.csv",
+ exclude_file = "exclude_php7.3.6.csv",
lang = "php",
+ shard_count = 5,
)
runtime_test(
name = "python3.7.3",
- blacklist_file = "blacklist_python3.7.3.csv",
+ exclude_file = "exclude_python3.7.3.csv",
lang = "python",
+ shard_count = 5,
)
diff --git a/test/runtimes/blacklist_go1.12.csv b/test/runtimes/blacklist_go1.12.csv
deleted file mode 100644
index 8c8ae0c5d..000000000
--- a/test/runtimes/blacklist_go1.12.csv
+++ /dev/null
@@ -1,16 +0,0 @@
-test name,bug id,comment
-cgo_errors,,FLAKY
-cgo_test,,FLAKY
-go_test:cmd/go,,FLAKY
-go_test:cmd/vendor/golang.org/x/sys/unix,b/118783622,/dev devices missing
-go_test:net,b/118784196,socket: invalid argument. Works as intended: see bug.
-go_test:os,b/118780122,we have a pollable filesystem but that's a surprise
-go_test:os/signal,b/118780860,/dev/pts not properly supported
-go_test:runtime,b/118782341,sigtrap not reported or caught or something
-go_test:syscall,b/118781998,bad bytes -- bad mem addr
-race,b/118782931,thread sanitizer. Works as intended: b/62219744.
-runtime:cpu124,b/118778254,segmentation fault
-test:0_1,,FLAKY
-testasan,,
-testcarchive,b/118782924,no sigpipe
-testshared,,FLAKY
diff --git a/test/runtimes/blacklist_java11.csv b/test/runtimes/blacklist_java11.csv
deleted file mode 100644
index c012e5a56..000000000
--- a/test/runtimes/blacklist_java11.csv
+++ /dev/null
@@ -1,126 +0,0 @@
-test name,bug id,comment
-com/sun/crypto/provider/Cipher/PBE/PKCS12Cipher.java,,Fails in Docker
-com/sun/jdi/NashornPopFrameTest.java,,
-com/sun/jdi/ProcessAttachTest.java,,
-com/sun/management/HotSpotDiagnosticMXBean/CheckOrigin.java,,Fails in Docker
-com/sun/management/OperatingSystemMXBean/GetCommittedVirtualMemorySize.java,,
-com/sun/management/UnixOperatingSystemMXBean/GetMaxFileDescriptorCount.sh,,
-com/sun/tools/attach/AttachSelf.java,,
-com/sun/tools/attach/BasicTests.java,,
-com/sun/tools/attach/PermissionTest.java,,
-com/sun/tools/attach/StartManagementAgent.java,,
-com/sun/tools/attach/TempDirTest.java,,
-com/sun/tools/attach/modules/Driver.java,,
-java/lang/Character/CheckScript.java,,Fails in Docker
-java/lang/Character/CheckUnicode.java,,Fails in Docker
-java/lang/Class/GetPackageBootLoaderChildLayer.java,,
-java/lang/ClassLoader/nativeLibrary/NativeLibraryTest.java,,Fails in Docker
-java/lang/String/nativeEncoding/StringPlatformChars.java,,
-java/net/DatagramSocket/ReuseAddressTest.java,,
-java/net/DatagramSocket/SendDatagramToBadAddress.java,b/78473345,
-java/net/Inet4Address/PingThis.java,,
-java/net/InterfaceAddress/NetworkPrefixLength.java,b/78507103,
-java/net/MulticastSocket/MulticastTTL.java,,
-java/net/MulticastSocket/Promiscuous.java,,
-java/net/MulticastSocket/SetLoopbackMode.java,,
-java/net/MulticastSocket/SetTTLAndGetTTL.java,,
-java/net/MulticastSocket/Test.java,,
-java/net/MulticastSocket/TestDefaults.java,,
-java/net/MulticastSocket/TimeToLive.java,,
-java/net/NetworkInterface/NetworkInterfaceStreamTest.java,,
-java/net/Socket/SetSoLinger.java,b/78527327,SO_LINGER is not yet supported
-java/net/Socket/TrafficClass.java,b/78527818,Not supported on gVisor
-java/net/Socket/UrgentDataTest.java,b/111515323,
-java/net/Socket/setReuseAddress/Basic.java,b/78519214,SO_REUSEADDR enabled by default
-java/net/SocketOption/OptionsTest.java,,Fails in Docker
-java/net/SocketOption/TcpKeepAliveTest.java,,
-java/net/SocketPermission/SocketPermissionTest.java,,
-java/net/URLConnection/6212146/TestDriver.java,,Fails in Docker
-java/net/httpclient/RequestBuilderTest.java,,Fails in Docker
-java/net/httpclient/ShortResponseBody.java,,
-java/net/httpclient/ShortResponseBodyWithRetry.java,,
-java/nio/channels/AsyncCloseAndInterrupt.java,,
-java/nio/channels/AsynchronousServerSocketChannel/Basic.java,,
-java/nio/channels/AsynchronousSocketChannel/Basic.java,b/77921528,SO_KEEPALIVE is not settable
-java/nio/channels/DatagramChannel/BasicMulticastTests.java,,
-java/nio/channels/DatagramChannel/SocketOptionTests.java,,Fails in Docker
-java/nio/channels/DatagramChannel/UseDGWithIPv6.java,,
-java/nio/channels/FileChannel/directio/DirectIOTest.java,,Fails in Docker
-java/nio/channels/Selector/OutOfBand.java,,
-java/nio/channels/Selector/SelectWithConsumer.java,,Flaky
-java/nio/channels/ServerSocketChannel/SocketOptionTests.java,,
-java/nio/channels/SocketChannel/LingerOnClose.java,,
-java/nio/channels/SocketChannel/SocketOptionTests.java,b/77965901,
-java/nio/channels/spi/SelectorProvider/inheritedChannel/InheritedChannelTest.java,,Fails in Docker
-java/rmi/activation/Activatable/extLoadedImpl/ext.sh,,
-java/rmi/transport/checkLeaseInfoLeak/CheckLeaseLeak.java,,
-java/text/Format/NumberFormat/CurrencyFormat.java,,Fails in Docker
-java/text/Format/NumberFormat/CurrencyFormat.java,,Fails in Docker
-java/util/Calendar/JapaneseEraNameTest.java,,
-java/util/Currency/CurrencyTest.java,,Fails in Docker
-java/util/Currency/ValidateISO4217.java,,Fails in Docker
-java/util/Locale/LSRDataTest.java,,
-java/util/concurrent/locks/Lock/TimedAcquireLeak.java,,
-java/util/jar/JarFile/mrjar/MultiReleaseJarAPI.java,,Fails in Docker
-java/util/logging/LogManager/Configuration/updateConfiguration/SimpleUpdateConfigWithInputStreamTest.java,,
-java/util/logging/TestLoggerWeakRefLeak.java,,
-javax/imageio/AppletResourceTest.java,,
-javax/management/security/HashedPasswordFileTest.java,,
-javax/net/ssl/SSLSession/JSSERenegotiate.java,,Fails in Docker
-javax/sound/sampled/AudioInputStream/FrameLengthAfterConversion.java,,
-jdk/jfr/event/runtime/TestNetworkUtilizationEvent.java,,
-jdk/jfr/event/runtime/TestThreadParkEvent.java,,
-jdk/jfr/event/sampling/TestNative.java,,
-jdk/jfr/jcmd/TestJcmdChangeLogLevel.java,,
-jdk/jfr/jcmd/TestJcmdConfigure.java,,
-jdk/jfr/jcmd/TestJcmdDump.java,,
-jdk/jfr/jcmd/TestJcmdDumpGeneratedFilename.java,,
-jdk/jfr/jcmd/TestJcmdDumpLimited.java,,
-jdk/jfr/jcmd/TestJcmdDumpPathToGCRoots.java,,
-jdk/jfr/jcmd/TestJcmdLegacy.java,,
-jdk/jfr/jcmd/TestJcmdSaveToFile.java,,
-jdk/jfr/jcmd/TestJcmdStartDirNotExist.java,,
-jdk/jfr/jcmd/TestJcmdStartInvaldFile.java,,
-jdk/jfr/jcmd/TestJcmdStartPathToGCRoots.java,,
-jdk/jfr/jcmd/TestJcmdStartStopDefault.java,,
-jdk/jfr/jcmd/TestJcmdStartWithOptions.java,,
-jdk/jfr/jcmd/TestJcmdStartWithSettings.java,,
-jdk/jfr/jcmd/TestJcmdStopInvalidFile.java,,
-jdk/jfr/jvm/TestJfrJavaBase.java,,
-jdk/jfr/startupargs/TestStartRecording.java,,
-jdk/modules/incubator/ImageModules.java,,
-jdk/net/Sockets/ExtOptionTest.java,,
-jdk/net/Sockets/QuickAckTest.java,,
-lib/security/cacerts/VerifyCACerts.java,,
-sun/management/jmxremote/bootstrap/CustomLauncherTest.java,,
-sun/management/jmxremote/bootstrap/JvmstatCountersTest.java,,
-sun/management/jmxremote/bootstrap/LocalManagementTest.java,,
-sun/management/jmxremote/bootstrap/RmiRegistrySslTest.java,,
-sun/management/jmxremote/bootstrap/RmiSslBootstrapTest.sh,,
-sun/management/jmxremote/startstop/JMXStartStopTest.java,,
-sun/management/jmxremote/startstop/JMXStatusPerfCountersTest.java,,
-sun/management/jmxremote/startstop/JMXStatusTest.java,,
-sun/text/resources/LocaleDataTest.java,,
-sun/tools/jcmd/TestJcmdSanity.java,,
-sun/tools/jhsdb/AlternateHashingTest.java,,
-sun/tools/jhsdb/BasicLauncherTest.java,,
-sun/tools/jhsdb/HeapDumpTest.java,,
-sun/tools/jhsdb/heapconfig/JMapHeapConfigTest.java,,
-sun/tools/jinfo/BasicJInfoTest.java,,
-sun/tools/jinfo/JInfoTest.java,,
-sun/tools/jmap/BasicJMapTest.java,,
-sun/tools/jstack/BasicJStackTest.java,,
-sun/tools/jstack/DeadlockDetectionTest.java,,
-sun/tools/jstatd/TestJstatdExternalRegistry.java,,
-sun/tools/jstatd/TestJstatdPort.java,,Flaky
-sun/tools/jstatd/TestJstatdPortAndServer.java,,Flaky
-sun/util/calendar/zi/TestZoneInfo310.java,,
-tools/jar/modularJar/Basic.java,,
-tools/jar/multiRelease/Basic.java,,
-tools/jimage/JImageExtractTest.java,,
-tools/jimage/JImageTest.java,,
-tools/jlink/JLinkTest.java,,
-tools/jlink/plugins/IncludeLocalesPluginTest.java,,
-tools/jmod/hashes/HashesTest.java,,
-tools/launcher/BigJar.java,b/111611473,
-tools/launcher/modules/patch/systemmodules/PatchSystemModules.java,,
diff --git a/test/runtimes/defs.bzl b/test/runtimes/defs.bzl
index f836dd952..db22029a8 100644
--- a/test/runtimes/defs.bzl
+++ b/test/runtimes/defs.bzl
@@ -9,18 +9,20 @@ def _runtime_test_impl(ctx):
ctx.attr.lang,
"--image",
ctx.attr.image,
+ "--batch",
+ str(ctx.attr.batch),
]
- if ctx.attr.blacklist_file:
+ if ctx.attr.exclude_file:
args += [
- "--blacklist_file",
- ctx.files.blacklist_file[0].short_path,
+ "--exclude_file",
+ ctx.files.exclude_file[0].short_path,
]
# Build a runner.
runner = ctx.actions.declare_file("%s-executer" % ctx.label.name)
runner_content = "\n".join([
"#!/bin/bash",
- "%s %s\n" % (ctx.files._runner[0].short_path, " ".join(args)),
+ "%s %s $@\n" % (ctx.files._runner[0].short_path, " ".join(args)),
])
ctx.actions.write(runner, runner_content, is_executable = True)
@@ -28,7 +30,7 @@ def _runtime_test_impl(ctx):
return [DefaultInfo(
executable = runner,
runfiles = ctx.runfiles(
- files = ctx.files._runner + ctx.files.blacklist_file + ctx.files._proctor,
+ files = ctx.files._runner + ctx.files.exclude_file + ctx.files._proctor,
collect_default = True,
collect_data = True,
),
@@ -43,10 +45,14 @@ _runtime_test = rule(
"lang": attr.string(
mandatory = True,
),
- "blacklist_file": attr.label(
+ "exclude_file": attr.label(
mandatory = False,
allow_single_file = True,
),
+ "batch": attr.int(
+ default = 50,
+ mandatory = False,
+ ),
"_runner": attr.label(
default = "//test/runtimes/runner:runner",
),
@@ -65,15 +71,16 @@ def runtime_test(name, **kwargs):
"local",
"manual",
],
+ size = "enormous",
**kwargs
)
-def blacklist_test(name, blacklist_file):
- """Test that a blacklist parses correctly."""
+def exclude_test(name, exclude_file):
+ """Test that a exclude file parses correctly."""
go_test(
- name = name + "_blacklist_test",
+ name = name + "_exclude_test",
library = ":runner",
- srcs = ["blacklist_test.go"],
- args = ["--blacklist_file", "test/runtimes/" + blacklist_file],
- data = [blacklist_file],
+ srcs = ["exclude_test.go"],
+ args = ["--exclude_file", "test/runtimes/" + exclude_file],
+ data = [exclude_file],
)
diff --git a/test/runtimes/exclude_go1.12.csv b/test/runtimes/exclude_go1.12.csv
new file mode 100644
index 000000000..81e02cf64
--- /dev/null
+++ b/test/runtimes/exclude_go1.12.csv
@@ -0,0 +1,13 @@
+test name,bug id,comment
+cgo_errors,,FLAKY
+cgo_test,,FLAKY
+go_test:cmd/go,,FLAKY
+go_test:net,b/162473575,setsockopt: protocol not available.
+go_test:os,b/118780122,we have a pollable filesystem but that's a surprise
+go_test:os/signal,b/118780860,/dev/pts not properly supported. Also being tracked in b/29356795.
+go_test:runtime,b/118782341,sigtrap not reported or caught or something. Also being tracked in b/33003106.
+go_test:syscall,b/118781998,bad bytes -- bad mem addr; FcntlFlock(F_GETLK) not supported.
+runtime:cpu124,b/118778254,segmentation fault
+test:0_1,,FLAKY
+testcarchive,b/118782924,no sigpipe
+testshared,,FLAKY
diff --git a/test/runtimes/exclude_java11.csv b/test/runtimes/exclude_java11.csv
new file mode 100644
index 000000000..4d62f7d3a
--- /dev/null
+++ b/test/runtimes/exclude_java11.csv
@@ -0,0 +1,195 @@
+test name,bug id,comment
+com/sun/crypto/provider/Cipher/PBE/PKCS12Cipher.java,,Fails in Docker
+com/sun/jdi/NashornPopFrameTest.java,,
+com/sun/jdi/ProcessAttachTest.java,,
+com/sun/management/HotSpotDiagnosticMXBean/CheckOrigin.java,,Fails in Docker
+com/sun/management/OperatingSystemMXBean/GetCommittedVirtualMemorySize.java,,
+com/sun/management/UnixOperatingSystemMXBean/GetMaxFileDescriptorCount.sh,,
+com/sun/tools/attach/AttachSelf.java,,
+com/sun/tools/attach/BasicTests.java,,
+com/sun/tools/attach/PermissionTest.java,,
+com/sun/tools/attach/StartManagementAgent.java,,
+com/sun/tools/attach/TempDirTest.java,,
+com/sun/tools/attach/modules/Driver.java,,
+java/lang/Character/CheckScript.java,,Fails in Docker
+java/lang/Character/CheckUnicode.java,,Fails in Docker
+java/lang/Class/GetPackageBootLoaderChildLayer.java,,
+java/lang/ClassLoader/nativeLibrary/NativeLibraryTest.java,,Fails in Docker
+java/lang/module/ModuleDescriptorTest.java,,
+java/lang/String/nativeEncoding/StringPlatformChars.java,,
+java/net/CookieHandler/B6791927.java,,java.lang.RuntimeException: Expiration date shouldn't be 0
+java/net/InterfaceAddress/NetworkPrefixLength.java,b/78507103,
+java/net/MulticastSocket/MulticastTTL.java,,
+java/net/MulticastSocket/Promiscuous.java,,
+java/net/MulticastSocket/SetLoopbackMode.java,,
+java/net/MulticastSocket/SetTTLAndGetTTL.java,,
+java/net/MulticastSocket/Test.java,,
+java/net/MulticastSocket/TestDefaults.java,,
+java/net/MulticastSocket/TimeToLive.java,,
+java/net/NetworkInterface/NetworkInterfaceStreamTest.java,,
+java/net/Socket/SetSoLinger.java,b/78527327,SO_LINGER is not yet supported
+java/net/Socket/UrgentDataTest.java,b/111515323,
+java/net/SocketOption/OptionsTest.java,,Fails in Docker
+java/net/SocketPermission/SocketPermissionTest.java,,
+java/net/URLConnection/6212146/TestDriver.java,,Fails in Docker
+java/net/httpclient/RequestBuilderTest.java,,Fails in Docker
+java/nio/channels/DatagramChannel/BasicMulticastTests.java,,
+java/nio/channels/DatagramChannel/SocketOptionTests.java,,java.net.SocketException: Invalid argument
+java/nio/channels/DatagramChannel/UseDGWithIPv6.java,,
+java/nio/channels/FileChannel/directio/DirectIOTest.java,,Fails in Docker
+java/nio/channels/FileChannel/directio/PwriteDirect.java,,java.io.IOException: Invalid argument
+java/nio/channels/Selector/OutOfBand.java,,
+java/nio/channels/Selector/SelectWithConsumer.java,,Flaky
+java/nio/channels/ServerSocketChannel/SocketOptionTests.java,,
+java/nio/channels/SocketChannel/LingerOnClose.java,,
+java/nio/channels/SocketChannel/SocketOptionTests.java,b/77965901,
+java/nio/channels/spi/SelectorProvider/inheritedChannel/InheritedChannelTest.java,,Fails in Docker
+java/rmi/activation/Activatable/extLoadedImpl/ext.sh,,
+java/rmi/transport/checkLeaseInfoLeak/CheckLeaseLeak.java,,
+java/text/Format/NumberFormat/CurrencyFormat.java,,Fails in Docker
+java/text/Format/NumberFormat/CurrencyFormat.java,,Fails in Docker
+java/util/Calendar/JapaneseEraNameTest.java,,
+java/util/Currency/CurrencyTest.java,,Fails in Docker
+java/util/Currency/ValidateISO4217.java,,Fails in Docker
+java/util/EnumSet/BogusEnumSet.java,,"java.io.InvalidClassException: java.util.EnumSet; local class incompatible: stream classdesc serialVersionUID = -2409567991088730183, local class serialVersionUID = 1009687484059888093"
+java/util/Locale/Bug8040211.java,,java.lang.RuntimeException: Failed.
+java/util/Locale/LSRDataTest.java,,
+java/util/Properties/CompatibilityTest.java,,"java.lang.RuntimeException: jdk.internal.org.xml.sax.SAXParseException; Internal DTD subset is not allowed. The Properties XML document must have the following DOCTYPE declaration: <!DOCTYPE properties SYSTEM ""http://java.sun.com/dtd/properties.dtd"">"
+java/util/ResourceBundle/Control/XMLResourceBundleTest.java,,java.util.MissingResourceException: Can't find bundle for base name XmlRB locale
+java/util/ResourceBundle/modules/xmlformat/xmlformat.sh,,Timeout reached: 60000. Process is not alive!
+java/util/TimeZone/TimeZoneTest.java,,Uncaught exception thrown in test method TestShortZoneIDs
+java/util/concurrent/locks/Lock/TimedAcquireLeak.java,,
+java/util/jar/JarFile/mrjar/MultiReleaseJarAPI.java,,Fails in Docker
+java/util/logging/LogManager/Configuration/updateConfiguration/SimpleUpdateConfigWithInputStreamTest.java,,
+java/util/logging/TestLoggerWeakRefLeak.java,,
+java/util/spi/ResourceBundleControlProvider/UserDefaultControlTest.java,,java.util.MissingResourceException: Can't find bundle for base name com.foo.XmlRB locale
+javax/imageio/AppletResourceTest.java,,
+javax/imageio/plugins/jpeg/JPEGsNotAcceleratedTest.java,,java.awt.HeadlessException: No X11 DISPLAY variable was set but this program performed an operation which requires it.
+javax/management/security/HashedPasswordFileTest.java,,
+javax/net/ssl/DTLS/DTLSBufferOverflowUnderflowTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSDataExchangeTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSEnginesClosureTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSHandshakeTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSHandshakeWithReplicatedPacketsTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSIncorrectAppDataTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSMFLNTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSNotEnabledRC4Test.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSRehandshakeTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSRehandshakeWithCipherChangeTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSRehandshakeWithDataExTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSSequenceNumberTest.java,,Compilation failed
+javax/net/ssl/DTLS/DTLSUnsupportedCiphersTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10BufferOverflowUnderflowTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10DataExchangeTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10EnginesClosureTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10HandshakeTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10HandshakeWithReplicatedPacketsTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10IncorrectAppDataTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10MFLNTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10NotEnabledRC4Test.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10RehandshakeTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10RehandshakeWithCipherChangeTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10RehandshakeWithDataExTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10SequenceNumberTest.java,,Compilation failed
+javax/net/ssl/DTLSv10/DTLSv10UnsupportedCiphersTest.java,,Compilation failed
+javax/net/ssl/SSLSession/JSSERenegotiate.java,,Fails in Docker
+javax/net/ssl/TLS/TLSDataExchangeTest.java,,Compilation failed
+javax/net/ssl/TLS/TLSEnginesClosureTest.java,,Compilation failed
+javax/net/ssl/TLS/TLSHandshakeTest.java,,Compilation failed
+javax/net/ssl/TLS/TLSMFLNTest.java,,Compilation failed
+javax/net/ssl/TLS/TLSNotEnabledRC4Test.java,,Compilation failed
+javax/net/ssl/TLS/TLSRehandshakeTest.java,,Compilation failed
+javax/net/ssl/TLS/TLSRehandshakeWithCipherChangeTest.java,,Compilation failed
+javax/net/ssl/TLS/TLSRehandshakeWithDataExTest.java,,Compilation failed
+javax/net/ssl/TLS/TLSUnsupportedCiphersTest.java,,Compilation failed
+javax/net/ssl/TLSv1/TLSDataExchangeTest.java,,Compilation failed
+javax/net/ssl/TLSv1/TLSEnginesClosureTest.java,,Compilation failed
+javax/net/ssl/TLSv1/TLSHandshakeTest.java,,Compilation failed
+javax/net/ssl/TLSv1/TLSMFLNTest.java,,Compilation failed
+javax/net/ssl/TLSv1/TLSNotEnabledRC4Test.java,,Compilation failed
+javax/net/ssl/TLSv1/TLSRehandshakeTest.java,,Compilation failed
+javax/net/ssl/TLSv1/TLSRehandshakeWithCipherChangeTest.java,,Compilation failed
+javax/net/ssl/TLSv1/TLSRehandshakeWithDataExTest.java,,Compilation failed
+javax/net/ssl/TLSv1/TLSUnsupportedCiphersTest.java,,Compilation failed
+javax/net/ssl/TLSv11/TLSDataExchangeTest.java,,Compilation failed
+javax/net/ssl/TLSv11/TLSEnginesClosureTest.java,,Compilation failed
+javax/net/ssl/TLSv11/TLSHandshakeTest.java,,Compilation failed
+javax/net/ssl/TLSv11/TLSMFLNTest.java,,Compilation failed
+javax/net/ssl/TLSv11/TLSNotEnabledRC4Test.java,,Compilation failed
+javax/net/ssl/TLSv11/TLSRehandshakeTest.java,,Compilation failed
+javax/net/ssl/TLSv11/TLSRehandshakeWithCipherChangeTest.java,,Compilation failed
+javax/net/ssl/TLSv11/TLSRehandshakeWithDataExTest.java,,Compilation failed
+javax/net/ssl/TLSv11/TLSUnsupportedCiphersTest.java,,Compilation failed
+javax/net/ssl/TLSv12/TLSEnginesClosureTest.java,,Compilation failed
+javax/sound/sampled/AudioInputStream/FrameLengthAfterConversion.java,,
+jdk/jfr/cmd/TestHelp.java,,java.lang.RuntimeException: 'Available commands are:' missing from stdout/stderr
+jdk/jfr/cmd/TestPrint.java,,Missing file' missing from stdout/stderr
+jdk/jfr/cmd/TestPrintDefault.java,,java.lang.RuntimeException: 'JVMInformation' missing from stdout/stderr
+jdk/jfr/cmd/TestPrintJSON.java,,javax.script.ScriptException: <eval>:1:17 Expected an operand but found eof var jsonObject = ^ in <eval> at line number 1 at column number 17
+jdk/jfr/cmd/TestPrintXML.java,,org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Premature end of file.
+jdk/jfr/cmd/TestReconstruct.java,,java.lang.RuntimeException: 'Too few arguments' missing from stdout/stderr
+jdk/jfr/cmd/TestSplit.java,,java.lang.RuntimeException: 'Missing file' missing from stdout/stderr
+jdk/jfr/cmd/TestSummary.java,,java.lang.RuntimeException: 'Missing file' missing from stdout/stderr
+jdk/jfr/event/compiler/TestCompilerStats.java,,java.lang.RuntimeException: Field nmetodsSize not in event
+jdk/jfr/event/metadata/TestDefaultConfigurations.java,,Setting 'threshold' in event 'jdk.SecurityPropertyModification' was not configured in the configuration 'default'
+jdk/jfr/event/runtime/TestActiveSettingEvent.java,,java.lang.Exception: Could not find setting with name jdk.X509Validation#threshold
+jdk/jfr/event/runtime/TestModuleEvents.java,,java.lang.RuntimeException: assertEquals: expected jdk.proxy1 to equal java.base
+jdk/jfr/event/runtime/TestNetworkUtilizationEvent.java,,
+jdk/jfr/event/runtime/TestThreadParkEvent.java,,
+jdk/jfr/event/sampling/TestNative.java,,
+jdk/jfr/jcmd/TestJcmdChangeLogLevel.java,,
+jdk/jfr/jcmd/TestJcmdConfigure.java,,
+jdk/jfr/jcmd/TestJcmdDump.java,,
+jdk/jfr/jcmd/TestJcmdDumpGeneratedFilename.java,,
+jdk/jfr/jcmd/TestJcmdDumpLimited.java,,
+jdk/jfr/jcmd/TestJcmdDumpPathToGCRoots.java,,
+jdk/jfr/jcmd/TestJcmdLegacy.java,,
+jdk/jfr/jcmd/TestJcmdSaveToFile.java,,
+jdk/jfr/jcmd/TestJcmdStartDirNotExist.java,,
+jdk/jfr/jcmd/TestJcmdStartInvaldFile.java,,
+jdk/jfr/jcmd/TestJcmdStartPathToGCRoots.java,,
+jdk/jfr/jcmd/TestJcmdStartStopDefault.java,,
+jdk/jfr/jcmd/TestJcmdStartWithOptions.java,,
+jdk/jfr/jcmd/TestJcmdStartWithSettings.java,,
+jdk/jfr/jcmd/TestJcmdStopInvalidFile.java,,
+jdk/jfr/jvm/TestGetAllEventClasses.java,,Compilation failed
+jdk/jfr/jvm/TestJfrJavaBase.java,,
+jdk/jfr/startupargs/TestStartRecording.java,,
+jdk/modules/incubator/ImageModules.java,,
+jdk/net/Sockets/ExtOptionTest.java,,
+jdk/net/Sockets/QuickAckTest.java,,
+lib/security/cacerts/VerifyCACerts.java,,
+sun/management/jmxremote/bootstrap/CustomLauncherTest.java,,
+sun/management/jmxremote/bootstrap/JvmstatCountersTest.java,,
+sun/management/jmxremote/bootstrap/LocalManagementTest.java,,
+sun/management/jmxremote/bootstrap/RmiRegistrySslTest.java,,
+sun/management/jmxremote/bootstrap/RmiSslBootstrapTest.sh,,
+sun/management/jmxremote/startstop/JMXStartStopTest.java,,
+sun/management/jmxremote/startstop/JMXStatusPerfCountersTest.java,,
+sun/management/jmxremote/startstop/JMXStatusTest.java,,
+sun/text/resources/LocaleDataTest.java,,
+sun/tools/jcmd/TestJcmdSanity.java,,
+sun/tools/jhsdb/AlternateHashingTest.java,,
+sun/tools/jhsdb/BasicLauncherTest.java,,
+sun/tools/jhsdb/HeapDumpTest.java,,
+sun/tools/jhsdb/heapconfig/JMapHeapConfigTest.java,,
+sun/tools/jinfo/BasicJInfoTest.java,,
+sun/tools/jinfo/JInfoTest.java,,
+sun/tools/jmap/BasicJMapTest.java,,
+sun/tools/jstack/BasicJStackTest.java,,
+sun/tools/jstack/DeadlockDetectionTest.java,,
+sun/tools/jstatd/TestJstatdExternalRegistry.java,,
+sun/tools/jstatd/TestJstatdPort.java,,Flaky
+sun/tools/jstatd/TestJstatdPortAndServer.java,,Flaky
+sun/util/calendar/zi/TestZoneInfo310.java,,
+tools/jar/modularJar/Basic.java,,
+tools/jar/multiRelease/Basic.java,,
+tools/jimage/JImageExtractTest.java,,
+tools/jimage/JImageTest.java,,
+tools/jlink/JLinkTest.java,,
+tools/jlink/plugins/IncludeLocalesPluginTest.java,,
+tools/jmod/hashes/HashesTest.java,,
+tools/launcher/BigJar.java,b/111611473,
+tools/launcher/HelpFlagsTest.java,,java.lang.AssertionError: HelpFlagsTest failed: Tool jfr not covered by this test. Add specification to jdkTools array!
+tools/launcher/VersionCheck.java,,java.lang.AssertionError: VersionCheck failed: testToolVersion: [jfr];
+tools/launcher/modules/patch/systemmodules/PatchSystemModules.java,,
diff --git a/test/runtimes/blacklist_nodejs12.4.0.csv b/test/runtimes/exclude_nodejs12.4.0.csv
index 4ab4e2927..4eb0a4807 100644
--- a/test/runtimes/blacklist_nodejs12.4.0.csv
+++ b/test/runtimes/exclude_nodejs12.4.0.csv
@@ -9,6 +9,8 @@ fixtures/test-fs-stat-sync-overflow.js,,
internet/test-dgram-broadcast-multi-process.js,,
internet/test-dgram-multicast-multi-process.js,,
internet/test-dgram-multicast-set-interface-lo.js,,
+internet/test-doctool-versions.js,,
+internet/test-uv-threadpool-schedule.js,,
parallel/test-cluster-dgram-reuse.js,b/64024294,
parallel/test-dgram-bind-fd.js,b/132447356,
parallel/test-dgram-create-socket-handle-fd.js,b/132447238,
@@ -45,3 +47,5 @@ pseudo-tty/test-tty-window-size.js,,
pseudo-tty/test-tty-wrap.js,,
pummel/test-net-pingpong.js,,
pummel/test-vm-memleak.js,,
+sequential/test-net-bytes-per-incoming-chunk-overhead.js,,flaky - timeout
+tick-processor/test-tick-processor-builtin.js,,
diff --git a/test/runtimes/blacklist_php7.3.6.csv b/test/runtimes/exclude_php7.3.6.csv
index 456bf7487..0bef786c0 100644
--- a/test/runtimes/blacklist_php7.3.6.csv
+++ b/test/runtimes/exclude_php7.3.6.csv
@@ -8,6 +8,10 @@ ext/mbstring/tests/bug77165.phpt,,
ext/mbstring/tests/bug77454.phpt,,
ext/mbstring/tests/mb_convert_encoding_leak.phpt,,
ext/mbstring/tests/mb_strrpos_encoding_3rd_param.phpt,,
+ext/session/tests/session_set_save_handler_class_018.phpt,,
+ext/session/tests/session_set_save_handler_iface_003.phpt,,
+ext/session/tests/session_set_save_handler_variation4.phpt,,
+ext/session/tests/session_set_save_handler_variation5.phpt,,
ext/standard/tests/file/filetype_variation.phpt,,
ext/standard/tests/file/fopen_variation19.phpt,,
ext/standard/tests/file/php_fd_wrapper_01.phpt,,
@@ -21,9 +25,13 @@ ext/standard/tests/file/symlink_link_linkinfo_is_link_variation8.phpt,,
ext/standard/tests/general_functions/escapeshellarg_bug71270.phpt,,
ext/standard/tests/general_functions/escapeshellcmd_bug71270.phpt,,
ext/standard/tests/network/bug20134.phpt,,
+ext/standard/tests/streams/stream_socket_sendto.phpt,,
+ext/standard/tests/strings/007.phpt,,
+sapi/cli/tests/upload_2G.phpt,,
tests/output/stream_isatty_err.phpt,b/68720279,
tests/output/stream_isatty_in-err.phpt,b/68720282,
tests/output/stream_isatty_in-out-err.phpt,,
tests/output/stream_isatty_in-out.phpt,b/68720299,
tests/output/stream_isatty_out-err.phpt,b/68720311,
tests/output/stream_isatty_out.phpt,b/68720325,
+Zend/tests/concat_003.phpt,,
diff --git a/test/runtimes/blacklist_python3.7.3.csv b/test/runtimes/exclude_python3.7.3.csv
index 2b9947212..2b9947212 100644
--- a/test/runtimes/blacklist_python3.7.3.csv
+++ b/test/runtimes/exclude_python3.7.3.csv
diff --git a/test/runtimes/proctor/BUILD b/test/runtimes/proctor/BUILD
index da1e331e1..f76e2ddc0 100644
--- a/test/runtimes/proctor/BUILD
+++ b/test/runtimes/proctor/BUILD
@@ -21,7 +21,7 @@ go_test(
size = "small",
srcs = ["proctor_test.go"],
library = ":proctor",
- nocgo = 1,
+ pure = True,
deps = [
"//pkg/test/testutil",
],
diff --git a/test/runtimes/proctor/go.go b/test/runtimes/proctor/go.go
index 3e2d5d8db..d0ae844e6 100644
--- a/test/runtimes/proctor/go.go
+++ b/test/runtimes/proctor/go.go
@@ -74,17 +74,26 @@ func (goRunner) ListTests() ([]string, error) {
return append(toolSlice, diskFiltered...), nil
}
-// TestCmd implements TestRunner.TestCmd.
-func (goRunner) TestCmd(test string) *exec.Cmd {
- // Check if test exists on disk by searching for file of the same name.
- // This will determine whether or not it is a Go test on disk.
- if strings.HasSuffix(test, ".go") {
- // Test has suffix ".go" which indicates a disk test, run it as such.
- cmd := exec.Command("go", "run", "run.go", "-v", "--", test)
+// TestCmds implements TestRunner.TestCmds.
+func (goRunner) TestCmds(tests []string) []*exec.Cmd {
+ var toolTests, onDiskTests []string
+ for _, test := range tests {
+ if strings.HasSuffix(test, ".go") {
+ onDiskTests = append(onDiskTests, test)
+ } else {
+ toolTests = append(toolTests, "^"+test+"$")
+ }
+ }
+
+ var cmds []*exec.Cmd
+ if len(toolTests) > 0 {
+ cmds = append(cmds, exec.Command("go", "tool", "dist", "test", "-v", "-no-rebuild", "-run", strings.Join(toolTests, "\\|")))
+ }
+ if len(onDiskTests) > 0 {
+ cmd := exec.Command("go", append([]string{"run", "run.go", "-v", "--"}, onDiskTests...)...)
cmd.Dir = goTestDir
- return cmd
+ cmds = append(cmds, cmd)
}
- // No ".go" suffix, run as a tool test.
- return exec.Command("go", "tool", "dist", "test", "-run", test)
+ return cmds
}
diff --git a/test/runtimes/proctor/java.go b/test/runtimes/proctor/java.go
index 8b362029d..d456fa681 100644
--- a/test/runtimes/proctor/java.go
+++ b/test/runtimes/proctor/java.go
@@ -60,12 +60,17 @@ func (javaRunner) ListTests() ([]string, error) {
return testSlice, nil
}
-// TestCmd implements TestRunner.TestCmd.
-func (javaRunner) TestCmd(test string) *exec.Cmd {
- args := []string{
- "-noreport",
- "-dir:" + javaTestDir,
- test,
- }
- return exec.Command("jtreg", args...)
+// TestCmds implements TestRunner.TestCmds.
+func (javaRunner) TestCmds(tests []string) []*exec.Cmd {
+ args := append(
+ []string{
+ "-agentvm", // Execute each action using a pool of reusable JVMs.
+ "-dir:" + javaTestDir, // Base directory for test files and directories.
+ "-noreport", // Do not generate a final report.
+ "-timeoutFactor:20", // Extend the default timeout (2 min) of all tests by this factor.
+ "-verbose:nopass", // Verbose output but supress it for tests that passed.
+ },
+ tests...,
+ )
+ return []*exec.Cmd{exec.Command("jtreg", args...)}
}
diff --git a/test/runtimes/proctor/nodejs.go b/test/runtimes/proctor/nodejs.go
index bd57db444..23d6edc72 100644
--- a/test/runtimes/proctor/nodejs.go
+++ b/test/runtimes/proctor/nodejs.go
@@ -39,8 +39,8 @@ func (nodejsRunner) ListTests() ([]string, error) {
return testSlice, nil
}
-// TestCmd implements TestRunner.TestCmd.
-func (nodejsRunner) TestCmd(test string) *exec.Cmd {
- args := []string{filepath.Join("tools", "test.py"), test}
- return exec.Command("/usr/bin/python", args...)
+// TestCmds implements TestRunner.TestCmds.
+func (nodejsRunner) TestCmds(tests []string) []*exec.Cmd {
+ args := append([]string{filepath.Join("tools", "test.py")}, tests...)
+ return []*exec.Cmd{exec.Command("/usr/bin/python", args...)}
}
diff --git a/test/runtimes/proctor/php.go b/test/runtimes/proctor/php.go
index 9115040e1..6a83d64e3 100644
--- a/test/runtimes/proctor/php.go
+++ b/test/runtimes/proctor/php.go
@@ -17,6 +17,7 @@ package main
import (
"os/exec"
"regexp"
+ "strings"
)
var phpTestRegEx = regexp.MustCompile(`^.+\.phpt$`)
@@ -35,8 +36,8 @@ func (phpRunner) ListTests() ([]string, error) {
return testSlice, nil
}
-// TestCmd implements TestRunner.TestCmd.
-func (phpRunner) TestCmd(test string) *exec.Cmd {
- args := []string{"test", "TESTS=" + test}
- return exec.Command("make", args...)
+// TestCmds implements TestRunner.TestCmds.
+func (phpRunner) TestCmds(tests []string) []*exec.Cmd {
+ args := []string{"test", "TESTS=" + strings.Join(tests, " ")}
+ return []*exec.Cmd{exec.Command("make", args...)}
}
diff --git a/test/runtimes/proctor/proctor.go b/test/runtimes/proctor/proctor.go
index b54abe434..9e0642424 100644
--- a/test/runtimes/proctor/proctor.go
+++ b/test/runtimes/proctor/proctor.go
@@ -25,6 +25,7 @@ import (
"os/signal"
"path/filepath"
"regexp"
+ "strings"
"syscall"
)
@@ -34,15 +35,18 @@ type TestRunner interface {
// ListTests returns a string slice of tests available to run.
ListTests() ([]string, error)
- // TestCmd returns an *exec.Cmd that will run the given test.
- TestCmd(test string) *exec.Cmd
+ // TestCmds returns a slice of *exec.Cmd that will run the given tests.
+ // There is no correlation between the number of exec.Cmds returned and the
+ // number of tests. It could return one command to run all tests or a few
+ // commands that collectively run all.
+ TestCmds(tests []string) []*exec.Cmd
}
var (
- runtime = flag.String("runtime", "", "name of runtime")
- list = flag.Bool("list", false, "list all available tests")
- testName = flag.String("test", "", "run a single test from the list of available tests")
- pause = flag.Bool("pause", false, "cause container to pause indefinitely, reaping any zombie children")
+ runtime = flag.String("runtime", "", "name of runtime")
+ list = flag.Bool("list", false, "list all available tests")
+ testNames = flag.String("tests", "", "run a subset of the available tests")
+ pause = flag.Bool("pause", false, "cause container to pause indefinitely, reaping any zombie children")
)
func main() {
@@ -75,18 +79,20 @@ func main() {
}
var tests []string
- if *testName == "" {
+ if *testNames == "" {
// Run every test.
tests, err = tr.ListTests()
if err != nil {
log.Fatalf("failed to get all tests: %v", err)
}
} else {
- // Run a single test.
- tests = []string{*testName}
+ // Run subset of test.
+ tests = strings.Split(*testNames, ",")
}
- for _, test := range tests {
- cmd := tr.TestCmd(test)
+
+ // Run tests.
+ cmds := tr.TestCmds(tests)
+ for _, cmd := range cmds {
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
if err := cmd.Run(); err != nil {
log.Fatalf("FAIL: %v", err)
diff --git a/test/runtimes/proctor/python.go b/test/runtimes/proctor/python.go
index b9e0fbe6f..7c598801b 100644
--- a/test/runtimes/proctor/python.go
+++ b/test/runtimes/proctor/python.go
@@ -42,8 +42,8 @@ func (pythonRunner) ListTests() ([]string, error) {
return toolSlice, nil
}
-// TestCmd implements TestRunner.TestCmd.
-func (pythonRunner) TestCmd(test string) *exec.Cmd {
- args := []string{"-m", "test", test}
- return exec.Command("./python", args...)
+// TestCmds implements TestRunner.TestCmds.
+func (pythonRunner) TestCmds(tests []string) []*exec.Cmd {
+ args := append([]string{"-m", "test"}, tests...)
+ return []*exec.Cmd{exec.Command("./python", args...)}
}
diff --git a/test/runtimes/runner/BUILD b/test/runtimes/runner/BUILD
index 63924b9c5..dc0d5d5b4 100644
--- a/test/runtimes/runner/BUILD
+++ b/test/runtimes/runner/BUILD
@@ -8,14 +8,15 @@ go_binary(
srcs = ["main.go"],
visibility = ["//test/runtimes:__pkg__"],
deps = [
+ "//pkg/log",
"//pkg/test/dockerutil",
"//pkg/test/testutil",
],
)
go_test(
- name = "blacklist_test",
+ name = "exclude_test",
size = "small",
- srcs = ["blacklist_test.go"],
+ srcs = ["exclude_test.go"],
library = ":runner",
)
diff --git a/test/runtimes/runner/blacklist_test.go b/test/runtimes/runner/exclude_test.go
index 0ff69ab18..67c2170c8 100644
--- a/test/runtimes/runner/blacklist_test.go
+++ b/test/runtimes/runner/exclude_test.go
@@ -25,13 +25,13 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}
-// Test that the blacklist parses without error.
-func TestBlacklists(t *testing.T) {
- bl, err := getBlacklist()
+// Test that the exclude file parses without error.
+func TestExcludelist(t *testing.T) {
+ ex, err := getExcludes()
if err != nil {
- t.Fatalf("error parsing blacklist: %v", err)
+ t.Fatalf("error parsing exclude file: %v", err)
}
- if *blacklistFile != "" && len(bl) == 0 {
- t.Errorf("got empty blacklist for file %q", *blacklistFile)
+ if *excludeFile != "" && len(ex) == 0 {
+ t.Errorf("got empty excludes for file %q", *excludeFile)
}
}
diff --git a/test/runtimes/runner/main.go b/test/runtimes/runner/main.go
index 57540e00e..948e7cf9c 100644
--- a/test/runtimes/runner/main.go
+++ b/test/runtimes/runner/main.go
@@ -16,6 +16,7 @@
package main
import (
+ "context"
"encoding/csv"
"flag"
"fmt"
@@ -26,18 +27,20 @@ import (
"testing"
"time"
+ "gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/test/dockerutil"
"gvisor.dev/gvisor/pkg/test/testutil"
)
var (
- lang = flag.String("lang", "", "language runtime to test")
- image = flag.String("image", "", "docker image with runtime tests")
- blacklistFile = flag.String("blacklist_file", "", "file containing blacklist of tests to exclude, in CSV format with fields: test name, bug id, comment")
+ lang = flag.String("lang", "", "language runtime to test")
+ image = flag.String("image", "", "docker image with runtime tests")
+ excludeFile = flag.String("exclude_file", "", "file containing list of tests to exclude, in CSV format with fields: test name, bug id, comment")
+ batchSize = flag.Int("batch", 50, "number of test cases run in one command")
)
// Wait time for each test to run.
-const timeout = 5 * time.Minute
+const timeout = 90 * time.Minute
func main() {
flag.Parse()
@@ -52,21 +55,27 @@ func main() {
// defered functions before exiting. It returns an exit code that should be
// passed to os.Exit.
func runTests() int {
- // Get tests to blacklist.
- blacklist, err := getBlacklist()
+ // Get tests to exclude..
+ excludes, err := getExcludes()
if err != nil {
- fmt.Fprintf(os.Stderr, "Error getting blacklist: %s\n", err.Error())
+ fmt.Fprintf(os.Stderr, "Error getting exclude list: %s\n", err.Error())
return 1
}
// Construct the shared docker instance.
- d := dockerutil.MakeDocker(testutil.DefaultLogger(*lang))
- defer d.CleanUp()
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, testutil.DefaultLogger(*lang))
+ defer d.CleanUp(ctx)
+
+ if err := testutil.TouchShardStatusFile(); err != nil {
+ fmt.Fprintf(os.Stderr, "error touching status shard file: %v\n", err)
+ return 1
+ }
// Get a slice of tests to run. This will also start a single Docker
// container that will be used to run each test. The final test will
// stop the Docker container.
- tests, err := getTests(d, blacklist)
+ tests, err := getTests(ctx, d, excludes)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
return 1
@@ -77,17 +86,18 @@ func runTests() int {
}
// getTests executes all tests as table tests.
-func getTests(d *dockerutil.Docker, blacklist map[string]struct{}) ([]testing.InternalTest, error) {
+func getTests(ctx context.Context, d *dockerutil.Container, excludes map[string]struct{}) ([]testing.InternalTest, error) {
// Start the container.
- d.CopyFiles("/proctor", "test/runtimes/proctor/proctor")
- if err := d.Spawn(dockerutil.RunOpts{
+ opts := dockerutil.RunOpts{
Image: fmt.Sprintf("runtimes/%s", *image),
- }, "/proctor/proctor", "--pause"); err != nil {
+ }
+ d.CopyFiles(&opts, "/proctor", "test/runtimes/proctor/proctor")
+ if err := d.Spawn(ctx, opts, "/proctor/proctor", "--pause"); err != nil {
return nil, fmt.Errorf("docker run failed: %v", err)
}
// Get a list of all tests in the image.
- list, err := d.Exec(dockerutil.RunOpts{}, "/proctor/proctor", "--runtime", *lang, "--list")
+ list, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/proctor/proctor", "--runtime", *lang, "--list")
if err != nil {
return nil, fmt.Errorf("docker exec failed: %v", err)
}
@@ -102,17 +112,23 @@ func getTests(d *dockerutil.Docker, blacklist map[string]struct{}) ([]testing.In
}
var itests []testing.InternalTest
- for _, tci := range indices {
- // Capture tc in this scope.
- tc := tests[tci]
+ for i := 0; i < len(indices); i += *batchSize {
+ var tcs []string
+ end := i + *batchSize
+ if end > len(indices) {
+ end = len(indices)
+ }
+ for _, tc := range indices[i:end] {
+ // Add test if not excluded.
+ if _, ok := excludes[tests[tc]]; ok {
+ log.Infof("Skipping test case %s\n", tests[tc])
+ continue
+ }
+ tcs = append(tcs, tests[tc])
+ }
itests = append(itests, testing.InternalTest{
- Name: tc,
+ Name: strings.Join(tcs, ", "),
F: func(t *testing.T) {
- // Is the test blacklisted?
- if _, ok := blacklist[tc]; ok {
- t.Skipf("SKIP: blacklisted test %q", tc)
- }
-
var (
now = time.Now()
done = make(chan struct{})
@@ -121,20 +137,20 @@ func getTests(d *dockerutil.Docker, blacklist map[string]struct{}) ([]testing.In
)
go func() {
- fmt.Printf("RUNNING %s...\n", tc)
- output, err = d.Exec(dockerutil.RunOpts{}, "/proctor/proctor", "--runtime", *lang, "--test", tc)
+ fmt.Printf("RUNNING the following in a batch\n%s\n", strings.Join(tcs, "\n"))
+ output, err = d.Exec(ctx, dockerutil.ExecOpts{}, "/proctor/proctor", "--runtime", *lang, "--tests", strings.Join(tcs, ","))
close(done)
}()
select {
case <-done:
if err == nil {
- fmt.Printf("PASS: %s (%v)\n\n", tc, time.Since(now))
+ fmt.Printf("PASS: (%v)\n\n", time.Since(now))
return
}
- t.Errorf("FAIL: %s (%v):\n%s\n", tc, time.Since(now), output)
+ t.Errorf("FAIL: (%v):\n%s\n", time.Since(now), output)
case <-time.After(timeout):
- t.Errorf("TIMEOUT: %s (%v):\n%s\n", tc, time.Since(now), output)
+ t.Errorf("TIMEOUT: (%v):\n%s\n", time.Since(now), output)
}
},
})
@@ -143,14 +159,14 @@ func getTests(d *dockerutil.Docker, blacklist map[string]struct{}) ([]testing.In
return itests, nil
}
-// getBlacklist reads the blacklist file and returns a set of test names to
+// getBlacklist reads the exclude file and returns a set of test names to
// exclude.
-func getBlacklist() (map[string]struct{}, error) {
- blacklist := make(map[string]struct{})
- if *blacklistFile == "" {
- return blacklist, nil
+func getExcludes() (map[string]struct{}, error) {
+ excludes := make(map[string]struct{})
+ if *excludeFile == "" {
+ return excludes, nil
}
- f, err := os.Open(*blacklistFile)
+ f, err := os.Open(*excludeFile)
if err != nil {
return nil, err
}
@@ -171,9 +187,9 @@ func getBlacklist() (map[string]struct{}, error) {
if err != nil {
return nil, err
}
- blacklist[record[0]] = struct{}{}
+ excludes[record[0]] = struct{}{}
}
- return blacklist, nil
+ return excludes, nil
}
// testDeps implements testing.testDeps (an unexported interface), and is
diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD
index 9800a0cdf..c19b30b4a 100644
--- a/test/syscalls/BUILD
+++ b/test/syscalls/BUILD
@@ -2,47 +2,78 @@ load("//test/runner:defs.bzl", "syscall_test")
package(licenses = ["notice"])
-syscall_test(test = "//test/syscalls/linux:32bit_test")
+syscall_test(
+ test = "//test/syscalls/linux:32bit_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:accept_bind_stream_test")
+syscall_test(
+ test = "//test/syscalls/linux:accept_bind_stream_test",
+ vfs2 = "True",
+)
syscall_test(
size = "large",
shard_count = 50,
test = "//test/syscalls/linux:accept_bind_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:access_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:affinity_test")
+syscall_test(
+ test = "//test/syscalls/linux:affinity_test",
+ vfs2 = "True",
+)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:aio_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
shard_count = 5,
test = "//test/syscalls/linux:alarm_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:arch_prctl_test")
+syscall_test(
+ test = "//test/syscalls/linux:arch_prctl_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:bad_test")
+syscall_test(
+ test = "//test/syscalls/linux:bad_test",
+ vfs2 = "True",
+)
syscall_test(
size = "large",
add_overlay = True,
test = "//test/syscalls/linux:bind_test",
+ vfs2 = "True",
+)
+
+syscall_test(
+ test = "//test/syscalls/linux:brk_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:brk_test")
+syscall_test(
+ test = "//test/syscalls/linux:socket_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:socket_test")
+syscall_test(
+ test = "//test/syscalls/linux:socket_capability_test",
+ vfs2 = "True",
+)
syscall_test(
size = "large",
@@ -51,16 +82,19 @@ syscall_test(
# involve much concurrency, TSAN's usefulness here is limited anyway.
tags = ["nogotsan"],
test = "//test/syscalls/linux:socket_stress_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:chdir_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:chmod_test",
+ vfs2 = "True",
)
syscall_test(
@@ -68,77 +102,116 @@ syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:chown_test",
use_tmpfs = True, # chwon tests require gofer to be running as root.
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:chroot_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:clock_getres_test")
+syscall_test(
+ test = "//test/syscalls/linux:clock_getres_test",
+ vfs2 = "True",
+)
syscall_test(
size = "medium",
test = "//test/syscalls/linux:clock_gettime_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:clock_nanosleep_test")
+syscall_test(
+ test = "//test/syscalls/linux:clock_nanosleep_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:concurrency_test")
+syscall_test(
+ test = "//test/syscalls/linux:concurrency_test",
+ vfs2 = "True",
+)
syscall_test(
add_uds_tree = True,
test = "//test/syscalls/linux:connect_external_test",
use_tmpfs = True,
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:creat_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:dev_test")
+syscall_test(
+ fuse = "True",
+ test = "//test/syscalls/linux:dev_test",
+ vfs2 = "True",
+)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:dup_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:epoll_test")
+syscall_test(
+ test = "//test/syscalls/linux:epoll_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:eventfd_test")
+syscall_test(
+ test = "//test/syscalls/linux:eventfd_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:exceptions_test")
+syscall_test(
+ test = "//test/syscalls/linux:exceptions_test",
+ vfs2 = "True",
+)
syscall_test(
size = "medium",
add_overlay = True,
test = "//test/syscalls/linux:exec_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
add_overlay = True,
test = "//test/syscalls/linux:exec_binary_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:exit_test")
+syscall_test(
+ test = "//test/syscalls/linux:exit_test",
+ vfs2 = "True",
+)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:fadvise64_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:fallocate_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:fault_test")
+syscall_test(
+ test = "//test/syscalls/linux:fault_test",
+ vfs2 = "True",
+)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:fchdir_test",
+ vfs2 = "True",
)
syscall_test(
@@ -150,326 +223,505 @@ syscall_test(
size = "medium",
add_overlay = True,
test = "//test/syscalls/linux:flock_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:fork_test")
+syscall_test(
+ test = "//test/syscalls/linux:fork_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:fpsig_fork_test")
+syscall_test(
+ test = "//test/syscalls/linux:fpsig_fork_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:fpsig_nested_test")
+syscall_test(
+ test = "//test/syscalls/linux:fpsig_nested_test",
+ vfs2 = "True",
+)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:fsync_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
shard_count = 5,
test = "//test/syscalls/linux:futex_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:getcpu_host_test")
+syscall_test(
+ test = "//test/syscalls/linux:getcpu_host_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:getcpu_test")
+syscall_test(
+ test = "//test/syscalls/linux:getcpu_test",
+ vfs2 = "True",
+)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:getdents_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:getrandom_test")
+syscall_test(
+ test = "//test/syscalls/linux:getrandom_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:getrusage_test")
+syscall_test(
+ test = "//test/syscalls/linux:getrusage_test",
+ vfs2 = "True",
+)
syscall_test(
size = "medium",
add_overlay = False, # TODO(gvisor.dev/issue/317): enable when fixed.
test = "//test/syscalls/linux:inotify_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
add_overlay = True,
test = "//test/syscalls/linux:ioctl_test",
+ vfs2 = "True",
)
syscall_test(
test = "//test/syscalls/linux:iptables_test",
+ vfs2 = "True",
)
syscall_test(
size = "large",
shard_count = 5,
test = "//test/syscalls/linux:itimer_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:kill_test")
+syscall_test(
+ test = "//test/syscalls/linux:kill_test",
+ vfs2 = "True",
+)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:link_test",
use_tmpfs = True, # gofer needs CAP_DAC_READ_SEARCH to use AT_EMPTY_PATH with linkat(2)
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:lseek_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:madvise_test")
+syscall_test(
+ test = "//test/syscalls/linux:madvise_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:memory_accounting_test")
+syscall_test(
+ test = "//test/syscalls/linux:memory_accounting_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:mempolicy_test")
+syscall_test(
+ test = "//test/syscalls/linux:mempolicy_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:mincore_test")
+syscall_test(
+ test = "//test/syscalls/linux:mincore_test",
+ vfs2 = "True",
+)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:mkdir_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:mknod_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
shard_count = 5,
test = "//test/syscalls/linux:mmap_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:mount_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
test = "//test/syscalls/linux:mremap_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
test = "//test/syscalls/linux:msync_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:munmap_test")
+syscall_test(
+ test = "//test/syscalls/linux:munmap_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:network_namespace_test")
+syscall_test(
+ test = "//test/syscalls/linux:network_namespace_test",
+ vfs2 = "True",
+)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:open_create_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:open_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:packet_socket_raw_test")
+syscall_test(
+ test = "//test/syscalls/linux:packet_socket_raw_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:packet_socket_test")
+syscall_test(
+ test = "//test/syscalls/linux:packet_socket_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:partial_bad_buffer_test")
+syscall_test(
+ test = "//test/syscalls/linux:partial_bad_buffer_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:pause_test")
+syscall_test(
+ test = "//test/syscalls/linux:pause_test",
+ vfs2 = "True",
+)
+
+syscall_test(
+ size = "medium",
+ # Takes too long under gotsan to run.
+ tags = ["nogotsan"],
+ test = "//test/syscalls/linux:ping_socket_test",
+ vfs2 = "True",
+)
syscall_test(
size = "large",
add_overlay = True,
shard_count = 5,
test = "//test/syscalls/linux:pipe_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:poll_test")
+syscall_test(
+ test = "//test/syscalls/linux:poll_test",
+ vfs2 = "True",
+)
syscall_test(
size = "medium",
test = "//test/syscalls/linux:ppoll_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:prctl_setuid_test")
+syscall_test(
+ test = "//test/syscalls/linux:prctl_setuid_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:prctl_test")
+syscall_test(
+ test = "//test/syscalls/linux:prctl_test",
+ vfs2 = "True",
+)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:pread64_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:preadv_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:preadv2_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:priority_test")
+syscall_test(
+ test = "//test/syscalls/linux:priority_test",
+ vfs2 = "True",
+)
syscall_test(
size = "medium",
test = "//test/syscalls/linux:proc_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:proc_net_test")
+syscall_test(
+ test = "//test/syscalls/linux:proc_net_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:proc_pid_oomscore_test")
+syscall_test(
+ test = "//test/syscalls/linux:proc_pid_oomscore_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:proc_pid_smaps_test")
+syscall_test(
+ test = "//test/syscalls/linux:proc_pid_smaps_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:proc_pid_uid_gid_map_test")
+syscall_test(
+ test = "//test/syscalls/linux:proc_pid_uid_gid_map_test",
+ vfs2 = "True",
+)
syscall_test(
size = "medium",
test = "//test/syscalls/linux:pselect_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:ptrace_test")
+syscall_test(
+ test = "//test/syscalls/linux:ptrace_test",
+ vfs2 = "True",
+)
syscall_test(
size = "medium",
shard_count = 5,
test = "//test/syscalls/linux:pty_test",
+ vfs2 = "True",
)
syscall_test(
test = "//test/syscalls/linux:pty_root_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:pwritev2_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:pwrite64_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:raw_socket_hdrincl_test")
+syscall_test(
+ test = "//test/syscalls/linux:raw_socket_hdrincl_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:raw_socket_icmp_test")
+syscall_test(
+ test = "//test/syscalls/linux:raw_socket_icmp_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:raw_socket_ipv4_test")
+syscall_test(
+ test = "//test/syscalls/linux:raw_socket_test",
+ vfs2 = "True",
+)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:read_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:readahead_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
shard_count = 5,
test = "//test/syscalls/linux:readv_socket_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
add_overlay = True,
test = "//test/syscalls/linux:readv_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
add_overlay = True,
test = "//test/syscalls/linux:rename_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:rlimits_test")
+syscall_test(
+ test = "//test/syscalls/linux:rlimits_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:rseq_test")
+syscall_test(
+ test = "//test/syscalls/linux:rseq_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:rtsignal_test")
+syscall_test(
+ test = "//test/syscalls/linux:rtsignal_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:signalfd_test")
+syscall_test(
+ test = "//test/syscalls/linux:signalfd_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:sched_test")
+syscall_test(
+ test = "//test/syscalls/linux:sched_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:sched_yield_test")
+syscall_test(
+ test = "//test/syscalls/linux:sched_yield_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:seccomp_test")
+syscall_test(
+ test = "//test/syscalls/linux:seccomp_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:select_test")
+syscall_test(
+ test = "//test/syscalls/linux:select_test",
+ vfs2 = "True",
+)
syscall_test(
shard_count = 20,
test = "//test/syscalls/linux:semaphore_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:sendfile_socket_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:sendfile_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:splice_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:sigaction_test")
+syscall_test(
+ test = "//test/syscalls/linux:sigaction_test",
+ vfs2 = "True",
+)
# TODO(b/119826902): Enable once the test passes in runsc.
-# syscall_test(test = "//test/syscalls/linux:sigaltstack_test")
+# syscall_test(vfs2="True",test = "//test/syscalls/linux:sigaltstack_test")
-syscall_test(test = "//test/syscalls/linux:sigiret_test")
+syscall_test(
+ test = "//test/syscalls/linux:sigiret_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:sigprocmask_test")
+syscall_test(
+ test = "//test/syscalls/linux:sigprocmask_test",
+ vfs2 = "True",
+)
syscall_test(
size = "medium",
test = "//test/syscalls/linux:sigstop_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:sigtimedwait_test")
+syscall_test(
+ test = "//test/syscalls/linux:sigtimedwait_test",
+ vfs2 = "True",
+)
syscall_test(
size = "medium",
test = "//test/syscalls/linux:shm_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
test = "//test/syscalls/linux:socket_abstract_non_blocking_test",
+ vfs2 = "True",
)
syscall_test(
size = "large",
shard_count = 50,
test = "//test/syscalls/linux:socket_abstract_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
test = "//test/syscalls/linux:socket_domain_non_blocking_test",
+ vfs2 = "True",
)
syscall_test(
size = "large",
shard_count = 50,
test = "//test/syscalls/linux:socket_domain_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
add_overlay = True,
test = "//test/syscalls/linux:socket_filesystem_non_blocking_test",
+ vfs2 = "True",
)
syscall_test(
@@ -477,89 +729,138 @@ syscall_test(
add_overlay = True,
shard_count = 50,
test = "//test/syscalls/linux:socket_filesystem_test",
+ vfs2 = "True",
)
syscall_test(
size = "large",
shard_count = 50,
test = "//test/syscalls/linux:socket_inet_loopback_test",
+ vfs2 = "True",
+)
+
+syscall_test(
+ size = "large",
+ shard_count = 50,
+ # Takes too long for TSAN. Creates a lot of TCP sockets.
+ tags = ["nogotsan"],
+ test = "//test/syscalls/linux:socket_inet_loopback_nogotsan_test",
+ vfs2 = "True",
)
syscall_test(
size = "large",
shard_count = 50,
test = "//test/syscalls/linux:socket_ip_tcp_generic_loopback_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
test = "//test/syscalls/linux:socket_ip_tcp_loopback_non_blocking_test",
+ vfs2 = "True",
)
syscall_test(
size = "large",
shard_count = 50,
test = "//test/syscalls/linux:socket_ip_tcp_loopback_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
shard_count = 50,
test = "//test/syscalls/linux:socket_ip_tcp_udp_generic_loopback_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
test = "//test/syscalls/linux:socket_ip_udp_loopback_non_blocking_test",
+ vfs2 = "True",
)
syscall_test(
size = "large",
shard_count = 50,
test = "//test/syscalls/linux:socket_ip_udp_loopback_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
test = "//test/syscalls/linux:socket_ipv4_udp_unbound_loopback_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:socket_ip_unbound_test")
+syscall_test(
+ test = "//test/syscalls/linux:socket_ip_unbound_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:socket_netdevice_test")
+syscall_test(
+ test = "//test/syscalls/linux:socket_netdevice_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:socket_netlink_test")
+syscall_test(
+ test = "//test/syscalls/linux:socket_netlink_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:socket_netlink_route_test")
+syscall_test(
+ test = "//test/syscalls/linux:socket_netlink_route_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:socket_netlink_uevent_test")
+syscall_test(
+ test = "//test/syscalls/linux:socket_netlink_uevent_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:socket_blocking_local_test")
+syscall_test(
+ test = "//test/syscalls/linux:socket_blocking_local_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:socket_blocking_ip_test")
+syscall_test(
+ test = "//test/syscalls/linux:socket_blocking_ip_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:socket_non_stream_blocking_local_test")
+syscall_test(
+ test = "//test/syscalls/linux:socket_non_stream_blocking_local_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:socket_non_stream_blocking_udp_test")
+syscall_test(
+ test = "//test/syscalls/linux:socket_non_stream_blocking_udp_test",
+ vfs2 = "True",
+)
syscall_test(
size = "large",
test = "//test/syscalls/linux:socket_stream_blocking_local_test",
+ vfs2 = "True",
)
syscall_test(
size = "large",
test = "//test/syscalls/linux:socket_stream_blocking_tcp_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
test = "//test/syscalls/linux:socket_stream_local_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
test = "//test/syscalls/linux:socket_stream_nonblock_local_test",
+ vfs2 = "True",
)
syscall_test(
@@ -567,11 +868,13 @@ syscall_test(
size = "enormous",
shard_count = 5,
test = "//test/syscalls/linux:socket_unix_dgram_local_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
test = "//test/syscalls/linux:socket_unix_dgram_non_blocking_test",
+ vfs2 = "True",
)
syscall_test(
@@ -579,6 +882,7 @@ syscall_test(
add_overlay = True,
shard_count = 50,
test = "//test/syscalls/linux:socket_unix_pair_test",
+ vfs2 = "True",
)
syscall_test(
@@ -586,155 +890,236 @@ syscall_test(
size = "enormous",
shard_count = 5,
test = "//test/syscalls/linux:socket_unix_seqpacket_local_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
test = "//test/syscalls/linux:socket_unix_stream_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
test = "//test/syscalls/linux:socket_unix_unbound_abstract_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
test = "//test/syscalls/linux:socket_unix_unbound_dgram_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
test = "//test/syscalls/linux:socket_unix_unbound_filesystem_test",
+ vfs2 = "True",
)
syscall_test(
size = "medium",
shard_count = 10,
test = "//test/syscalls/linux:socket_unix_unbound_seqpacket_test",
+ vfs2 = "True",
)
syscall_test(
size = "large",
shard_count = 50,
test = "//test/syscalls/linux:socket_unix_unbound_stream_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:statfs_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:stat_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:stat_times_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:sticky_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:symlink_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:sync_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:sync_file_range_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:sysinfo_test")
+syscall_test(
+ test = "//test/syscalls/linux:sysinfo_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:syslog_test")
+syscall_test(
+ test = "//test/syscalls/linux:syslog_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:sysret_test")
+syscall_test(
+ test = "//test/syscalls/linux:sysret_test",
+ vfs2 = "True",
+)
syscall_test(
size = "medium",
shard_count = 10,
test = "//test/syscalls/linux:tcp_socket_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:tgkill_test")
+syscall_test(
+ test = "//test/syscalls/linux:tgkill_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:timerfd_test")
+syscall_test(
+ test = "//test/syscalls/linux:timerfd_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:timers_test")
+syscall_test(
+ test = "//test/syscalls/linux:timers_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:time_test")
+syscall_test(
+ test = "//test/syscalls/linux:time_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:tkill_test")
+syscall_test(
+ test = "//test/syscalls/linux:tkill_test",
+ vfs2 = "True",
+)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:truncate_test",
)
-syscall_test(test = "//test/syscalls/linux:tuntap_test")
+syscall_test(
+ test = "//test/syscalls/linux:tuntap_test",
+ vfs2 = "True",
+)
syscall_test(
add_hostinet = True,
test = "//test/syscalls/linux:tuntap_hostinet_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:udp_bind_test")
+syscall_test(
+ test = "//test/syscalls/linux:udp_bind_test",
+ vfs2 = "True",
+)
syscall_test(
size = "medium",
add_hostinet = True,
shard_count = 10,
test = "//test/syscalls/linux:udp_socket_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:uidgid_test")
+syscall_test(
+ test = "//test/syscalls/linux:uidgid_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:uname_test")
+syscall_test(
+ test = "//test/syscalls/linux:uname_test",
+ vfs2 = "True",
+)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:unlink_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:unshare_test")
+syscall_test(
+ test = "//test/syscalls/linux:unshare_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:utimes_test")
+syscall_test(
+ test = "//test/syscalls/linux:utimes_test",
+ vfs2 = "True",
+)
syscall_test(
size = "medium",
test = "//test/syscalls/linux:vdso_clock_gettime_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:vdso_test")
+syscall_test(
+ test = "//test/syscalls/linux:vdso_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:vsyscall_test")
+syscall_test(
+ test = "//test/syscalls/linux:vsyscall_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:vfork_test")
+syscall_test(
+ test = "//test/syscalls/linux:vfork_test",
+ vfs2 = "True",
+)
syscall_test(
size = "medium",
shard_count = 5,
test = "//test/syscalls/linux:wait_test",
+ vfs2 = "True",
)
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:write_test",
+ vfs2 = "True",
)
-syscall_test(test = "//test/syscalls/linux:proc_net_unix_test")
+syscall_test(
+ test = "//test/syscalls/linux:proc_net_unix_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:proc_net_tcp_test")
+syscall_test(
+ test = "//test/syscalls/linux:proc_net_tcp_test",
+ vfs2 = "True",
+)
-syscall_test(test = "//test/syscalls/linux:proc_net_udp_test")
+syscall_test(
+ test = "//test/syscalls/linux:proc_net_udp_test",
+ vfs2 = "True",
+)
diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD
index 5acdb8438..66a31cd28 100644
--- a/test/syscalls/linux/BUILD
+++ b/test/syscalls/linux/BUILD
@@ -363,8 +363,8 @@ cc_binary(
linkstatic = 1,
deps = [
":socket_test_util",
- "//test/util:file_descriptor",
gtest,
+ "//test/util:file_descriptor",
"//test/util:temp_umask",
"//test/util:test_main",
"//test/util:test_util",
@@ -372,6 +372,21 @@ cc_binary(
)
cc_binary(
+ name = "socket_capability_test",
+ testonly = 1,
+ srcs = ["socket_capability.cc"],
+ linkstatic = 1,
+ deps = [
+ ":socket_test_util",
+ "//test/util:capability_util",
+ "//test/util:file_descriptor",
+ gtest,
+ "//test/util:test_main",
+ "//test/util:test_util",
+ ],
+)
+
+cc_binary(
name = "brk_test",
testonly = 1,
srcs = ["brk.cc"],
@@ -733,9 +748,14 @@ cc_binary(
linkstatic = 1,
deps = [
":file_base",
+ ":socket_test_util",
"//test/util:cleanup",
+ "//test/util:eventfd_util",
"//test/util:file_descriptor",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/time",
gtest,
+ "//test/util:posix_error",
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
@@ -776,6 +796,7 @@ cc_binary(
deps = [
":socket_test_util",
"//test/util:cleanup",
+ "//test/util:epoll_util",
"//test/util:eventfd_util",
"//test/util:fs_util",
"@com_google_absl//absl/base:core_headers",
@@ -789,6 +810,7 @@ cc_binary(
"//test/util:save_util",
"//test/util:temp_path",
"//test/util:test_util",
+ "//test/util:thread_util",
"//test/util:timer_util",
],
)
@@ -802,10 +824,13 @@ cc_binary(
],
linkstatic = 1,
deps = [
+ ":socket_test_util",
"//test/util:file_descriptor",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
gtest,
+ "//test/util:epoll_util",
+ "//test/util:eventfd_util",
"//test/util:posix_error",
"//test/util:temp_path",
"//test/util:test_main",
@@ -849,10 +874,7 @@ cc_binary(
cc_binary(
name = "fpsig_nested_test",
testonly = 1,
- srcs = select_arch(
- amd64 = ["fpsig_nested.cc"],
- arm64 = [],
- ),
+ srcs = ["fpsig_nested.cc"],
linkstatic = 1,
deps = [
gtest,
@@ -921,6 +943,7 @@ cc_binary(
"//test/util:eventfd_util",
"//test/util:file_descriptor",
"//test/util:fs_util",
+ "@com_google_absl//absl/container:node_hash_set",
"@com_google_absl//absl/strings",
gtest,
"//test/util:posix_error",
@@ -951,12 +974,14 @@ cc_binary(
"//test/util:epoll_util",
"//test/util:file_descriptor",
"//test/util:fs_util",
+ "//test/util:posix_error",
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
"//test/util:thread_util",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
+ "@com_google_absl//absl/synchronization",
"@com_google_absl//absl/time",
],
)
@@ -1306,6 +1331,7 @@ cc_binary(
name = "packet_socket_raw_test",
testonly = 1,
srcs = ["packet_socket_raw.cc"],
+ defines = select_system(),
linkstatic = 1,
deps = [
":socket_test_util",
@@ -1382,7 +1408,7 @@ cc_binary(
srcs = ["partial_bad_buffer.cc"],
linkstatic = 1,
deps = [
- "//test/syscalls/linux:socket_test_util",
+ ":socket_test_util",
"//test/util:file_descriptor",
"//test/util:fs_util",
"@com_google_absl//absl/time",
@@ -1411,6 +1437,21 @@ cc_binary(
)
cc_binary(
+ name = "ping_socket_test",
+ testonly = 1,
+ srcs = ["ping_socket.cc"],
+ linkstatic = 1,
+ deps = [
+ ":socket_test_util",
+ "//test/util:file_descriptor",
+ gtest,
+ "//test/util:save_util",
+ "//test/util:test_main",
+ "//test/util:test_util",
+ ],
+)
+
+cc_binary(
name = "pipe_test",
testonly = 1,
srcs = ["pipe.cc"],
@@ -1767,9 +1808,10 @@ cc_binary(
)
cc_binary(
- name = "raw_socket_ipv4_test",
+ name = "raw_socket_test",
testonly = 1,
- srcs = ["raw_socket_ipv4.cc"],
+ srcs = ["raw_socket.cc"],
+ defines = select_system(),
linkstatic = 1,
deps = [
":socket_test_util",
@@ -2780,6 +2822,26 @@ cc_binary(
)
cc_binary(
+ name = "socket_inet_loopback_nogotsan_test",
+ testonly = 1,
+ srcs = ["socket_inet_loopback_nogotsan.cc"],
+ linkstatic = 1,
+ deps = [
+ ":ip_socket_test_util",
+ ":socket_test_util",
+ "//test/util:file_descriptor",
+ "@com_google_absl//absl/memory",
+ "@com_google_absl//absl/strings",
+ gtest,
+ "//test/util:posix_error",
+ "//test/util:save_util",
+ "//test/util:test_main",
+ "//test/util:test_util",
+ "//test/util:thread_util",
+ ],
+)
+
+cc_binary(
name = "socket_netlink_test",
testonly = 1,
srcs = ["socket_netlink.cc"],
@@ -3348,6 +3410,7 @@ cc_binary(
name = "tcp_socket_test",
testonly = 1,
srcs = ["tcp_socket.cc"],
+ defines = select_system(),
linkstatic = 1,
deps = [
":socket_test_util",
@@ -3461,7 +3524,7 @@ cc_binary(
deps = [
":socket_test_util",
gtest,
- "//test/syscalls/linux:socket_netlink_route_util",
+ ":socket_netlink_route_util",
"//test/util:capability_util",
"//test/util:file_descriptor",
"//test/util:fs_util",
@@ -3494,11 +3557,15 @@ cc_library(
hdrs = ["udp_socket_test_cases.h"],
defines = select_system(),
deps = [
+ ":ip_socket_test_util",
":socket_test_util",
":unix_domain_socket_test_util",
"@com_google_absl//absl/base:core_headers",
+ "@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/time",
gtest,
+ "//test/util:file_descriptor",
+ "//test/util:posix_error",
"//test/util:test_main",
"//test/util:test_util",
"//test/util:thread_util",
diff --git a/test/syscalls/linux/accept_bind.cc b/test/syscalls/linux/accept_bind.cc
index e08c578f0..f65a14fb8 100644
--- a/test/syscalls/linux/accept_bind.cc
+++ b/test/syscalls/linux/accept_bind.cc
@@ -13,6 +13,7 @@
// limitations under the License.
#include <stdio.h>
+#include <sys/socket.h>
#include <sys/un.h>
#include <algorithm>
@@ -141,6 +142,47 @@ TEST_P(AllSocketPairTest, Connect) {
SyscallSucceeds());
}
+TEST_P(AllSocketPairTest, ConnectWithWrongType) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+
+ int type;
+ socklen_t typelen = sizeof(type);
+ EXPECT_THAT(
+ getsockopt(sockets->first_fd(), SOL_SOCKET, SO_TYPE, &type, &typelen),
+ SyscallSucceeds());
+ switch (type) {
+ case SOCK_STREAM:
+ type = SOCK_SEQPACKET;
+ break;
+ case SOCK_SEQPACKET:
+ type = SOCK_STREAM;
+ break;
+ }
+
+ const FileDescriptor another_socket =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, type, 0));
+
+ ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
+ sockets->first_addr_size()),
+ SyscallSucceeds());
+
+ ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
+
+ if (sockets->first_addr()->sa_data[0] != 0) {
+ ASSERT_THAT(connect(another_socket.get(), sockets->first_addr(),
+ sockets->first_addr_size()),
+ SyscallFailsWithErrno(EPROTOTYPE));
+ } else {
+ ASSERT_THAT(connect(another_socket.get(), sockets->first_addr(),
+ sockets->first_addr_size()),
+ SyscallFailsWithErrno(ECONNREFUSED));
+ }
+
+ ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
+ sockets->first_addr_size()),
+ SyscallSucceeds());
+}
+
TEST_P(AllSocketPairTest, ConnectNonListening) {
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
diff --git a/test/syscalls/linux/dev.cc b/test/syscalls/linux/dev.cc
index 4dd302eed..1d0d584cd 100644
--- a/test/syscalls/linux/dev.cc
+++ b/test/syscalls/linux/dev.cc
@@ -153,6 +153,27 @@ TEST(DevTest, TTYExists) {
EXPECT_EQ(statbuf.st_mode, S_IFCHR | 0666);
}
+TEST(DevTest, OpenDevFuse) {
+ // Note(gvisor.dev/issue/3076) This won't work in the sentry until the new
+ // device registration is complete.
+ SKIP_IF(IsRunningWithVFS1() || IsRunningOnGvisor() || !IsFUSEEnabled());
+
+ ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/fuse", O_RDONLY));
+}
+
+TEST(DevTest, ReadDevFuseWithoutMount) {
+ // Note(gvisor.dev/issue/3076) This won't work in the sentry until the new
+ // device registration is complete.
+ SKIP_IF(IsRunningWithVFS1() || IsRunningOnGvisor());
+
+ const FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/fuse", O_RDONLY));
+
+ std::vector<char> buf(1);
+ EXPECT_THAT(ReadFd(fd.get(), buf.data(), sizeof(buf)),
+ SyscallFailsWithErrno(EPERM));
+}
+
} // namespace
} // namespace testing
diff --git a/test/syscalls/linux/epoll.cc b/test/syscalls/linux/epoll.cc
index f57d38dc7..2101e5c9f 100644
--- a/test/syscalls/linux/epoll.cc
+++ b/test/syscalls/linux/epoll.cc
@@ -422,6 +422,28 @@ TEST(EpollTest, CloseFile) {
SyscallSucceedsWithValue(0));
}
+TEST(EpollTest, PipeReaderHupAfterWriterClosed) {
+ auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
+ int pipefds[2];
+ ASSERT_THAT(pipe(pipefds), SyscallSucceeds());
+ FileDescriptor rfd(pipefds[0]);
+ FileDescriptor wfd(pipefds[1]);
+
+ ASSERT_NO_ERRNO(RegisterEpollFD(epollfd.get(), rfd.get(), 0, kMagicConstant));
+ struct epoll_event result[kFDsPerEpoll];
+ // Initially, rfd should not generate any events of interest.
+ ASSERT_THAT(epoll_wait(epollfd.get(), result, kFDsPerEpoll, 0),
+ SyscallSucceedsWithValue(0));
+ // Close the write end of the pipe.
+ wfd.reset();
+ // rfd should now generate EPOLLHUP, which EPOLL_CTL_ADD unconditionally adds
+ // to the set of events of interest.
+ ASSERT_THAT(epoll_wait(epollfd.get(), result, kFDsPerEpoll, 0),
+ SyscallSucceedsWithValue(1));
+ EXPECT_EQ(result[0].events, EPOLLHUP);
+ EXPECT_EQ(result[0].data.u64, kMagicConstant);
+}
+
} // namespace
} // namespace testing
diff --git a/test/syscalls/linux/eventfd.cc b/test/syscalls/linux/eventfd.cc
index 548b05a64..dc794415e 100644
--- a/test/syscalls/linux/eventfd.cc
+++ b/test/syscalls/linux/eventfd.cc
@@ -100,20 +100,21 @@ TEST(EventfdTest, SmallRead) {
ASSERT_THAT(read(efd.get(), &l, 4), SyscallFailsWithErrno(EINVAL));
}
-TEST(EventfdTest, PreadIllegalSeek) {
- FileDescriptor efd =
- ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_NONBLOCK | EFD_SEMAPHORE));
-
- uint64_t l = 0;
- ASSERT_THAT(pread(efd.get(), &l, 4, 0), SyscallFailsWithErrno(ESPIPE));
+TEST(EventfdTest, IllegalSeek) {
+ FileDescriptor efd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0));
+ EXPECT_THAT(lseek(efd.get(), 0, SEEK_SET), SyscallFailsWithErrno(ESPIPE));
}
-TEST(EventfdTest, PwriteIllegalSeek) {
- FileDescriptor efd =
- ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_NONBLOCK | EFD_SEMAPHORE));
+TEST(EventfdTest, IllegalPread) {
+ FileDescriptor efd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0));
+ int l;
+ EXPECT_THAT(pread(efd.get(), &l, sizeof(l), 0),
+ SyscallFailsWithErrno(ESPIPE));
+}
- uint64_t l = 0;
- ASSERT_THAT(pwrite(efd.get(), &l, 4, 0), SyscallFailsWithErrno(ESPIPE));
+TEST(EventfdTest, IllegalPwrite) {
+ FileDescriptor efd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0));
+ EXPECT_THAT(pwrite(efd.get(), "x", 1, 0), SyscallFailsWithErrno(ESPIPE));
}
TEST(EventfdTest, BigWrite) {
diff --git a/test/syscalls/linux/exec.cc b/test/syscalls/linux/exec.cc
index 12c9b05ca..c5acfc794 100644
--- a/test/syscalls/linux/exec.cc
+++ b/test/syscalls/linux/exec.cc
@@ -553,7 +553,12 @@ TEST(ExecTest, SymlinkLimitRefreshedForInterpreter) {
// Hold onto TempPath objects so they are not destructed prematurely.
std::vector<TempPath> interpreter_symlinks;
std::vector<TempPath> script_symlinks;
- for (int i = 0; i < kLinuxMaxSymlinks; i++) {
+ // Replace both the interpreter and script paths with symlink chains of just
+ // over half the symlink limit each; this is the minimum required to test that
+ // the symlink limit applies separately to each traversal, while tolerating
+ // some symlinks in the resolution of (the original) interpreter_path and
+ // script_path.
+ for (int i = 0; i < (kLinuxMaxSymlinks / 2) + 1; i++) {
interpreter_symlinks.push_back(ASSERT_NO_ERRNO_AND_VALUE(
TempPath::CreateSymlinkTo(tmp_dir, interpreter_path)));
interpreter_path = interpreter_symlinks[i].path();
@@ -673,6 +678,31 @@ TEST(ExecveatTest, SymlinkNoFollowWithRelativePath) {
EXPECT_EQ(execve_errno, ELOOP);
}
+TEST(ExecveatTest, UnshareFiles) {
+ TempPath tempFile = ASSERT_NO_ERRNO_AND_VALUE(
+ TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), "bar", 0755));
+ const FileDescriptor fd_closed_on_exec =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(tempFile.path(), O_RDONLY | O_CLOEXEC));
+
+ ExecveArray argv = {"test"};
+ ExecveArray envp;
+ std::string child_path = RunfilePath(kBasicWorkload);
+ pid_t child =
+ syscall(__NR_clone, SIGCHLD | CLONE_VFORK | CLONE_FILES, 0, 0, 0, 0);
+ if (child == 0) {
+ execve(child_path.c_str(), argv.get(), envp.get());
+ _exit(1);
+ }
+ ASSERT_THAT(child, SyscallSucceeds());
+
+ int status;
+ ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceeds());
+ EXPECT_EQ(status, 0);
+
+ struct stat st;
+ EXPECT_THAT(fstat(fd_closed_on_exec.get(), &st), SyscallSucceeds());
+}
+
TEST(ExecveatTest, SymlinkNoFollowWithAbsolutePath) {
std::string parent_dir = "/tmp";
TempPath link = ASSERT_NO_ERRNO_AND_VALUE(
diff --git a/test/syscalls/linux/exec_binary.cc b/test/syscalls/linux/exec_binary.cc
index 1a9f203b9..18d2f22c1 100644
--- a/test/syscalls/linux/exec_binary.cc
+++ b/test/syscalls/linux/exec_binary.cc
@@ -438,7 +438,12 @@ TEST(ElfTest, MissingText) {
ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0),
SyscallSucceedsWithValue(child));
// It runs off the end of the zeroes filling the end of the page.
+#if defined(__x86_64__)
EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV) << status;
+#elif defined(__aarch64__)
+ // 0 is an invalid instruction opcode on arm64.
+ EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGILL) << status;
+#endif
}
// Typical ELF with a data + bss segment
diff --git a/test/syscalls/linux/fallocate.cc b/test/syscalls/linux/fallocate.cc
index 7819f4ac3..cabc2b751 100644
--- a/test/syscalls/linux/fallocate.cc
+++ b/test/syscalls/linux/fallocate.cc
@@ -15,16 +15,27 @@
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
+#include <sys/eventfd.h>
#include <sys/resource.h>
+#include <sys/signalfd.h>
+#include <sys/socket.h>
#include <sys/stat.h>
+#include <sys/timerfd.h>
#include <syscall.h>
#include <time.h>
#include <unistd.h>
+#include <ctime>
+
#include "gtest/gtest.h"
+#include "absl/strings/str_cat.h"
+#include "absl/time/time.h"
#include "test/syscalls/linux/file_base.h"
+#include "test/syscalls/linux/socket_test_util.h"
#include "test/util/cleanup.h"
+#include "test/util/eventfd_util.h"
#include "test/util/file_descriptor.h"
+#include "test/util/posix_error.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
@@ -70,6 +81,12 @@ TEST_F(AllocateTest, Fallocate) {
ASSERT_THAT(fallocate(test_file_fd_.get(), 0, 39, 1), SyscallSucceeds());
ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds());
EXPECT_EQ(buf.st_size, 40);
+
+ // Given length 0 should fail with EINVAL.
+ ASSERT_THAT(fallocate(test_file_fd_.get(), 0, 50, 0),
+ SyscallFailsWithErrno(EINVAL));
+ ASSERT_THAT(fstat(test_file_fd_.get(), &buf), SyscallSucceeds());
+ EXPECT_EQ(buf.st_size, 40);
}
TEST_F(AllocateTest, FallocateInvalid) {
@@ -136,6 +153,34 @@ TEST_F(AllocateTest, FallocateRlimit) {
ASSERT_THAT(sigprocmask(SIG_UNBLOCK, &new_mask, nullptr), SyscallSucceeds());
}
+TEST_F(AllocateTest, FallocateOtherFDs) {
+ int fd;
+ ASSERT_THAT(fd = timerfd_create(CLOCK_MONOTONIC, 0), SyscallSucceeds());
+ auto timer_fd = FileDescriptor(fd);
+ EXPECT_THAT(fallocate(timer_fd.get(), 0, 0, 10),
+ SyscallFailsWithErrno(ENODEV));
+
+ sigset_t mask;
+ sigemptyset(&mask);
+ ASSERT_THAT(fd = signalfd(-1, &mask, 0), SyscallSucceeds());
+ auto sfd = FileDescriptor(fd);
+ EXPECT_THAT(fallocate(sfd.get(), 0, 0, 10), SyscallFailsWithErrno(ENODEV));
+
+ auto efd =
+ ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_NONBLOCK | EFD_SEMAPHORE));
+ EXPECT_THAT(fallocate(efd.get(), 0, 0, 10), SyscallFailsWithErrno(ENODEV));
+
+ auto sockfd = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
+ EXPECT_THAT(fallocate(sockfd.get(), 0, 0, 10), SyscallFailsWithErrno(ENODEV));
+
+ int socks[2];
+ ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, PF_UNIX, socks),
+ SyscallSucceeds());
+ auto sock0 = FileDescriptor(socks[0]);
+ auto sock1 = FileDescriptor(socks[1]);
+ EXPECT_THAT(fallocate(sock0.get(), 0, 0, 10), SyscallFailsWithErrno(ENODEV));
+}
+
} // namespace
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/fcntl.cc b/test/syscalls/linux/fcntl.cc
index c7cc5816e..34016d4bd 100644
--- a/test/syscalls/linux/fcntl.cc
+++ b/test/syscalls/linux/fcntl.cc
@@ -18,7 +18,10 @@
#include <syscall.h>
#include <unistd.h>
+#include <iostream>
+#include <list>
#include <string>
+#include <vector>
#include "gtest/gtest.h"
#include "absl/base/macros.h"
@@ -37,6 +40,7 @@
#include "test/util/save_util.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
+#include "test/util/thread_util.h"
#include "test/util/timer_util.h"
ABSL_FLAG(std::string, child_setlock_on, "",
@@ -115,6 +119,15 @@ PosixErrorOr<Cleanup> SubprocessLock(std::string const& path, bool for_write,
return std::move(cleanup);
}
+TEST(FcntlTest, SetCloExecBadFD) {
+ // Open an eventfd file descriptor with FD_CLOEXEC descriptor flag not set.
+ FileDescriptor f = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0));
+ auto fd = f.get();
+ f.reset();
+ ASSERT_THAT(fcntl(fd, F_GETFD), SyscallFailsWithErrno(EBADF));
+ ASSERT_THAT(fcntl(fd, F_SETFD, FD_CLOEXEC), SyscallFailsWithErrno(EBADF));
+}
+
TEST(FcntlTest, SetCloExec) {
// Open an eventfd file descriptor with FD_CLOEXEC descriptor flag not set.
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0));
@@ -182,45 +195,85 @@ TEST(FcntlTest, SetFlags) {
EXPECT_EQ(rflags, expected);
}
-TEST_F(FcntlLockTest, SetLockBadFd) {
+void TestLock(int fd, short lock_type = F_RDLCK) { // NOLINT, type in flock
struct flock fl;
- fl.l_type = F_WRLCK;
+ fl.l_type = lock_type;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
- // len 0 has a special meaning: lock all bytes despite how
- // large the file grows.
+ // len 0 locks all bytes despite how large the file grows.
fl.l_len = 0;
- EXPECT_THAT(fcntl(-1, F_SETLK, &fl), SyscallFailsWithErrno(EBADF));
+ EXPECT_THAT(fcntl(fd, F_SETLK, &fl), SyscallSucceeds());
}
-TEST_F(FcntlLockTest, SetLockPipe) {
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
-
+void TestLockBadFD(int fd,
+ short lock_type = F_RDLCK) { // NOLINT, type in flock
struct flock fl;
- fl.l_type = F_WRLCK;
+ fl.l_type = lock_type;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
- // Same as SetLockBadFd, but doesn't matter, we expect this to fail.
+ // len 0 locks all bytes despite how large the file grows.
fl.l_len = 0;
- EXPECT_THAT(fcntl(fds[0], F_SETLK, &fl), SyscallFailsWithErrno(EBADF));
- EXPECT_THAT(close(fds[0]), SyscallSucceeds());
- EXPECT_THAT(close(fds[1]), SyscallSucceeds());
+ EXPECT_THAT(fcntl(fd, F_SETLK, &fl), SyscallFailsWithErrno(EBADF));
}
+TEST_F(FcntlLockTest, SetLockBadFd) { TestLockBadFD(-1); }
+
TEST_F(FcntlLockTest, SetLockDir) {
auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY, 0666));
+ auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY, 0000));
+ TestLock(fd.get());
+}
- struct flock fl;
- fl.l_type = F_RDLCK;
- fl.l_whence = SEEK_SET;
- fl.l_start = 0;
- // Same as SetLockBadFd.
- fl.l_len = 0;
+TEST_F(FcntlLockTest, SetLockSymlink) {
+ // TODO(gvisor.dev/issue/2782): Replace with IsRunningWithVFS1() when O_PATH
+ // is supported.
+ SKIP_IF(IsRunningOnGvisor());
- EXPECT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds());
+ auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ auto symlink = ASSERT_NO_ERRNO_AND_VALUE(
+ TempPath::CreateSymlinkTo(GetAbsoluteTestTmpdir(), file.path()));
+
+ auto fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(symlink.path(), O_RDONLY | O_PATH, 0000));
+ TestLockBadFD(fd.get());
+}
+
+TEST_F(FcntlLockTest, SetLockProc) {
+ auto fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/status", O_RDONLY, 0000));
+ TestLock(fd.get());
+}
+
+TEST_F(FcntlLockTest, SetLockPipe) {
+ SKIP_IF(IsRunningWithVFS1());
+
+ int fds[2];
+ ASSERT_THAT(pipe(fds), SyscallSucceeds());
+
+ TestLock(fds[0]);
+ TestLockBadFD(fds[0], F_WRLCK);
+
+ TestLock(fds[1], F_WRLCK);
+ TestLockBadFD(fds[1]);
+
+ EXPECT_THAT(close(fds[0]), SyscallSucceeds());
+ EXPECT_THAT(close(fds[1]), SyscallSucceeds());
+}
+
+TEST_F(FcntlLockTest, SetLockSocket) {
+ SKIP_IF(IsRunningWithVFS1());
+
+ int sock = socket(AF_UNIX, SOCK_STREAM, 0);
+ ASSERT_THAT(sock, SyscallSucceeds());
+
+ struct sockaddr_un addr =
+ ASSERT_NO_ERRNO_AND_VALUE(UniqueUnixAddr(true /* abstract */, AF_UNIX));
+ ASSERT_THAT(
+ bind(sock, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)),
+ SyscallSucceeds());
+
+ TestLock(sock);
+ EXPECT_THAT(close(sock), SyscallSucceeds());
}
TEST_F(FcntlLockTest, SetLockBadOpenFlagsWrite) {
@@ -232,8 +285,7 @@ TEST_F(FcntlLockTest, SetLockBadOpenFlagsWrite) {
fl0.l_type = F_WRLCK;
fl0.l_whence = SEEK_SET;
fl0.l_start = 0;
- // Same as SetLockBadFd.
- fl0.l_len = 0;
+ fl0.l_len = 0; // Lock all file
// Expect that setting a write lock using a read only file descriptor
// won't work.
@@ -695,7 +747,7 @@ TEST_F(FcntlLockTest, SetWriteLockThenBlockingWriteLock) {
<< "Exited with code: " << status;
}
-// This test will veirfy that blocking works as expected when another process
+// This test will verify that blocking works as expected when another process
// holds a read lock when obtaining a write lock. This test will hold the lock
// for some amount of time and then wait for the second process to send over the
// socket_fd the amount of time it was blocked for before the lock succeeded.
@@ -905,15 +957,18 @@ TEST(FcntlTest, DupAfterO_ASYNC) {
EXPECT_EQ(after & O_ASYNC, O_ASYNC);
}
-TEST(FcntlTest, GetOwn) {
+TEST(FcntlTest, GetOwnNone) {
FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
- EXPECT_EQ(syscall(__NR_fcntl, s.get(), F_GETOWN), 0);
+ // Use the raw syscall because the glibc wrapper may convert F_{GET,SET}OWN
+ // into F_{GET,SET}OWN_EX.
+ EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN),
+ SyscallSucceedsWithValue(0));
MaybeSave();
}
-TEST(FcntlTest, GetOwnEx) {
+TEST(FcntlTest, GetOwnExNone) {
FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
@@ -922,6 +977,100 @@ TEST(FcntlTest, GetOwnEx) {
SyscallSucceedsWithValue(0));
}
+TEST(FcntlTest, SetOwnInvalidPid) {
+ SKIP_IF(IsRunningWithVFS1());
+
+ FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
+
+ EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, 12345678),
+ SyscallFailsWithErrno(ESRCH));
+}
+
+TEST(FcntlTest, SetOwnInvalidPgrp) {
+ SKIP_IF(IsRunningWithVFS1());
+
+ FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
+
+ EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, -12345678),
+ SyscallFailsWithErrno(ESRCH));
+}
+
+TEST(FcntlTest, SetOwnPid) {
+ FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
+
+ pid_t pid;
+ EXPECT_THAT(pid = getpid(), SyscallSucceeds());
+
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, pid),
+ SyscallSucceedsWithValue(0));
+
+ EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN),
+ SyscallSucceedsWithValue(pid));
+ MaybeSave();
+}
+
+TEST(FcntlTest, SetOwnPgrp) {
+ FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
+
+ pid_t pgid;
+ EXPECT_THAT(pgid = getpgrp(), SyscallSucceeds());
+
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, -pgid),
+ SyscallSucceedsWithValue(0));
+
+ // Verify with F_GETOWN_EX; using F_GETOWN on Linux may incorrectly treat the
+ // negative return value as an error, converting the return value to -1 and
+ // setting errno accordingly.
+ f_owner_ex got_owner = {};
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN_EX, &got_owner),
+ SyscallSucceedsWithValue(0));
+ EXPECT_EQ(got_owner.type, F_OWNER_PGRP);
+ EXPECT_EQ(got_owner.pid, pgid);
+ MaybeSave();
+}
+
+TEST(FcntlTest, SetOwnUnset) {
+ FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
+
+ // Set and unset pid.
+ pid_t pid;
+ EXPECT_THAT(pid = getpid(), SyscallSucceeds());
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, pid),
+ SyscallSucceedsWithValue(0));
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, 0),
+ SyscallSucceedsWithValue(0));
+
+ EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN),
+ SyscallSucceedsWithValue(0));
+
+ // Set and unset pgid.
+ pid_t pgid;
+ EXPECT_THAT(pgid = getpgrp(), SyscallSucceeds());
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, -pgid),
+ SyscallSucceedsWithValue(0));
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, 0),
+ SyscallSucceedsWithValue(0));
+
+ EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN),
+ SyscallSucceedsWithValue(0));
+ MaybeSave();
+}
+
+// F_SETOWN flips the sign of negative values, an operation that is guarded
+// against overflow.
+TEST(FcntlTest, SetOwnOverflow) {
+ FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
+
+ EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, INT_MIN),
+ SyscallFailsWithErrno(EINVAL));
+}
+
TEST(FcntlTest, SetOwnExInvalidType) {
FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
@@ -977,9 +1126,10 @@ TEST(FcntlTest, SetOwnExTid) {
EXPECT_THAT(owner.pid = syscall(__NR_gettid), SyscallSucceeds());
ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &owner),
- SyscallSucceeds());
+ SyscallSucceedsWithValue(0));
- EXPECT_EQ(syscall(__NR_fcntl, s.get(), F_GETOWN), owner.pid);
+ EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN),
+ SyscallSucceedsWithValue(owner.pid));
MaybeSave();
}
@@ -992,9 +1142,10 @@ TEST(FcntlTest, SetOwnExPid) {
EXPECT_THAT(owner.pid = getpid(), SyscallSucceeds());
ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &owner),
- SyscallSucceeds());
+ SyscallSucceedsWithValue(0));
- EXPECT_EQ(syscall(__NR_fcntl, s.get(), F_GETOWN), owner.pid);
+ EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN),
+ SyscallSucceedsWithValue(owner.pid));
MaybeSave();
}
@@ -1002,18 +1153,54 @@ TEST(FcntlTest, SetOwnExPgrp) {
FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
+ f_owner_ex set_owner = {};
+ set_owner.type = F_OWNER_PGRP;
+ EXPECT_THAT(set_owner.pid = getpgrp(), SyscallSucceeds());
+
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &set_owner),
+ SyscallSucceedsWithValue(0));
+
+ // Verify with F_GETOWN_EX; using F_GETOWN on Linux may incorrectly treat the
+ // negative return value as an error, converting the return value to -1 and
+ // setting errno accordingly.
+ f_owner_ex got_owner = {};
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN_EX, &got_owner),
+ SyscallSucceedsWithValue(0));
+ EXPECT_EQ(got_owner.type, set_owner.type);
+ EXPECT_EQ(got_owner.pid, set_owner.pid);
+ MaybeSave();
+}
+
+TEST(FcntlTest, SetOwnExUnset) {
+ SKIP_IF(IsRunningWithVFS1());
+
+ FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
+
+ // Set and unset pid.
f_owner_ex owner = {};
+ owner.type = F_OWNER_PID;
+ EXPECT_THAT(owner.pid = getpid(), SyscallSucceeds());
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &owner),
+ SyscallSucceedsWithValue(0));
+ owner.pid = 0;
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &owner),
+ SyscallSucceedsWithValue(0));
+
+ EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN),
+ SyscallSucceedsWithValue(0));
+
+ // Set and unset pgid.
owner.type = F_OWNER_PGRP;
EXPECT_THAT(owner.pid = getpgrp(), SyscallSucceeds());
-
ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &owner),
- SyscallSucceeds());
-
- // NOTE(igudger): I don't understand why, but this is flaky on Linux.
- // GetOwnExPgrp (below) does not have this issue.
- SKIP_IF(!IsRunningOnGvisor());
+ SyscallSucceedsWithValue(0));
+ owner.pid = 0;
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &owner),
+ SyscallSucceedsWithValue(0));
- EXPECT_EQ(syscall(__NR_fcntl, s.get(), F_GETOWN), -owner.pid);
+ EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN),
+ SyscallSucceedsWithValue(0));
MaybeSave();
}
@@ -1026,7 +1213,7 @@ TEST(FcntlTest, GetOwnExTid) {
EXPECT_THAT(set_owner.pid = syscall(__NR_gettid), SyscallSucceeds());
ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &set_owner),
- SyscallSucceeds());
+ SyscallSucceedsWithValue(0));
f_owner_ex got_owner = {};
ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN_EX, &got_owner),
@@ -1044,7 +1231,7 @@ TEST(FcntlTest, GetOwnExPid) {
EXPECT_THAT(set_owner.pid = getpid(), SyscallSucceeds());
ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &set_owner),
- SyscallSucceeds());
+ SyscallSucceedsWithValue(0));
f_owner_ex got_owner = {};
ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN_EX, &got_owner),
@@ -1062,7 +1249,7 @@ TEST(FcntlTest, GetOwnExPgrp) {
EXPECT_THAT(set_owner.pid = getpgrp(), SyscallSucceeds());
ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN_EX, &set_owner),
- SyscallSucceeds());
+ SyscallSucceedsWithValue(0));
f_owner_ex got_owner = {};
ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN_EX, &got_owner),
@@ -1071,6 +1258,45 @@ TEST(FcntlTest, GetOwnExPgrp) {
EXPECT_EQ(got_owner.pid, set_owner.pid);
}
+// Make sure that making multiple concurrent changes to async signal generation
+// does not cause any race issues.
+TEST(FcntlTest, SetFlSetOwnDoNotRace) {
+ FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
+
+ pid_t pid;
+ EXPECT_THAT(pid = getpid(), SyscallSucceeds());
+
+ constexpr absl::Duration runtime = absl::Milliseconds(300);
+ auto setAsync = [&s, &runtime] {
+ for (auto start = absl::Now(); absl::Now() - start < runtime;) {
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETFL, O_ASYNC),
+ SyscallSucceeds());
+ sched_yield();
+ }
+ };
+ auto resetAsync = [&s, &runtime] {
+ for (auto start = absl::Now(); absl::Now() - start < runtime;) {
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETFL, 0), SyscallSucceeds());
+ sched_yield();
+ }
+ };
+ auto setOwn = [&s, &pid, &runtime] {
+ for (auto start = absl::Now(); absl::Now() - start < runtime;) {
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETOWN, pid),
+ SyscallSucceeds());
+ sched_yield();
+ }
+ };
+
+ std::list<ScopedThread> threads;
+ for (int i = 0; i < 10; i++) {
+ threads.emplace_back(setAsync);
+ threads.emplace_back(resetAsync);
+ threads.emplace_back(setOwn);
+ }
+}
+
} // namespace
} // namespace testing
@@ -1100,8 +1326,7 @@ int main(int argc, char** argv) {
fl.l_start = absl::GetFlag(FLAGS_child_setlock_start);
fl.l_len = absl::GetFlag(FLAGS_child_setlock_len);
- // Test the fcntl, no need to log, the error is unambiguously
- // from fcntl at this point.
+ // Test the fcntl.
int err = 0;
int ret = 0;
@@ -1114,6 +1339,8 @@ int main(int argc, char** argv) {
if (ret == -1 && errno != 0) {
err = errno;
+ std::cerr << "CHILD lock " << setlock_on << " failed " << err
+ << std::endl;
}
// If there is a socket fd let's send back the time in microseconds it took
diff --git a/test/syscalls/linux/flock.cc b/test/syscalls/linux/flock.cc
index 3ecb8db8e..638a93979 100644
--- a/test/syscalls/linux/flock.cc
+++ b/test/syscalls/linux/flock.cc
@@ -21,6 +21,7 @@
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "test/syscalls/linux/file_base.h"
+#include "test/syscalls/linux/socket_test_util.h"
#include "test/util/file_descriptor.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
@@ -34,11 +35,6 @@ namespace {
class FlockTest : public FileTest {};
-TEST_F(FlockTest, BadFD) {
- // EBADF: fd is not an open file descriptor.
- ASSERT_THAT(flock(-1, 0), SyscallFailsWithErrno(EBADF));
-}
-
TEST_F(FlockTest, InvalidOpCombinations) {
// The operation cannot be both exclusive and shared.
EXPECT_THAT(flock(test_file_fd_.get(), LOCK_EX | LOCK_SH | LOCK_NB),
@@ -57,15 +53,6 @@ TEST_F(FlockTest, NoOperationSpecified) {
SyscallFailsWithErrno(EINVAL));
}
-TEST(FlockTestNoFixture, FlockSupportsPipes) {
- int fds[2];
- ASSERT_THAT(pipe(fds), SyscallSucceeds());
-
- EXPECT_THAT(flock(fds[0], LOCK_EX | LOCK_NB), SyscallSucceeds());
- EXPECT_THAT(close(fds[0]), SyscallSucceeds());
- EXPECT_THAT(close(fds[1]), SyscallSucceeds());
-}
-
TEST_F(FlockTest, TestSimpleExLock) {
// Test that we can obtain an exclusive lock (no other holders)
// and that we can unlock it.
@@ -583,6 +570,66 @@ TEST_F(FlockTest, BlockingLockFirstExclusiveSecondExclusive_NoRandomSave) {
EXPECT_THAT(flock(test_file_fd_.get(), LOCK_UN), SyscallSucceeds());
}
+TEST(FlockTestNoFixture, BadFD) {
+ // EBADF: fd is not an open file descriptor.
+ ASSERT_THAT(flock(-1, 0), SyscallFailsWithErrno(EBADF));
+}
+
+TEST(FlockTestNoFixture, FlockDir) {
+ auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY, 0000));
+ EXPECT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallSucceeds());
+}
+
+TEST(FlockTestNoFixture, FlockSymlink) {
+ // TODO(gvisor.dev/issue/2782): Replace with IsRunningWithVFS1() when O_PATH
+ // is supported.
+ SKIP_IF(IsRunningOnGvisor());
+
+ auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ auto symlink = ASSERT_NO_ERRNO_AND_VALUE(
+ TempPath::CreateSymlinkTo(GetAbsoluteTestTmpdir(), file.path()));
+
+ auto fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(symlink.path(), O_RDONLY | O_PATH, 0000));
+ EXPECT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallFailsWithErrno(EBADF));
+}
+
+TEST(FlockTestNoFixture, FlockProc) {
+ auto fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/status", O_RDONLY, 0000));
+ EXPECT_THAT(flock(fd.get(), LOCK_EX | LOCK_NB), SyscallSucceeds());
+}
+
+TEST(FlockTestNoFixture, FlockPipe) {
+ int fds[2];
+ ASSERT_THAT(pipe(fds), SyscallSucceeds());
+
+ EXPECT_THAT(flock(fds[0], LOCK_EX | LOCK_NB), SyscallSucceeds());
+ // Check that the pipe was locked above.
+ EXPECT_THAT(flock(fds[1], LOCK_EX | LOCK_NB), SyscallFailsWithErrno(EAGAIN));
+
+ EXPECT_THAT(flock(fds[0], LOCK_UN), SyscallSucceeds());
+ EXPECT_THAT(flock(fds[1], LOCK_EX | LOCK_NB), SyscallSucceeds());
+
+ EXPECT_THAT(close(fds[0]), SyscallSucceeds());
+ EXPECT_THAT(close(fds[1]), SyscallSucceeds());
+}
+
+TEST(FlockTestNoFixture, FlockSocket) {
+ int sock = socket(AF_UNIX, SOCK_STREAM, 0);
+ ASSERT_THAT(sock, SyscallSucceeds());
+
+ struct sockaddr_un addr =
+ ASSERT_NO_ERRNO_AND_VALUE(UniqueUnixAddr(true /* abstract */, AF_UNIX));
+ ASSERT_THAT(
+ bind(sock, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)),
+ SyscallSucceeds());
+
+ EXPECT_THAT(flock(sock, LOCK_EX | LOCK_NB), SyscallSucceeds());
+ EXPECT_THAT(close(sock), SyscallSucceeds());
+}
+
} // namespace
} // namespace testing
diff --git a/test/syscalls/linux/fpsig_fork.cc b/test/syscalls/linux/fpsig_fork.cc
index d08111cd3..c47567b4e 100644
--- a/test/syscalls/linux/fpsig_fork.cc
+++ b/test/syscalls/linux/fpsig_fork.cc
@@ -37,11 +37,11 @@ namespace {
#define __stringify_1(x...) #x
#define __stringify(x...) __stringify_1(x)
#define GET_FPREG(var, regname) \
- asm volatile("str "__stringify(regname) ", %0" : "=m"(var))
+ asm volatile("str " __stringify(regname) ", %0" : "=m"(var))
#define SET_FPREG(var, regname) \
- asm volatile("ldr "__stringify(regname) ", %0" : "=m"(var))
+ asm volatile("ldr " __stringify(regname) ", %0" : "=m"(var))
#define GET_FP0(var) GET_FPREG(var, d0)
-#define SET_FP0(var) GET_FPREG(var, d0)
+#define SET_FP0(var) SET_FPREG(var, d0)
#endif
int parent, child;
diff --git a/test/syscalls/linux/fpsig_nested.cc b/test/syscalls/linux/fpsig_nested.cc
index c476a8e7a..302d928d1 100644
--- a/test/syscalls/linux/fpsig_nested.cc
+++ b/test/syscalls/linux/fpsig_nested.cc
@@ -26,9 +26,22 @@ namespace testing {
namespace {
+#ifdef __x86_64__
#define GET_XMM(__var, __xmm) \
asm volatile("movq %%" #__xmm ", %0" : "=r"(__var))
#define SET_XMM(__var, __xmm) asm volatile("movq %0, %%" #__xmm : : "r"(__var))
+#define GET_FP0(__var) GET_XMM(__var, xmm0)
+#define SET_FP0(__var) SET_XMM(__var, xmm0)
+#elif __aarch64__
+#define __stringify_1(x...) #x
+#define __stringify(x...) __stringify_1(x)
+#define GET_FPREG(var, regname) \
+ asm volatile("str " __stringify(regname) ", %0" : "=m"(var))
+#define SET_FPREG(var, regname) \
+ asm volatile("ldr " __stringify(regname) ", %0" : "=m"(var))
+#define GET_FP0(var) GET_FPREG(var, d0)
+#define SET_FP0(var) SET_FPREG(var, d0)
+#endif
int pid;
int tid;
@@ -40,20 +53,21 @@ void sigusr2(int s, siginfo_t* siginfo, void* _uc) {
uint64_t val = SIGUSR2;
// Record the value of %xmm0 on entry and then clobber it.
- GET_XMM(entryxmm[1], xmm0);
- SET_XMM(val, xmm0);
- GET_XMM(exitxmm[1], xmm0);
+ GET_FP0(entryxmm[1]);
+ SET_FP0(val);
+ GET_FP0(exitxmm[1]);
}
void sigusr1(int s, siginfo_t* siginfo, void* _uc) {
uint64_t val = SIGUSR1;
// Record the value of %xmm0 on entry and then clobber it.
- GET_XMM(entryxmm[0], xmm0);
- SET_XMM(val, xmm0);
+ GET_FP0(entryxmm[0]);
+ SET_FP0(val);
// Send a SIGUSR2 to ourself. The signal mask is configured such that
// the SIGUSR2 handler will run before this handler returns.
+#ifdef __x86_64__
asm volatile(
"movl %[killnr], %%eax;"
"movl %[pid], %%edi;"
@@ -66,10 +80,19 @@ void sigusr1(int s, siginfo_t* siginfo, void* _uc) {
: "rax", "rdi", "rsi", "rdx",
// Clobbered by syscall.
"rcx", "r11");
+#elif __aarch64__
+ asm volatile(
+ "mov x8, %0\n"
+ "mov x0, %1\n"
+ "mov x1, %2\n"
+ "mov x2, %3\n"
+ "svc #0\n" ::"r"(__NR_tgkill),
+ "r"(pid), "r"(tid), "r"(SIGUSR2));
+#endif
// Record value of %xmm0 again to verify that the nested signal handler
// does not clobber it.
- GET_XMM(exitxmm[0], xmm0);
+ GET_FP0(exitxmm[0]);
}
TEST(FPSigTest, NestedSignals) {
@@ -98,8 +121,9 @@ TEST(FPSigTest, NestedSignals) {
// to signal the current thread ensures that this is the clobbered thread.
uint64_t expected = 0xdeadbeeffacefeed;
- SET_XMM(expected, xmm0);
+ SET_FP0(expected);
+#ifdef __x86_64__
asm volatile(
"movl %[killnr], %%eax;"
"movl %[pid], %%edi;"
@@ -112,9 +136,18 @@ TEST(FPSigTest, NestedSignals) {
: "rax", "rdi", "rsi", "rdx",
// Clobbered by syscall.
"rcx", "r11");
+#elif __aarch64__
+ asm volatile(
+ "mov x8, %0\n"
+ "mov x0, %1\n"
+ "mov x1, %2\n"
+ "mov x2, %3\n"
+ "svc #0\n" ::"r"(__NR_tgkill),
+ "r"(pid), "r"(tid), "r"(SIGUSR1));
+#endif
uint64_t got;
- GET_XMM(got, xmm0);
+ GET_FP0(got);
//
// The checks below verifies the following:
diff --git a/test/syscalls/linux/futex.cc b/test/syscalls/linux/futex.cc
index 40c80a6e1..90b1f0508 100644
--- a/test/syscalls/linux/futex.cc
+++ b/test/syscalls/linux/futex.cc
@@ -18,6 +18,7 @@
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
+#include <syscall.h>
#include <unistd.h>
#include <algorithm>
@@ -737,6 +738,97 @@ TEST_P(PrivateAndSharedFutexTest, PITryLockConcurrency_NoRandomSave) {
}
}
+int get_robust_list(int pid, struct robust_list_head** head_ptr,
+ size_t* len_ptr) {
+ return syscall(__NR_get_robust_list, pid, head_ptr, len_ptr);
+}
+
+int set_robust_list(struct robust_list_head* head, size_t len) {
+ return syscall(__NR_set_robust_list, head, len);
+}
+
+TEST(RobustFutexTest, BasicSetGet) {
+ struct robust_list_head hd = {};
+ struct robust_list_head* hd_ptr = &hd;
+
+ // Set!
+ EXPECT_THAT(set_robust_list(hd_ptr, sizeof(hd)), SyscallSucceedsWithValue(0));
+
+ // Get!
+ struct robust_list_head* new_hd_ptr = hd_ptr;
+ size_t len;
+ EXPECT_THAT(get_robust_list(0, &new_hd_ptr, &len),
+ SyscallSucceedsWithValue(0));
+ EXPECT_EQ(new_hd_ptr, hd_ptr);
+ EXPECT_EQ(len, sizeof(hd));
+}
+
+TEST(RobustFutexTest, GetFromOtherTid) {
+ // Get the current tid and list head.
+ pid_t tid = gettid();
+ struct robust_list_head* hd_ptr = {};
+ size_t len;
+ EXPECT_THAT(get_robust_list(0, &hd_ptr, &len), SyscallSucceedsWithValue(0));
+
+ // Create a new thread.
+ ScopedThread t([&] {
+ // Current tid list head should be different from parent tid.
+ struct robust_list_head* got_hd_ptr = {};
+ EXPECT_THAT(get_robust_list(0, &got_hd_ptr, &len),
+ SyscallSucceedsWithValue(0));
+ EXPECT_NE(hd_ptr, got_hd_ptr);
+
+ // Get the parent list head by passing its tid.
+ EXPECT_THAT(get_robust_list(tid, &got_hd_ptr, &len),
+ SyscallSucceedsWithValue(0));
+ EXPECT_EQ(hd_ptr, got_hd_ptr);
+ });
+
+ // Wait for thread.
+ t.Join();
+}
+
+TEST(RobustFutexTest, InvalidSize) {
+ struct robust_list_head* hd = {};
+ EXPECT_THAT(set_robust_list(hd, sizeof(*hd) + 1),
+ SyscallFailsWithErrno(EINVAL));
+}
+
+TEST(RobustFutexTest, PthreadMutexAttr) {
+ constexpr int kNumMutexes = 3;
+
+ // Create a bunch of robust mutexes.
+ pthread_mutexattr_t attrs[kNumMutexes];
+ pthread_mutex_t mtxs[kNumMutexes];
+ for (int i = 0; i < kNumMutexes; i++) {
+ TEST_PCHECK(pthread_mutexattr_init(&attrs[i]) == 0);
+ TEST_PCHECK(pthread_mutexattr_setrobust(&attrs[i], PTHREAD_MUTEX_ROBUST) ==
+ 0);
+ TEST_PCHECK(pthread_mutex_init(&mtxs[i], &attrs[i]) == 0);
+ }
+
+ // Start thread to lock the mutexes and then exit.
+ ScopedThread t([&] {
+ for (int i = 0; i < kNumMutexes; i++) {
+ TEST_PCHECK(pthread_mutex_lock(&mtxs[i]) == 0);
+ }
+ pthread_exit(NULL);
+ });
+
+ // Wait for thread.
+ t.Join();
+
+ // Now try to take the mutexes.
+ for (int i = 0; i < kNumMutexes; i++) {
+ // Should get EOWNERDEAD.
+ EXPECT_EQ(pthread_mutex_lock(&mtxs[i]), EOWNERDEAD);
+ // Make the mutex consistent.
+ EXPECT_EQ(pthread_mutex_consistent(&mtxs[i]), 0);
+ // Unlock.
+ EXPECT_EQ(pthread_mutex_unlock(&mtxs[i]), 0);
+ }
+}
+
} // namespace
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/getdents.cc b/test/syscalls/linux/getdents.cc
index b147d6181..b040cdcf7 100644
--- a/test/syscalls/linux/getdents.cc
+++ b/test/syscalls/linux/getdents.cc
@@ -32,6 +32,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "absl/container/node_hash_set.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "test/util/eventfd_util.h"
@@ -393,7 +394,7 @@ TYPED_TEST(GetdentsTest, ProcSelfFd) {
// Make the buffer very small since we want to iterate.
typename TestFixture::DirentBufferType dirents(
2 * sizeof(typename TestFixture::LinuxDirentType));
- std::unordered_set<int> prev_fds;
+ absl::node_hash_set<int> prev_fds;
while (true) {
dirents.Reset();
int rv;
diff --git a/test/syscalls/linux/inotify.cc b/test/syscalls/linux/inotify.cc
index 0e13ad190..6cf398097 100644
--- a/test/syscalls/linux/inotify.cc
+++ b/test/syscalls/linux/inotify.cc
@@ -18,7 +18,9 @@
#include <sys/epoll.h>
#include <sys/inotify.h>
#include <sys/ioctl.h>
+#include <sys/sendfile.h>
#include <sys/time.h>
+#include <sys/xattr.h>
#include <atomic>
#include <list>
@@ -28,11 +30,13 @@
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
+#include "absl/synchronization/mutex.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "test/util/epoll_util.h"
#include "test/util/file_descriptor.h"
#include "test/util/fs_util.h"
+#include "test/util/posix_error.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
#include "test/util/thread_util.h"
@@ -330,9 +334,32 @@ PosixErrorOr<int> InotifyAddWatch(int fd, const std::string& path,
return wd;
}
-TEST(Inotify, InotifyFdNotWritable) {
+TEST(Inotify, IllegalSeek) {
const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(0));
- EXPECT_THAT(write(fd.get(), "x", 1), SyscallFailsWithErrno(EBADF));
+ EXPECT_THAT(lseek(fd.get(), 0, SEEK_SET), SyscallFailsWithErrno(ESPIPE));
+}
+
+TEST(Inotify, IllegalPread) {
+ const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(0));
+ int val;
+ EXPECT_THAT(pread(fd.get(), &val, sizeof(val), 0),
+ SyscallFailsWithErrno(ESPIPE));
+}
+
+TEST(Inotify, IllegalPwrite) {
+ const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(0));
+ EXPECT_THAT(pwrite(fd.get(), "x", 1, 0), SyscallFailsWithErrno(ESPIPE));
+}
+
+TEST(Inotify, IllegalWrite) {
+ const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(0));
+ int val = 0;
+ EXPECT_THAT(write(fd.get(), &val, sizeof(val)), SyscallFailsWithErrno(EBADF));
+}
+
+TEST(Inotify, InitFlags) {
+ EXPECT_THAT(inotify_init1(IN_NONBLOCK | IN_CLOEXEC), SyscallSucceeds());
+ EXPECT_THAT(inotify_init1(12345), SyscallFailsWithErrno(EINVAL));
}
TEST(Inotify, NonBlockingReadReturnsEagain) {
@@ -395,7 +422,7 @@ TEST(Inotify, CanDeleteFileAfterRemovingWatch) {
file1.reset();
}
-TEST(Inotify, CanRemoveWatchAfterDeletingFile) {
+TEST(Inotify, RemoveWatchAfterDeletingFileFails) {
const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
TempPath file1 =
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
@@ -491,17 +518,23 @@ TEST(Inotify, DeletingChildGeneratesEvents) {
Event(IN_DELETE, root_wd, Basename(file1_path))}));
}
+// Creating a file in "parent/child" should generate events for child, but not
+// parent.
TEST(Inotify, CreatingFileGeneratesEvents) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ const TempPath child =
+ ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path()));
const FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
+ ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(fd.get(), parent.path(), IN_ALL_EVENTS));
const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
+ InotifyAddWatch(fd.get(), child.path(), IN_ALL_EVENTS));
// Create a new file in the directory.
const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
+ ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(child.path()));
const std::vector<Event> events =
ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
@@ -554,6 +587,47 @@ TEST(Inotify, WritingFileGeneratesModifyEvent) {
ASSERT_THAT(events, Are({Event(IN_MODIFY, wd, Basename(file1.path()))}));
}
+TEST(Inotify, SizeZeroReadWriteGeneratesNothing) {
+ const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ const FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
+ const TempPath file1 =
+ ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
+
+ const FileDescriptor file1_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDWR));
+ ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
+
+ // Read from the empty file.
+ int val;
+ ASSERT_THAT(read(file1_fd.get(), &val, sizeof(val)),
+ SyscallSucceedsWithValue(0));
+
+ // Write zero bytes.
+ ASSERT_THAT(write(file1_fd.get(), "", 0), SyscallSucceedsWithValue(0));
+
+ const std::vector<Event> events =
+ ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
+ ASSERT_THAT(events, Are({}));
+}
+
+TEST(Inotify, FailedFileCreationGeneratesNoEvents) {
+ const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ const std::string dir_path = dir.path();
+ const FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
+ ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(fd.get(), dir_path, IN_ALL_EVENTS));
+
+ const char* p = dir_path.c_str();
+ ASSERT_THAT(mkdir(p, 0777), SyscallFails());
+ ASSERT_THAT(mknod(p, S_IFIFO, 0777), SyscallFails());
+ ASSERT_THAT(symlink(p, p), SyscallFails());
+ ASSERT_THAT(link(p, p), SyscallFails());
+ std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
+ ASSERT_THAT(events, Are({}));
+}
+
TEST(Inotify, WatchSetAfterOpenReportsCloseFdEvent) {
const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
const FileDescriptor fd =
@@ -602,7 +676,7 @@ TEST(Inotify, ChildrenDeletionInWatchedDirGeneratesEvent) {
Event(IN_DELETE | IN_ISDIR, wd, Basename(dir1_path))}));
}
-TEST(Inotify, WatchTargetDeletionGeneratesEvent) {
+TEST(Inotify, RmdirOnWatchedTargetGeneratesEvent) {
const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
const FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
@@ -1228,7 +1302,7 @@ TEST(Inotify, LinkGeneratesAttribAndCreateEvents) {
InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
const int rc = link(file1.path().c_str(), link1.path().c_str());
- // link(2) is only supported on tmpfs in the sandbox.
+ // NOTE(b/34861058): link(2) is only supported on tmpfs in the sandbox.
SKIP_IF(IsRunningOnGvisor() && rc != 0 &&
(errno == EPERM || errno == ENOENT));
ASSERT_THAT(rc, SyscallSucceeds());
@@ -1251,7 +1325,7 @@ TEST(Inotify, UtimesGeneratesAttribEvent) {
const int wd = ASSERT_NO_ERRNO_AND_VALUE(
InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- struct timeval times[2] = {{1, 0}, {2, 0}};
+ const struct timeval times[2] = {{1, 0}, {2, 0}};
EXPECT_THAT(futimes(file1_fd.get(), times), SyscallSucceeds());
const std::vector<Event> events =
@@ -1322,21 +1396,27 @@ TEST(Inotify, HardlinksReuseSameWatch) {
Event(IN_DELETE, root_wd, Basename(file1_path))}));
}
+// Calling mkdir within "parent/child" should generate an event for child, but
+// not parent.
TEST(Inotify, MkdirGeneratesCreateEventWithDirFlag) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ const TempPath child =
+ ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path()));
const FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
+ ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(fd.get(), parent.path(), IN_ALL_EVENTS));
+ const int child_wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(fd.get(), child.path(), IN_ALL_EVENTS));
- const TempPath dir1(NewTempAbsPathInDir(root.path()));
+ const TempPath dir1(NewTempAbsPathInDir(child.path()));
ASSERT_THAT(mkdir(dir1.path().c_str(), 0777), SyscallSucceeds());
const std::vector<Event> events =
ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
ASSERT_THAT(
events,
- Are({Event(IN_CREATE | IN_ISDIR, root_wd, Basename(dir1.path()))}));
+ Are({Event(IN_CREATE | IN_ISDIR, child_wd, Basename(dir1.path()))}));
}
TEST(Inotify, MultipleInotifyInstancesAndWatchesAllGetEvents) {
@@ -1424,20 +1504,26 @@ TEST(Inotify, DuplicateWatchReturnsSameWatchDescriptor) {
TEST(Inotify, UnmatchedEventsAreDiscarded) {
const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
+ TempPath file1 =
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
const FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(fd.get(), file1.path(), IN_ACCESS));
+ const int wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(fd.get(), file1.path(), IN_ACCESS));
- const FileDescriptor file1_fd =
+ FileDescriptor file1_fd =
ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY));
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
+ std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
// We only asked for access events, the open event should be discarded.
ASSERT_THAT(events, Are({}));
+
+ // IN_IGNORED events are always generated, regardless of the mask.
+ file1_fd.reset();
+ file1.reset();
+ events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
+ ASSERT_THAT(events, Are({Event(IN_IGNORED, wd)}));
}
TEST(Inotify, AddWatchWithInvalidEventMaskFails) {
@@ -1596,7 +1682,98 @@ TEST(Inotify, EpollNoDeadlock) {
}
}
-TEST(Inotify, SpliceEvent) {
+TEST(Inotify, Fallocate) {
+ const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ const FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
+
+ const FileDescriptor inotify_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
+ const int wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(inotify_fd.get(), file.path(), IN_ALL_EVENTS));
+
+ // Do an arbitrary modification with fallocate.
+ ASSERT_THAT(fallocate(fd.get(), 0, 0, 123), SyscallSucceeds());
+ std::vector<Event> events =
+ ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({Event(IN_MODIFY, wd)}));
+}
+
+TEST(Inotify, Sendfile) {
+ SKIP_IF(IsRunningWithVFS1());
+
+ const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(
+ TempPath::CreateFileWith(root.path(), "x", 0644));
+ const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ const FileDescriptor in =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY));
+ const FileDescriptor out =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY));
+
+ // Create separate inotify instances for the in and out fds. If both watches
+ // were on the same instance, we would have discrepancies between Linux and
+ // gVisor (order of events, duplicate events), which is not that important
+ // since inotify is asynchronous anyway.
+ const FileDescriptor in_inotify =
+ ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
+ const FileDescriptor out_inotify =
+ ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
+ const int in_wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(in_inotify.get(), in_file.path(), IN_ALL_EVENTS));
+ const int out_wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(out_inotify.get(), out_file.path(), IN_ALL_EVENTS));
+
+ ASSERT_THAT(sendfile(out.get(), in.get(), /*offset=*/nullptr, 1),
+ SyscallSucceeds());
+
+ // Expect a single access event and a single modify event.
+ std::vector<Event> in_events =
+ ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(in_inotify.get()));
+ std::vector<Event> out_events =
+ ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(out_inotify.get()));
+ EXPECT_THAT(in_events, Are({Event(IN_ACCESS, in_wd)}));
+ EXPECT_THAT(out_events, Are({Event(IN_MODIFY, out_wd)}));
+}
+
+// On Linux, inotify behavior is not very consistent with splice(2). We try our
+// best to emulate Linux for very basic calls to splice.
+TEST(Inotify, SpliceOnWatchTarget) {
+ int pipes[2];
+ ASSERT_THAT(pipe2(pipes, O_NONBLOCK), SyscallSucceeds());
+
+ const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ const FileDescriptor inotify_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
+ const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
+ dir.path(), "some content", TempPath::kDefaultFileMode));
+
+ const FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
+ const int dir_wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(inotify_fd.get(), dir.path(), IN_ALL_EVENTS));
+ const int file_wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(inotify_fd.get(), file.path(), IN_ALL_EVENTS));
+
+ EXPECT_THAT(splice(fd.get(), nullptr, pipes[1], nullptr, 1, /*flags=*/0),
+ SyscallSucceedsWithValue(1));
+
+ // Surprisingly, events are not generated in Linux if we read from a file.
+ std::vector<Event> events =
+ ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ ASSERT_THAT(events, Are({}));
+
+ EXPECT_THAT(splice(pipes[0], nullptr, fd.get(), nullptr, 1, /*flags=*/0),
+ SyscallSucceedsWithValue(1));
+
+ events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ ASSERT_THAT(events, Are({
+ Event(IN_MODIFY, dir_wd, Basename(file.path())),
+ Event(IN_MODIFY, file_wd),
+ }));
+}
+
+TEST(Inotify, SpliceOnInotifyFD) {
int pipes[2];
ASSERT_THAT(pipe2(pipes, O_NONBLOCK), SyscallSucceeds());
@@ -1624,6 +1801,635 @@ TEST(Inotify, SpliceEvent) {
ASSERT_THAT(events, Are({Event(IN_ACCESS, watcher)}));
}
+// Watches on a parent should not be triggered by actions on a hard link to one
+// of its children that has a different parent.
+TEST(Inotify, LinkOnOtherParent) {
+ const TempPath dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ const TempPath dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ const TempPath file =
+ ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path()));
+ std::string link_path = NewTempAbsPathInDir(dir2.path());
+
+ const int rc = link(file.path().c_str(), link_path.c_str());
+ // NOTE(b/34861058): link(2) is only supported on tmpfs in the sandbox.
+ SKIP_IF(IsRunningOnGvisor() && rc != 0 &&
+ (errno == EPERM || errno == ENOENT));
+ ASSERT_THAT(rc, SyscallSucceeds());
+
+ const FileDescriptor inotify_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
+ ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(inotify_fd.get(), dir1.path(), IN_ALL_EVENTS));
+
+ // Perform various actions on the link outside of dir1, which should trigger
+ // no inotify events.
+ const FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(link_path.c_str(), O_RDWR));
+ int val = 0;
+ ASSERT_THAT(write(fd.get(), &val, sizeof(val)), SyscallSucceeds());
+ ASSERT_THAT(read(fd.get(), &val, sizeof(val)), SyscallSucceeds());
+ ASSERT_THAT(ftruncate(fd.get(), 12345), SyscallSucceeds());
+ ASSERT_THAT(unlink(link_path.c_str()), SyscallSucceeds());
+ const std::vector<Event> events =
+ ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({}));
+}
+
+TEST(Inotify, Xattr) {
+ // TODO(gvisor.dev/issue/1636): Support extended attributes in runsc gofer.
+ SKIP_IF(IsRunningOnGvisor());
+
+ const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ const std::string path = file.path();
+ const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_RDWR));
+ const FileDescriptor inotify_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
+ const int wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(inotify_fd.get(), path, IN_ALL_EVENTS));
+
+ const char* cpath = path.c_str();
+ const char* name = "user.test";
+ int val = 123;
+ ASSERT_THAT(setxattr(cpath, name, &val, sizeof(val), /*flags=*/0),
+ SyscallSucceeds());
+ std::vector<Event> events =
+ ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({Event(IN_ATTRIB, wd)}));
+
+ ASSERT_THAT(getxattr(cpath, name, &val, sizeof(val)), SyscallSucceeds());
+ events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({}));
+
+ char list[100];
+ ASSERT_THAT(listxattr(cpath, list, sizeof(list)), SyscallSucceeds());
+ events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({}));
+
+ ASSERT_THAT(removexattr(cpath, name), SyscallSucceeds());
+ events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({Event(IN_ATTRIB, wd)}));
+
+ ASSERT_THAT(fsetxattr(fd.get(), name, &val, sizeof(val), /*flags=*/0),
+ SyscallSucceeds());
+ events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({Event(IN_ATTRIB, wd)}));
+
+ ASSERT_THAT(fgetxattr(fd.get(), name, &val, sizeof(val)), SyscallSucceeds());
+ events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({}));
+
+ ASSERT_THAT(flistxattr(fd.get(), list, sizeof(list)), SyscallSucceeds());
+ events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({}));
+
+ ASSERT_THAT(fremovexattr(fd.get(), name), SyscallSucceeds());
+ events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({Event(IN_ATTRIB, wd)}));
+}
+
+TEST(Inotify, Exec) {
+ const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ const TempPath bin = ASSERT_NO_ERRNO_AND_VALUE(
+ TempPath::CreateSymlinkTo(dir.path(), "/bin/true"));
+
+ const FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
+ const int wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(fd.get(), bin.path(), IN_ALL_EVENTS));
+
+ // Perform exec.
+ ScopedThread t([&bin]() {
+ ASSERT_THAT(execl(bin.path().c_str(), bin.path().c_str(), (char*)nullptr),
+ SyscallSucceeds());
+ });
+ t.Join();
+
+ std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
+ EXPECT_THAT(events, Are({Event(IN_OPEN, wd), Event(IN_ACCESS, wd)}));
+}
+
+// Watches without IN_EXCL_UNLINK, should continue to emit events for file
+// descriptors after their corresponding files have been unlinked.
+//
+// We need to disable S/R because there are filesystems where we cannot re-open
+// fds to an unlinked file across S/R, e.g. gofer-backed filesytems.
+TEST(Inotify, IncludeUnlinkedFile_NoRandomSave) {
+ const DisableSave ds;
+
+ const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(
+ TempPath::CreateFileWith(dir.path(), "123", TempPath::kDefaultFileMode));
+ FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
+
+ const FileDescriptor inotify_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
+ const int dir_wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(inotify_fd.get(), dir.path(), IN_ALL_EVENTS));
+ const int file_wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(inotify_fd.get(), file.path(), IN_ALL_EVENTS));
+
+ ASSERT_THAT(unlink(file.path().c_str()), SyscallSucceeds());
+ int val = 0;
+ ASSERT_THAT(read(fd.get(), &val, sizeof(val)), SyscallSucceeds());
+ ASSERT_THAT(write(fd.get(), &val, sizeof(val)), SyscallSucceeds());
+ std::vector<Event> events =
+ ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({
+ Event(IN_ATTRIB, file_wd),
+ Event(IN_DELETE, dir_wd, Basename(file.path())),
+ Event(IN_ACCESS, dir_wd, Basename(file.path())),
+ Event(IN_ACCESS, file_wd),
+ Event(IN_MODIFY, dir_wd, Basename(file.path())),
+ Event(IN_MODIFY, file_wd),
+ }));
+
+ fd.reset();
+ events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({
+ Event(IN_CLOSE_WRITE, dir_wd, Basename(file.path())),
+ Event(IN_CLOSE_WRITE, file_wd),
+ Event(IN_DELETE_SELF, file_wd),
+ Event(IN_IGNORED, file_wd),
+ }));
+}
+
+// Watches created with IN_EXCL_UNLINK will stop emitting events on fds for
+// children that have already been unlinked.
+//
+// We need to disable S/R because there are filesystems where we cannot re-open
+// fds to an unlinked file across S/R, e.g. gofer-backed filesytems.
+TEST(Inotify, ExcludeUnlink_NoRandomSave) {
+ const DisableSave ds;
+ // TODO(gvisor.dev/issue/1624): This test fails on VFS1.
+ SKIP_IF(IsRunningWithVFS1());
+
+ const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ const TempPath file =
+ ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
+
+ FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
+
+ const FileDescriptor inotify_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
+ const int dir_wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(
+ inotify_fd.get(), dir.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK));
+ const int file_wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(
+ inotify_fd.get(), file.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK));
+
+ // Unlink the child, which should cause further operations on the open file
+ // descriptor to be ignored.
+ ASSERT_THAT(unlink(file.path().c_str()), SyscallSucceeds());
+ int val = 0;
+ ASSERT_THAT(write(fd.get(), &val, sizeof(val)), SyscallSucceeds());
+ ASSERT_THAT(read(fd.get(), &val, sizeof(val)), SyscallSucceeds());
+ std::vector<Event> events =
+ ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({
+ Event(IN_ATTRIB, file_wd),
+ Event(IN_DELETE, dir_wd, Basename(file.path())),
+ }));
+
+ fd.reset();
+ events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ ASSERT_THAT(events, Are({
+ Event(IN_DELETE_SELF, file_wd),
+ Event(IN_IGNORED, file_wd),
+ }));
+}
+
+// We need to disable S/R because there are filesystems where we cannot re-open
+// fds to an unlinked file across S/R, e.g. gofer-backed filesytems.
+TEST(Inotify, ExcludeUnlinkDirectory_NoRandomSave) {
+ // TODO(gvisor.dev/issue/1624): This test fails on VFS1. Remove once VFS1 is
+ // deleted.
+ SKIP_IF(IsRunningWithVFS1());
+
+ const DisableSave ds;
+
+ const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ TempPath dir =
+ ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path()));
+ std::string dirPath = dir.path();
+ const FileDescriptor inotify_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
+
+ FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(dirPath.c_str(), O_RDONLY | O_DIRECTORY));
+ const int parent_wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(
+ inotify_fd.get(), parent.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK));
+ const int self_wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(
+ inotify_fd.get(), dir.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK));
+
+ // Unlink the dir, and then close the open fd.
+ ASSERT_THAT(rmdir(dirPath.c_str()), SyscallSucceeds());
+ dir.reset();
+
+ std::vector<Event> events =
+ ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ // No close event should appear.
+ ASSERT_THAT(events,
+ Are({Event(IN_DELETE | IN_ISDIR, parent_wd, Basename(dirPath))}));
+
+ fd.reset();
+ events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ ASSERT_THAT(events, Are({
+ Event(IN_DELETE_SELF, self_wd),
+ Event(IN_IGNORED, self_wd),
+ }));
+}
+
+// If "dir/child" and "dir/child2" are links to the same file, and "dir/child"
+// is unlinked, a watch on "dir" with IN_EXCL_UNLINK will exclude future events
+// for fds on "dir/child" but not "dir/child2".
+//
+// We need to disable S/R because there are filesystems where we cannot re-open
+// fds to an unlinked file across S/R, e.g. gofer-backed filesytems.
+TEST(Inotify, ExcludeUnlinkMultipleChildren_NoRandomSave) {
+ const DisableSave ds;
+ // TODO(gvisor.dev/issue/1624): This test fails on VFS1.
+ SKIP_IF(IsRunningWithVFS1());
+
+ const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ const TempPath file =
+ ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
+ std::string path1 = file.path();
+ std::string path2 = NewTempAbsPathInDir(dir.path());
+
+ const int rc = link(path1.c_str(), path2.c_str());
+ // NOTE(b/34861058): link(2) is only supported on tmpfs in the sandbox.
+ SKIP_IF(IsRunningOnGvisor() && rc != 0 &&
+ (errno == EPERM || errno == ENOENT));
+ ASSERT_THAT(rc, SyscallSucceeds());
+ const FileDescriptor fd1 =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(path1.c_str(), O_RDWR));
+ const FileDescriptor fd2 =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(path2.c_str(), O_RDWR));
+
+ const FileDescriptor inotify_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
+ const int wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(
+ inotify_fd.get(), dir.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK));
+
+ // After unlinking path1, only events on the fd for path2 should be generated.
+ ASSERT_THAT(unlink(path1.c_str()), SyscallSucceeds());
+ ASSERT_THAT(write(fd1.get(), "x", 1), SyscallSucceeds());
+ ASSERT_THAT(write(fd2.get(), "x", 1), SyscallSucceeds());
+
+ const std::vector<Event> events =
+ ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({
+ Event(IN_DELETE, wd, Basename(path1)),
+ Event(IN_MODIFY, wd, Basename(path2)),
+ }));
+}
+
+// On native Linux, actions of data type FSNOTIFY_EVENT_INODE are not affected
+// by IN_EXCL_UNLINK (see
+// fs/notify/inotify/inotify_fsnotify.c:inotify_handle_event). Inode-level
+// events include changes to metadata and extended attributes.
+//
+// We need to disable S/R because there are filesystems where we cannot re-open
+// fds to an unlinked file across S/R, e.g. gofer-backed filesytems.
+TEST(Inotify, ExcludeUnlinkInodeEvents_NoRandomSave) {
+ const DisableSave ds;
+
+ const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ const TempPath file =
+ ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
+
+ const FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(file.path().c_str(), O_RDWR));
+
+ // NOTE(b/157163751): Create another link before unlinking. This is needed for
+ // the gofer filesystem in gVisor, where open fds will not work once the link
+ // count hits zero. In VFS2, we end up skipping the gofer test anyway, because
+ // hard links are not supported for gofer fs.
+ if (IsRunningOnGvisor()) {
+ std::string link_path = NewTempAbsPath();
+ const int rc = link(file.path().c_str(), link_path.c_str());
+ // NOTE(b/34861058): link(2) is only supported on tmpfs in the sandbox.
+ SKIP_IF(rc != 0 && (errno == EPERM || errno == ENOENT));
+ ASSERT_THAT(rc, SyscallSucceeds());
+ }
+
+ const FileDescriptor inotify_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
+ const int dir_wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(
+ inotify_fd.get(), dir.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK));
+ const int file_wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(
+ inotify_fd.get(), file.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK));
+
+ // Even after unlinking, inode-level operations will trigger events regardless
+ // of IN_EXCL_UNLINK.
+ ASSERT_THAT(unlink(file.path().c_str()), SyscallSucceeds());
+
+ // Perform various actions on fd.
+ ASSERT_THAT(ftruncate(fd.get(), 12345), SyscallSucceeds());
+ std::vector<Event> events =
+ ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({
+ Event(IN_ATTRIB, file_wd),
+ Event(IN_DELETE, dir_wd, Basename(file.path())),
+ Event(IN_MODIFY, dir_wd, Basename(file.path())),
+ Event(IN_MODIFY, file_wd),
+ }));
+
+ const struct timeval times[2] = {{1, 0}, {2, 0}};
+ ASSERT_THAT(futimes(fd.get(), times), SyscallSucceeds());
+ events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({
+ Event(IN_ATTRIB, dir_wd, Basename(file.path())),
+ Event(IN_ATTRIB, file_wd),
+ }));
+
+ // S/R is disabled on this entire test due to behavior with unlink; it must
+ // also be disabled after this point because of fchmod.
+ ASSERT_THAT(fchmod(fd.get(), 0777), SyscallSucceeds());
+ events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({
+ Event(IN_ATTRIB, dir_wd, Basename(file.path())),
+ Event(IN_ATTRIB, file_wd),
+ }));
+}
+
+TEST(Inotify, OneShot) {
+ // TODO(gvisor.dev/issue/1624): IN_ONESHOT not supported in VFS1.
+ SKIP_IF(IsRunningWithVFS1());
+
+ const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ const FileDescriptor inotify_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
+
+ const int wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(inotify_fd.get(), file.path(), IN_MODIFY | IN_ONESHOT));
+
+ // Open an fd, write to it, and then close it.
+ FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_WRONLY));
+ ASSERT_THAT(write(fd.get(), "x", 1), SyscallSucceedsWithValue(1));
+ fd.reset();
+
+ // We should get a single event followed by IN_IGNORED indicating removal
+ // of the one-shot watch. Prior activity (i.e. open) that is not in the mask
+ // should not trigger removal, and activity after removal (i.e. close) should
+ // not generate events.
+ std::vector<Event> events =
+ ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({
+ Event(IN_MODIFY, wd),
+ Event(IN_IGNORED, wd),
+ }));
+
+ // The watch should already have been removed.
+ EXPECT_THAT(inotify_rm_watch(inotify_fd.get(), wd),
+ SyscallFailsWithErrno(EINVAL));
+}
+
+// This test helps verify that the lock order of filesystem and inotify locks
+// is respected when inotify instances and watch targets are concurrently being
+// destroyed.
+TEST(InotifyTest, InotifyAndTargetDestructionDoNotDeadlock_NoRandomSave) {
+ const DisableSave ds; // Too many syscalls.
+
+ // A file descriptor protected by a mutex. This ensures that while a
+ // descriptor is in use, it cannot be closed and reused for a different file
+ // description.
+ struct atomic_fd {
+ int fd;
+ absl::Mutex mu;
+ };
+
+ // Set up initial inotify instances.
+ constexpr int num_fds = 3;
+ std::vector<atomic_fd> fds(num_fds);
+ for (int i = 0; i < num_fds; i++) {
+ int fd;
+ ASSERT_THAT(fd = inotify_init1(IN_NONBLOCK), SyscallSucceeds());
+ fds[i].fd = fd;
+ }
+
+ // Set up initial watch targets.
+ std::vector<std::string> paths;
+ for (int i = 0; i < 3; i++) {
+ paths.push_back(NewTempAbsPath());
+ ASSERT_THAT(mknod(paths[i].c_str(), S_IFREG | 0600, 0), SyscallSucceeds());
+ }
+
+ constexpr absl::Duration runtime = absl::Seconds(4);
+
+ // Constantly replace each inotify instance with a new one.
+ auto replace_fds = [&] {
+ for (auto start = absl::Now(); absl::Now() - start < runtime;) {
+ for (auto& afd : fds) {
+ int new_fd;
+ ASSERT_THAT(new_fd = inotify_init1(IN_NONBLOCK), SyscallSucceeds());
+ absl::MutexLock l(&afd.mu);
+ ASSERT_THAT(close(afd.fd), SyscallSucceeds());
+ afd.fd = new_fd;
+ for (auto& p : paths) {
+ // inotify_add_watch may fail if the file at p was deleted.
+ ASSERT_THAT(inotify_add_watch(afd.fd, p.c_str(), IN_ALL_EVENTS),
+ AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(ENOENT)));
+ }
+ }
+ sched_yield();
+ }
+ };
+
+ std::list<ScopedThread> ts;
+ for (int i = 0; i < 3; i++) {
+ ts.emplace_back(replace_fds);
+ }
+
+ // Constantly replace each watch target with a new one.
+ for (auto start = absl::Now(); absl::Now() - start < runtime;) {
+ for (auto& p : paths) {
+ ASSERT_THAT(unlink(p.c_str()), SyscallSucceeds());
+ ASSERT_THAT(mknod(p.c_str(), S_IFREG | 0600, 0), SyscallSucceeds());
+ }
+ sched_yield();
+ }
+}
+
+// This test helps verify that the lock order of filesystem and inotify locks
+// is respected when adding/removing watches occurs concurrently with the
+// removal of their targets.
+TEST(InotifyTest, AddRemoveUnlinkDoNotDeadlock_NoRandomSave) {
+ const DisableSave ds; // Too many syscalls.
+
+ // Set up inotify instances.
+ constexpr int num_fds = 3;
+ std::vector<int> fds(num_fds);
+ for (int i = 0; i < num_fds; i++) {
+ ASSERT_THAT(fds[i] = inotify_init1(IN_NONBLOCK), SyscallSucceeds());
+ }
+
+ // Set up initial watch targets.
+ std::vector<std::string> paths;
+ for (int i = 0; i < 3; i++) {
+ paths.push_back(NewTempAbsPath());
+ ASSERT_THAT(mknod(paths[i].c_str(), S_IFREG | 0600, 0), SyscallSucceeds());
+ }
+
+ constexpr absl::Duration runtime = absl::Seconds(1);
+
+ // Constantly add/remove watches for each inotify instance/watch target pair.
+ auto add_remove_watches = [&] {
+ for (auto start = absl::Now(); absl::Now() - start < runtime;) {
+ for (int fd : fds) {
+ for (auto& p : paths) {
+ // Do not assert on inotify_add_watch and inotify_rm_watch. They may
+ // fail if the file at p was deleted. inotify_add_watch may also fail
+ // if another thread beat us to adding a watch.
+ const int wd = inotify_add_watch(fd, p.c_str(), IN_ALL_EVENTS);
+ if (wd > 0) {
+ inotify_rm_watch(fd, wd);
+ }
+ }
+ }
+ sched_yield();
+ }
+ };
+
+ std::list<ScopedThread> ts;
+ for (int i = 0; i < 15; i++) {
+ ts.emplace_back(add_remove_watches);
+ }
+
+ // Constantly replace each watch target with a new one.
+ for (auto start = absl::Now(); absl::Now() - start < runtime;) {
+ for (auto& p : paths) {
+ ASSERT_THAT(unlink(p.c_str()), SyscallSucceeds());
+ ASSERT_THAT(mknod(p.c_str(), S_IFREG | 0600, 0), SyscallSucceeds());
+ }
+ sched_yield();
+ }
+}
+
+// This test helps verify that the lock order of filesystem and inotify locks
+// is respected when many inotify events and filesystem operations occur
+// simultaneously.
+TEST(InotifyTest, NotifyNoDeadlock_NoRandomSave) {
+ const DisableSave ds; // Too many syscalls.
+
+ const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ const std::string dir = parent.path();
+
+ // mu protects file, which will change on rename.
+ absl::Mutex mu;
+ std::string file = NewTempAbsPathInDir(dir);
+ ASSERT_THAT(mknod(file.c_str(), 0644 | S_IFREG, 0), SyscallSucceeds());
+
+ const absl::Duration runtime = absl::Milliseconds(300);
+
+ // Add/remove watches on dir and file.
+ ScopedThread add_remove_watches([&] {
+ const FileDescriptor ifd =
+ ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
+ int dir_wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(ifd.get(), dir, IN_ALL_EVENTS));
+ int file_wd;
+ {
+ absl::ReaderMutexLock l(&mu);
+ file_wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(ifd.get(), file, IN_ALL_EVENTS));
+ }
+ for (auto start = absl::Now(); absl::Now() - start < runtime;) {
+ ASSERT_THAT(inotify_rm_watch(ifd.get(), file_wd), SyscallSucceeds());
+ ASSERT_THAT(inotify_rm_watch(ifd.get(), dir_wd), SyscallSucceeds());
+ dir_wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(ifd.get(), dir, IN_ALL_EVENTS));
+ {
+ absl::ReaderMutexLock l(&mu);
+ file_wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(ifd.get(), file, IN_ALL_EVENTS));
+ }
+ sched_yield();
+ }
+ });
+
+ // Modify attributes on dir and file.
+ ScopedThread stats([&] {
+ int fd, dir_fd;
+ {
+ absl::ReaderMutexLock l(&mu);
+ ASSERT_THAT(fd = open(file.c_str(), O_RDONLY), SyscallSucceeds());
+ }
+ ASSERT_THAT(dir_fd = open(dir.c_str(), O_RDONLY | O_DIRECTORY),
+ SyscallSucceeds());
+ const struct timeval times[2] = {{1, 0}, {2, 0}};
+
+ for (auto start = absl::Now(); absl::Now() - start < runtime;) {
+ {
+ absl::ReaderMutexLock l(&mu);
+ EXPECT_THAT(utimes(file.c_str(), times), SyscallSucceeds());
+ }
+ EXPECT_THAT(futimes(fd, times), SyscallSucceeds());
+ EXPECT_THAT(utimes(dir.c_str(), times), SyscallSucceeds());
+ EXPECT_THAT(futimes(dir_fd, times), SyscallSucceeds());
+ sched_yield();
+ }
+ });
+
+ // Modify extended attributes on dir and file.
+ ScopedThread xattrs([&] {
+ // TODO(gvisor.dev/issue/1636): Support extended attributes in runsc gofer.
+ if (!IsRunningOnGvisor()) {
+ int fd;
+ {
+ absl::ReaderMutexLock l(&mu);
+ ASSERT_THAT(fd = open(file.c_str(), O_RDONLY), SyscallSucceeds());
+ }
+
+ const char* name = "user.test";
+ int val = 123;
+ for (auto start = absl::Now(); absl::Now() - start < runtime;) {
+ {
+ absl::ReaderMutexLock l(&mu);
+ ASSERT_THAT(
+ setxattr(file.c_str(), name, &val, sizeof(val), /*flags=*/0),
+ SyscallSucceeds());
+ ASSERT_THAT(removexattr(file.c_str(), name), SyscallSucceeds());
+ }
+
+ ASSERT_THAT(fsetxattr(fd, name, &val, sizeof(val), /*flags=*/0),
+ SyscallSucceeds());
+ ASSERT_THAT(fremovexattr(fd, name), SyscallSucceeds());
+ sched_yield();
+ }
+ }
+ });
+
+ // Read and write file's contents. Read and write dir's entries.
+ ScopedThread read_write([&] {
+ int fd;
+ {
+ absl::ReaderMutexLock l(&mu);
+ ASSERT_THAT(fd = open(file.c_str(), O_RDWR), SyscallSucceeds());
+ }
+ for (auto start = absl::Now(); absl::Now() - start < runtime;) {
+ int val = 123;
+ ASSERT_THAT(write(fd, &val, sizeof(val)), SyscallSucceeds());
+ ASSERT_THAT(read(fd, &val, sizeof(val)), SyscallSucceeds());
+ TempPath new_file =
+ ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir));
+ ASSERT_NO_ERRNO(ListDir(dir, false));
+ new_file.reset();
+ sched_yield();
+ }
+ });
+
+ // Rename file.
+ for (auto start = absl::Now(); absl::Now() - start < runtime;) {
+ const std::string new_path = NewTempAbsPathInDir(dir);
+ {
+ absl::WriterMutexLock l(&mu);
+ ASSERT_THAT(rename(file.c_str(), new_path.c_str()), SyscallSucceeds());
+ file = new_path;
+ }
+ sched_yield();
+ }
+}
+
} // namespace
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/link.cc b/test/syscalls/linux/link.cc
index e74fa2ed5..544681168 100644
--- a/test/syscalls/linux/link.cc
+++ b/test/syscalls/linux/link.cc
@@ -79,8 +79,13 @@ TEST(LinkTest, PermissionDenied) {
// Make the file "unsafe" to link by making it only readable, but not
// writable.
- const auto oldfile =
+ const auto unwriteable_file =
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0400));
+ const std::string special_path = NewTempAbsPath();
+ ASSERT_THAT(mkfifo(special_path.c_str(), 0666), SyscallSucceeds());
+ const auto setuid_file =
+ ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0666 | S_ISUID));
+
const std::string newname = NewTempAbsPath();
// Do setuid in a separate thread so that after finishing this test, the
@@ -97,8 +102,14 @@ TEST(LinkTest, PermissionDenied) {
EXPECT_THAT(syscall(SYS_setuid, absl::GetFlag(FLAGS_scratch_uid)),
SyscallSucceeds());
- EXPECT_THAT(link(oldfile.path().c_str(), newname.c_str()),
+ EXPECT_THAT(link(unwriteable_file.path().c_str(), newname.c_str()),
+ SyscallFailsWithErrno(EPERM));
+ EXPECT_THAT(link(special_path.c_str(), newname.c_str()),
SyscallFailsWithErrno(EPERM));
+ if (!IsRunningWithVFS1()) {
+ EXPECT_THAT(link(setuid_file.path().c_str(), newname.c_str()),
+ SyscallFailsWithErrno(EPERM));
+ }
});
}
diff --git a/test/syscalls/linux/mknod.cc b/test/syscalls/linux/mknod.cc
index 4c45766c7..05dfb375a 100644
--- a/test/syscalls/linux/mknod.cc
+++ b/test/syscalls/linux/mknod.cc
@@ -15,6 +15,7 @@
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
+#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
@@ -39,7 +40,28 @@ TEST(MknodTest, RegularFile) {
EXPECT_THAT(mknod(node1.c_str(), 0, 0), SyscallSucceeds());
}
-TEST(MknodTest, MknodAtRegularFile) {
+TEST(MknodTest, RegularFilePermissions) {
+ const std::string node = NewTempAbsPath();
+ mode_t newUmask = 0077;
+ umask(newUmask);
+
+ // Attempt to open file with mode 0777. Not specifying file type should create
+ // a regualar file.
+ mode_t perms = S_IRWXU | S_IRWXG | S_IRWXO;
+ EXPECT_THAT(mknod(node.c_str(), perms, 0), SyscallSucceeds());
+
+ // In the absence of a default ACL, the permissions of the created node are
+ // (mode & ~umask). -- mknod(2)
+ mode_t wantPerms = perms & ~newUmask;
+ struct stat st;
+ ASSERT_THAT(stat(node.c_str(), &st), SyscallSucceeds());
+ ASSERT_EQ(st.st_mode & 0777, wantPerms);
+
+ // "Zero file type is equivalent to type S_IFREG." - mknod(2)
+ ASSERT_EQ(st.st_mode & S_IFMT, S_IFREG);
+}
+
+TEST(MknodTest, MknodAtFIFO) {
const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
const std::string fifo_relpath = NewTempRelPath();
const std::string fifo = JoinPath(dir.path(), fifo_relpath);
@@ -72,7 +94,7 @@ TEST(MknodTest, MknodOnExistingPathFails) {
TEST(MknodTest, UnimplementedTypesReturnError) {
const std::string path = NewTempAbsPath();
- if (IsRunningOnGvisor()) {
+ if (IsRunningWithVFS1()) {
ASSERT_THAT(mknod(path.c_str(), S_IFSOCK, 0),
SyscallFailsWithErrno(EOPNOTSUPP));
}
diff --git a/test/syscalls/linux/mount.cc b/test/syscalls/linux/mount.cc
index a3e9745cf..46b6f38db 100644
--- a/test/syscalls/linux/mount.cc
+++ b/test/syscalls/linux/mount.cc
@@ -321,6 +321,42 @@ TEST(MountTest, RenameRemoveMountPoint) {
ASSERT_THAT(rmdir(dir.path().c_str()), SyscallFailsWithErrno(EBUSY));
}
+TEST(MountTest, MountFuseFilesystemNoDevice) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
+ SKIP_IF(IsRunningOnGvisor() && !IsFUSEEnabled());
+
+ auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+
+ // Before kernel version 4.16-rc6, FUSE mount is protected by
+ // capable(CAP_SYS_ADMIN). After this version, it uses
+ // ns_capable(CAP_SYS_ADMIN) to protect. Before the 4.16 kernel, it was not
+ // allowed to mount fuse file systems without the global CAP_SYS_ADMIN.
+ int res = mount("", dir.path().c_str(), "fuse", 0, "");
+ SKIP_IF(!IsRunningOnGvisor() && res == -1 && errno == EPERM);
+
+ EXPECT_THAT(mount("", dir.path().c_str(), "fuse", 0, ""),
+ SyscallFailsWithErrno(EINVAL));
+}
+
+TEST(MountTest, MountFuseFilesystem) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
+ SKIP_IF(IsRunningOnGvisor() && !IsFUSEEnabled());
+
+ const FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/fuse", O_WRONLY));
+ std::string mopts = "fd=" + std::to_string(fd.get());
+
+ auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+
+ // See comments in MountFuseFilesystemNoDevice for the reason why we skip
+ // EPERM when running on Linux.
+ int res = mount("", dir.path().c_str(), "fuse", 0, "");
+ SKIP_IF(!IsRunningOnGvisor() && res == -1 && errno == EPERM);
+
+ auto const mount =
+ ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "fuse", 0, mopts, 0));
+}
+
} // namespace
} // namespace testing
diff --git a/test/syscalls/linux/open.cc b/test/syscalls/linux/open.cc
index 640fe6bfc..bf350946b 100644
--- a/test/syscalls/linux/open.cc
+++ b/test/syscalls/linux/open.cc
@@ -235,7 +235,7 @@ TEST_F(OpenTest, AppendOnly) {
ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDWR | O_APPEND));
EXPECT_THAT(lseek(fd2.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0));
- // Then try to write to the first file and make sure the bytes are appended.
+ // Then try to write to the first fd and make sure the bytes are appended.
EXPECT_THAT(WriteFd(fd1.get(), buf.data(), buf.size()),
SyscallSucceedsWithValue(buf.size()));
@@ -247,7 +247,7 @@ TEST_F(OpenTest, AppendOnly) {
EXPECT_THAT(lseek(fd1.get(), 0, SEEK_CUR),
SyscallSucceedsWithValue(kBufSize * 2));
- // Then try to write to the second file and make sure the bytes are appended.
+ // Then try to write to the second fd and make sure the bytes are appended.
EXPECT_THAT(WriteFd(fd2.get(), buf.data(), buf.size()),
SyscallSucceedsWithValue(buf.size()));
@@ -416,6 +416,35 @@ TEST_F(OpenTest, CanTruncateWriteOnlyNoReadPermission_NoRandomSave) {
EXPECT_EQ(stat.st_size, 0);
}
+TEST_F(OpenTest, CanTruncateWithStrangePermissions) {
+ ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
+ ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
+ const DisableSave ds; // Permissions are dropped.
+ std::string path = NewTempAbsPath();
+ int fd;
+ // Create a file without user permissions.
+ EXPECT_THAT( // SAVE_BELOW
+ fd = open(path.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 055),
+ SyscallSucceeds());
+ EXPECT_THAT(close(fd), SyscallSucceeds());
+
+ // Cannot open file because we are owner and have no permissions set.
+ EXPECT_THAT(open(path.c_str(), O_RDONLY), SyscallFailsWithErrno(EACCES));
+
+ // We *can* chmod the file, because we are the owner.
+ EXPECT_THAT(chmod(path.c_str(), 0755), SyscallSucceeds());
+
+ // Now we can open the file again.
+ EXPECT_THAT(fd = open(path.c_str(), O_RDWR), SyscallSucceeds());
+ EXPECT_THAT(close(fd), SyscallSucceeds());
+}
+
+TEST_F(OpenTest, OpenNonDirectoryWithTrailingSlash) {
+ const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ const std::string bad_path = file.path() + "/";
+ EXPECT_THAT(open(bad_path.c_str(), O_RDONLY), SyscallFailsWithErrno(ENOTDIR));
+}
+
} // namespace
} // namespace testing
diff --git a/test/syscalls/linux/packet_socket.cc b/test/syscalls/linux/packet_socket.cc
index 5ac68feb4..861617ff7 100644
--- a/test/syscalls/linux/packet_socket.cc
+++ b/test/syscalls/linux/packet_socket.cc
@@ -188,11 +188,12 @@ void ReceiveMessage(int sock, int ifindex) {
// sizeof(sockaddr_ll).
ASSERT_THAT(src_len, AnyOf(Eq(sizeof(src)), Eq(sizeof(src) - 2)));
- // TODO(b/129292371): Verify protocol once we return it.
+ // TODO(gvisor.dev/issue/173): Verify protocol once we return it.
// Verify the source address.
EXPECT_EQ(src.sll_family, AF_PACKET);
EXPECT_EQ(src.sll_ifindex, ifindex);
EXPECT_EQ(src.sll_halen, ETH_ALEN);
+ EXPECT_EQ(ntohs(src.sll_protocol), ETH_P_IP);
// This came from the loopback device, so the address is all 0s.
for (int i = 0; i < src.sll_halen; i++) {
EXPECT_EQ(src.sll_addr[i], 0);
@@ -233,7 +234,7 @@ TEST_P(CookedPacketTest, Receive) {
// Send via a packet socket.
TEST_P(CookedPacketTest, Send) {
- // TODO(b/129292371): Remove once we support packet socket writing.
+ // TODO(gvisor.dev/issue/173): Remove once we support packet socket writing.
SKIP_IF(IsRunningOnGvisor());
// Let's send a UDP packet and receive it using a regular UDP socket.
@@ -343,7 +344,7 @@ TEST_P(CookedPacketTest, BindReceive) {
}
// Double Bind socket.
-TEST_P(CookedPacketTest, DoubleBind) {
+TEST_P(CookedPacketTest, DoubleBindSucceeds) {
struct sockaddr_ll bind_addr = {};
bind_addr.sll_family = AF_PACKET;
bind_addr.sll_protocol = htons(GetParam());
@@ -354,12 +355,11 @@ TEST_P(CookedPacketTest, DoubleBind) {
SyscallSucceeds());
// Binding socket again should fail.
- ASSERT_THAT(
- bind(socket_, reinterpret_cast<struct sockaddr*>(&bind_addr),
- sizeof(bind_addr)),
- // Linux 4.09 returns EINVAL here, but some time before 4.19 it switched
- // to EADDRINUSE.
- AnyOf(SyscallFailsWithErrno(EADDRINUSE), SyscallFailsWithErrno(EINVAL)));
+ ASSERT_THAT(bind(socket_, reinterpret_cast<struct sockaddr*>(&bind_addr),
+ sizeof(bind_addr)),
+ // Linux 4.09 returns EINVAL here, but some time before 4.19 it
+ // switched to EADDRINUSE.
+ SyscallSucceeds());
}
// Bind and verify we do not receive data on interface which is not bound
@@ -417,6 +417,122 @@ TEST_P(CookedPacketTest, BindDrop) {
EXPECT_THAT(RetryEINTR(poll)(&pfd, 1, 1000), SyscallSucceedsWithValue(0));
}
+// Verify that we receive outbound packets. This test requires at least one
+// non loopback interface so that we can actually capture an outgoing packet.
+TEST_P(CookedPacketTest, ReceiveOutbound) {
+ // Only ETH_P_ALL sockets can receive outbound packets on linux.
+ SKIP_IF(GetParam() != ETH_P_ALL);
+
+ // Let's use a simple IP payload: a UDP datagram.
+ FileDescriptor udp_sock =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
+
+ struct ifaddrs* if_addr_list = nullptr;
+ auto cleanup = Cleanup([&if_addr_list]() { freeifaddrs(if_addr_list); });
+
+ ASSERT_THAT(getifaddrs(&if_addr_list), SyscallSucceeds());
+
+ // Get interface other than loopback.
+ struct ifreq ifr = {};
+ for (struct ifaddrs* i = if_addr_list; i; i = i->ifa_next) {
+ if (strcmp(i->ifa_name, "lo") != 0) {
+ strncpy(ifr.ifr_name, i->ifa_name, sizeof(ifr.ifr_name));
+ break;
+ }
+ }
+
+ // Skip if no interface is available other than loopback.
+ if (strlen(ifr.ifr_name) == 0) {
+ GTEST_SKIP();
+ }
+
+ // Get interface index and name.
+ EXPECT_THAT(ioctl(socket_, SIOCGIFINDEX, &ifr), SyscallSucceeds());
+ EXPECT_NE(ifr.ifr_ifindex, 0);
+ int ifindex = ifr.ifr_ifindex;
+
+ constexpr int kMACSize = 6;
+ char hwaddr[kMACSize];
+ // Get interface address.
+ ASSERT_THAT(ioctl(socket_, SIOCGIFHWADDR, &ifr), SyscallSucceeds());
+ ASSERT_THAT(ifr.ifr_hwaddr.sa_family,
+ AnyOf(Eq(ARPHRD_NONE), Eq(ARPHRD_ETHER)));
+ memcpy(hwaddr, ifr.ifr_hwaddr.sa_data, kMACSize);
+
+ // Just send it to the google dns server 8.8.8.8. It's UDP we don't care
+ // if it actually gets to the DNS Server we just want to see that we receive
+ // it on our AF_PACKET socket.
+ //
+ // NOTE: We just want to pick an IP that is non-local to avoid having to
+ // handle ARP as this should cause the UDP packet to be sent to the default
+ // gateway configured for the system under test. Otherwise the only packet we
+ // will see is the ARP query unless we picked an IP which will actually
+ // resolve. The test is a bit brittle but this was the best compromise for
+ // now.
+ struct sockaddr_in dest = {};
+ ASSERT_EQ(inet_pton(AF_INET, "8.8.8.8", &dest.sin_addr.s_addr), 1);
+ dest.sin_family = AF_INET;
+ dest.sin_port = kPort;
+ EXPECT_THAT(sendto(udp_sock.get(), kMessage, sizeof(kMessage), 0,
+ reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)),
+ SyscallSucceedsWithValue(sizeof(kMessage)));
+
+ // Wait and make sure the socket receives the data.
+ struct pollfd pfd = {};
+ pfd.fd = socket_;
+ pfd.events = POLLIN;
+ EXPECT_THAT(RetryEINTR(poll)(&pfd, 1, 1000), SyscallSucceedsWithValue(1));
+
+ // Now read and check that the packet is the one we just sent.
+ // Read and verify the data.
+ constexpr size_t packet_size =
+ sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kMessage);
+ char buf[64];
+ struct sockaddr_ll src = {};
+ socklen_t src_len = sizeof(src);
+ ASSERT_THAT(recvfrom(socket_, buf, sizeof(buf), 0,
+ reinterpret_cast<struct sockaddr*>(&src), &src_len),
+ SyscallSucceedsWithValue(packet_size));
+
+ // sockaddr_ll ends with an 8 byte physical address field, but ethernet
+ // addresses only use 6 bytes. Linux used to return sizeof(sockaddr_ll)-2
+ // here, but since commit b2cf86e1563e33a14a1c69b3e508d15dc12f804c returns
+ // sizeof(sockaddr_ll).
+ ASSERT_THAT(src_len, AnyOf(Eq(sizeof(src)), Eq(sizeof(src) - 2)));
+
+ // Verify the source address.
+ EXPECT_EQ(src.sll_family, AF_PACKET);
+ EXPECT_EQ(src.sll_ifindex, ifindex);
+ EXPECT_EQ(src.sll_halen, ETH_ALEN);
+ EXPECT_EQ(ntohs(src.sll_protocol), ETH_P_IP);
+ EXPECT_EQ(src.sll_pkttype, PACKET_OUTGOING);
+ // Verify the link address of the interface matches that of the non
+ // non loopback interface address we stored above.
+ for (int i = 0; i < src.sll_halen; i++) {
+ EXPECT_EQ(src.sll_addr[i], hwaddr[i]);
+ }
+
+ // Verify the IP header.
+ struct iphdr ip = {};
+ memcpy(&ip, buf, sizeof(ip));
+ EXPECT_EQ(ip.ihl, 5);
+ EXPECT_EQ(ip.version, 4);
+ EXPECT_EQ(ip.tot_len, htons(packet_size));
+ EXPECT_EQ(ip.protocol, IPPROTO_UDP);
+ EXPECT_EQ(ip.daddr, dest.sin_addr.s_addr);
+ EXPECT_NE(ip.saddr, htonl(INADDR_LOOPBACK));
+
+ // Verify the UDP header.
+ struct udphdr udp = {};
+ memcpy(&udp, buf + sizeof(iphdr), sizeof(udp));
+ EXPECT_EQ(udp.dest, kPort);
+ EXPECT_EQ(udp.len, htons(sizeof(udphdr) + sizeof(kMessage)));
+
+ // Verify the payload.
+ char* payload = reinterpret_cast<char*>(buf + sizeof(iphdr) + sizeof(udphdr));
+ EXPECT_EQ(strncmp(payload, kMessage, sizeof(kMessage)), 0);
+}
+
// Bind with invalid address.
TEST_P(CookedPacketTest, BindFail) {
// Null address.
diff --git a/test/syscalls/linux/packet_socket_raw.cc b/test/syscalls/linux/packet_socket_raw.cc
index d258d353c..a11a03415 100644
--- a/test/syscalls/linux/packet_socket_raw.cc
+++ b/test/syscalls/linux/packet_socket_raw.cc
@@ -14,6 +14,9 @@
#include <arpa/inet.h>
#include <linux/capability.h>
+#ifndef __fuchsia__
+#include <linux/filter.h>
+#endif // __fuchsia__
#include <linux/if_arp.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
@@ -97,7 +100,7 @@ class RawPacketTest : public ::testing::TestWithParam<int> {
int GetLoopbackIndex();
// The socket used for both reading and writing.
- int socket_;
+ int s_;
};
void RawPacketTest::SetUp() {
@@ -108,34 +111,58 @@ void RawPacketTest::SetUp() {
}
if (!IsRunningOnGvisor()) {
+ // Ensure that looped back packets aren't rejected by the kernel.
FileDescriptor acceptLocal = ASSERT_NO_ERRNO_AND_VALUE(
- Open("/proc/sys/net/ipv4/conf/lo/accept_local", O_RDONLY));
+ Open("/proc/sys/net/ipv4/conf/lo/accept_local", O_RDWR));
FileDescriptor routeLocalnet = ASSERT_NO_ERRNO_AND_VALUE(
- Open("/proc/sys/net/ipv4/conf/lo/route_localnet", O_RDONLY));
+ Open("/proc/sys/net/ipv4/conf/lo/route_localnet", O_RDWR));
char enabled;
ASSERT_THAT(read(acceptLocal.get(), &enabled, 1),
SyscallSucceedsWithValue(1));
- ASSERT_EQ(enabled, '1');
+ if (enabled != '1') {
+ enabled = '1';
+ ASSERT_THAT(lseek(acceptLocal.get(), 0, SEEK_SET),
+ SyscallSucceedsWithValue(0));
+ ASSERT_THAT(write(acceptLocal.get(), &enabled, 1),
+ SyscallSucceedsWithValue(1));
+ ASSERT_THAT(lseek(acceptLocal.get(), 0, SEEK_SET),
+ SyscallSucceedsWithValue(0));
+ ASSERT_THAT(read(acceptLocal.get(), &enabled, 1),
+ SyscallSucceedsWithValue(1));
+ ASSERT_EQ(enabled, '1');
+ }
+
ASSERT_THAT(read(routeLocalnet.get(), &enabled, 1),
SyscallSucceedsWithValue(1));
- ASSERT_EQ(enabled, '1');
+ if (enabled != '1') {
+ enabled = '1';
+ ASSERT_THAT(lseek(routeLocalnet.get(), 0, SEEK_SET),
+ SyscallSucceedsWithValue(0));
+ ASSERT_THAT(write(routeLocalnet.get(), &enabled, 1),
+ SyscallSucceedsWithValue(1));
+ ASSERT_THAT(lseek(routeLocalnet.get(), 0, SEEK_SET),
+ SyscallSucceedsWithValue(0));
+ ASSERT_THAT(read(routeLocalnet.get(), &enabled, 1),
+ SyscallSucceedsWithValue(1));
+ ASSERT_EQ(enabled, '1');
+ }
}
- ASSERT_THAT(socket_ = socket(AF_PACKET, SOCK_RAW, htons(GetParam())),
+ ASSERT_THAT(s_ = socket(AF_PACKET, SOCK_RAW, htons(GetParam())),
SyscallSucceeds());
}
void RawPacketTest::TearDown() {
// TearDown will be run even if we skip the test.
if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) {
- EXPECT_THAT(close(socket_), SyscallSucceeds());
+ EXPECT_THAT(close(s_), SyscallSucceeds());
}
}
int RawPacketTest::GetLoopbackIndex() {
struct ifreq ifr;
snprintf(ifr.ifr_name, IFNAMSIZ, "lo");
- EXPECT_THAT(ioctl(socket_, SIOCGIFINDEX, &ifr), SyscallSucceeds());
+ EXPECT_THAT(ioctl(s_, SIOCGIFINDEX, &ifr), SyscallSucceeds());
EXPECT_NE(ifr.ifr_ifindex, 0);
return ifr.ifr_ifindex;
}
@@ -149,7 +176,7 @@ TEST_P(RawPacketTest, Receive) {
// Wait for the socket to become readable.
struct pollfd pfd = {};
- pfd.fd = socket_;
+ pfd.fd = s_;
pfd.events = POLLIN;
EXPECT_THAT(RetryEINTR(poll)(&pfd, 1, 2000), SyscallSucceedsWithValue(1));
@@ -159,7 +186,7 @@ TEST_P(RawPacketTest, Receive) {
char buf[64];
struct sockaddr_ll src = {};
socklen_t src_len = sizeof(src);
- ASSERT_THAT(recvfrom(socket_, buf, sizeof(buf), 0,
+ ASSERT_THAT(recvfrom(s_, buf, sizeof(buf), 0,
reinterpret_cast<struct sockaddr*>(&src), &src_len),
SyscallSucceedsWithValue(packet_size));
// sockaddr_ll ends with an 8 byte physical address field, but ethernet
@@ -168,11 +195,12 @@ TEST_P(RawPacketTest, Receive) {
// sizeof(sockaddr_ll).
ASSERT_THAT(src_len, AnyOf(Eq(sizeof(src)), Eq(sizeof(src) - 2)));
- // TODO(b/129292371): Verify protocol once we return it.
+ // TODO(gvisor.dev/issue/173): Verify protocol once we return it.
// Verify the source address.
EXPECT_EQ(src.sll_family, AF_PACKET);
EXPECT_EQ(src.sll_ifindex, GetLoopbackIndex());
EXPECT_EQ(src.sll_halen, ETH_ALEN);
+ EXPECT_EQ(ntohs(src.sll_protocol), ETH_P_IP);
// This came from the loopback device, so the address is all 0s.
for (int i = 0; i < src.sll_halen; i++) {
EXPECT_EQ(src.sll_addr[i], 0);
@@ -212,7 +240,7 @@ TEST_P(RawPacketTest, Receive) {
// Send via a packet socket.
TEST_P(RawPacketTest, Send) {
- // TODO(b/129292371): Remove once we support packet socket writing.
+ // TODO(gvisor.dev/issue/173): Remove once we support packet socket writing.
SKIP_IF(IsRunningOnGvisor());
// Let's send a UDP packet and receive it using a regular UDP socket.
@@ -277,7 +305,7 @@ TEST_P(RawPacketTest, Send) {
sizeof(kMessage));
// Send it.
- ASSERT_THAT(sendto(socket_, send_buf, sizeof(send_buf), 0,
+ ASSERT_THAT(sendto(s_, send_buf, sizeof(send_buf), 0,
reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)),
SyscallSucceedsWithValue(sizeof(send_buf)));
@@ -286,13 +314,13 @@ TEST_P(RawPacketTest, Send) {
pfd.fd = udp_sock.get();
pfd.events = POLLIN;
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 5000), SyscallSucceedsWithValue(1));
- pfd.fd = socket_;
+ pfd.fd = s_;
pfd.events = POLLIN;
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 5000), SyscallSucceedsWithValue(1));
// Receive on the packet socket.
char recv_buf[sizeof(send_buf)];
- ASSERT_THAT(recv(socket_, recv_buf, sizeof(recv_buf), 0),
+ ASSERT_THAT(recv(s_, recv_buf, sizeof(recv_buf), 0),
SyscallSucceedsWithValue(sizeof(recv_buf)));
ASSERT_EQ(memcmp(recv_buf, send_buf, sizeof(send_buf)), 0);
@@ -309,6 +337,318 @@ TEST_P(RawPacketTest, Send) {
EXPECT_EQ(src.sin_addr.s_addr, htonl(INADDR_LOOPBACK));
}
+// Check that setting SO_RCVBUF below min is clamped to the minimum
+// receive buffer size.
+TEST_P(RawPacketTest, SetSocketRecvBufBelowMin) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ // Discover minimum receive buf size by trying to set it to zero.
+ // See:
+ // https://github.com/torvalds/linux/blob/a5dc8300df75e8b8384b4c82225f1e4a0b4d9b55/net/core/sock.c#L820
+ constexpr int kRcvBufSz = 0;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, sizeof(kRcvBufSz)),
+ SyscallSucceeds());
+
+ int min = 0;
+ socklen_t min_len = sizeof(min);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &min, &min_len),
+ SyscallSucceeds());
+
+ // Linux doubles the value so let's use a value that when doubled will still
+ // be smaller than min.
+ int below_min = min / 2 - 1;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &below_min, sizeof(below_min)),
+ SyscallSucceeds());
+
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &val, &val_len),
+ SyscallSucceeds());
+
+ ASSERT_EQ(min, val);
+}
+
+// Check that setting SO_RCVBUF above max is clamped to the maximum
+// receive buffer size.
+TEST_P(RawPacketTest, SetSocketRecvBufAboveMax) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ // Discover max buf size by trying to set the largest possible buffer size.
+ constexpr int kRcvBufSz = 0xffffffff;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, sizeof(kRcvBufSz)),
+ SyscallSucceeds());
+
+ int max = 0;
+ socklen_t max_len = sizeof(max);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &max, &max_len),
+ SyscallSucceeds());
+
+ int above_max = max + 1;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &above_max, sizeof(above_max)),
+ SyscallSucceeds());
+
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &val, &val_len),
+ SyscallSucceeds());
+ ASSERT_EQ(max, val);
+}
+
+// Check that setting SO_RCVBUF min <= kRcvBufSz <= max is honored.
+TEST_P(RawPacketTest, SetSocketRecvBuf) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ int max = 0;
+ int min = 0;
+ {
+ // Discover max buf size by trying to set a really large buffer size.
+ constexpr int kRcvBufSz = 0xffffffff;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, sizeof(kRcvBufSz)),
+ SyscallSucceeds());
+
+ max = 0;
+ socklen_t max_len = sizeof(max);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &max, &max_len),
+ SyscallSucceeds());
+ }
+
+ {
+ // Discover minimum buffer size by trying to set a zero size receive buffer
+ // size.
+ // See:
+ // https://github.com/torvalds/linux/blob/a5dc8300df75e8b8384b4c82225f1e4a0b4d9b55/net/core/sock.c#L820
+ constexpr int kRcvBufSz = 0;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, sizeof(kRcvBufSz)),
+ SyscallSucceeds());
+
+ socklen_t min_len = sizeof(min);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &min, &min_len),
+ SyscallSucceeds());
+ }
+
+ int quarter_sz = min + (max - min) / 4;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &quarter_sz, sizeof(quarter_sz)),
+ SyscallSucceeds());
+
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &val, &val_len),
+ SyscallSucceeds());
+
+ // Linux doubles the value set by SO_SNDBUF/SO_RCVBUF.
+ // TODO(gvisor.dev/issue/2926): Remove when Netstack matches linux behavior.
+ if (!IsRunningOnGvisor()) {
+ quarter_sz *= 2;
+ }
+ ASSERT_EQ(quarter_sz, val);
+}
+
+// Check that setting SO_SNDBUF below min is clamped to the minimum
+// receive buffer size.
+TEST_P(RawPacketTest, SetSocketSendBufBelowMin) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ // Discover minimum buffer size by trying to set it to zero.
+ constexpr int kSndBufSz = 0;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &kSndBufSz, sizeof(kSndBufSz)),
+ SyscallSucceeds());
+
+ int min = 0;
+ socklen_t min_len = sizeof(min);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &min, &min_len),
+ SyscallSucceeds());
+
+ // Linux doubles the value so let's use a value that when doubled will still
+ // be smaller than min.
+ int below_min = min / 2 - 1;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &below_min, sizeof(below_min)),
+ SyscallSucceeds());
+
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &val, &val_len),
+ SyscallSucceeds());
+
+ ASSERT_EQ(min, val);
+}
+
+// Check that setting SO_SNDBUF above max is clamped to the maximum
+// send buffer size.
+TEST_P(RawPacketTest, SetSocketSendBufAboveMax) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ // Discover maximum buffer size by trying to set it to a large value.
+ constexpr int kSndBufSz = 0xffffffff;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &kSndBufSz, sizeof(kSndBufSz)),
+ SyscallSucceeds());
+
+ int max = 0;
+ socklen_t max_len = sizeof(max);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &max, &max_len),
+ SyscallSucceeds());
+
+ int above_max = max + 1;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &above_max, sizeof(above_max)),
+ SyscallSucceeds());
+
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &val, &val_len),
+ SyscallSucceeds());
+ ASSERT_EQ(max, val);
+}
+
+// Check that setting SO_SNDBUF min <= kSndBufSz <= max is honored.
+TEST_P(RawPacketTest, SetSocketSendBuf) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ int max = 0;
+ int min = 0;
+ {
+ // Discover maximum buffer size by trying to set it to a large value.
+ constexpr int kSndBufSz = 0xffffffff;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &kSndBufSz, sizeof(kSndBufSz)),
+ 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 trying to set it to zero.
+ constexpr int kSndBufSz = 0;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &kSndBufSz, sizeof(kSndBufSz)),
+ 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_RCVBUF.
+ // TODO(gvisor.dev/issue/2926): Remove the gvisor special casing when Netstack
+ // matches linux behavior.
+ if (!IsRunningOnGvisor()) {
+ quarter_sz *= 2;
+ }
+
+ ASSERT_EQ(quarter_sz, val);
+}
+
+TEST_P(RawPacketTest, GetSocketError) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_ERROR, &val, &val_len),
+ SyscallSucceeds());
+ ASSERT_EQ(val, 0);
+}
+
+TEST_P(RawPacketTest, GetSocketErrorBind) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ {
+ // Bind to the loopback device.
+ struct sockaddr_ll bind_addr = {};
+ bind_addr.sll_family = AF_PACKET;
+ bind_addr.sll_protocol = htons(GetParam());
+ bind_addr.sll_ifindex = GetLoopbackIndex();
+
+ ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&bind_addr),
+ sizeof(bind_addr)),
+ SyscallSucceeds());
+
+ // SO_ERROR should return no errors.
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_ERROR, &val, &val_len),
+ SyscallSucceeds());
+ ASSERT_EQ(val, 0);
+ }
+
+ {
+ // Now try binding to an invalid interface.
+ struct sockaddr_ll bind_addr = {};
+ bind_addr.sll_family = AF_PACKET;
+ bind_addr.sll_protocol = htons(GetParam());
+ bind_addr.sll_ifindex = 0xffff; // Just pick a really large number.
+
+ // Binding should fail with EINVAL
+ ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&bind_addr),
+ sizeof(bind_addr)),
+ SyscallFailsWithErrno(ENODEV));
+
+ // SO_ERROR does not return error when the device is invalid.
+ // On Linux there is just one odd ball condition where this can return
+ // an error where the device was valid and then removed or disabled
+ // between the first check for index and the actual registration of
+ // the packet endpoint. On Netstack this is not possible as the stack
+ // global mutex is held during registration and check.
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_ERROR, &val, &val_len),
+ SyscallSucceeds());
+ ASSERT_EQ(val, 0);
+ }
+}
+
+#ifndef __fuchsia__
+
+TEST_P(RawPacketTest, SetSocketDetachFilterNoInstalledFilter) {
+ // TODO(gvisor.dev/2746): Support SO_ATTACH_FILTER/SO_DETACH_FILTER.
+ //
+ // gVisor returns no error on SO_DETACH_FILTER even if there is no filter
+ // attached unlike linux which does return ENOENT in such cases. This is
+ // because gVisor doesn't support SO_ATTACH_FILTER and just silently returns
+ // success.
+ if (IsRunningOnGvisor()) {
+ constexpr int val = 0;
+ ASSERT_THAT(setsockopt(s_, SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val)),
+ SyscallSucceeds());
+ return;
+ }
+ constexpr int val = 0;
+ ASSERT_THAT(setsockopt(s_, SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val)),
+ SyscallFailsWithErrno(ENOENT));
+}
+
+TEST_P(RawPacketTest, GetSocketDetachFilter) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_DETACH_FILTER, &val, &val_len),
+ SyscallFailsWithErrno(ENOPROTOOPT));
+}
+
+#endif // __fuchsia__
+
INSTANTIATE_TEST_SUITE_P(AllInetTests, RawPacketTest,
::testing::Values(ETH_P_IP, ETH_P_ALL));
diff --git a/test/syscalls/linux/ping_socket.cc b/test/syscalls/linux/ping_socket.cc
new file mode 100644
index 000000000..a9bfdb37b
--- /dev/null
+++ b/test/syscalls/linux/ping_socket.cc
@@ -0,0 +1,91 @@
+// Copyright 2020 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 <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "test/syscalls/linux/socket_test_util.h"
+#include "test/util/file_descriptor.h"
+#include "test/util/save_util.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+namespace {
+
+class PingSocket : public ::testing::Test {
+ protected:
+ // Creates a socket to be used in tests.
+ void SetUp() override;
+
+ // Closes the socket created by SetUp().
+ void TearDown() override;
+
+ // The loopback address.
+ struct sockaddr_in addr_;
+};
+
+void PingSocket::SetUp() {
+ // On some hosts ping sockets are restricted to specific groups using the
+ // sysctl "ping_group_range".
+ int s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
+ if (s < 0 && errno == EPERM) {
+ GTEST_SKIP();
+ }
+ close(s);
+
+ addr_ = {};
+ // Just a random port as the destination port number is irrelevant for ping
+ // sockets.
+ addr_.sin_port = 12345;
+ addr_.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ addr_.sin_family = AF_INET;
+}
+
+void PingSocket::TearDown() {}
+
+// Test ICMP port exhaustion returns EAGAIN.
+//
+// We disable both random/cooperative S/R for this test as it makes way too many
+// syscalls.
+TEST_F(PingSocket, ICMPPortExhaustion_NoRandomSave) {
+ DisableSave ds;
+ std::vector<FileDescriptor> sockets;
+ constexpr int kSockets = 65536;
+ addr_.sin_port = 0;
+ for (int i = 0; i < kSockets; i++) {
+ auto s =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP));
+ int ret = connect(s.get(), reinterpret_cast<struct sockaddr*>(&addr_),
+ sizeof(addr_));
+ if (ret == 0) {
+ sockets.push_back(std::move(s));
+ continue;
+ }
+ ASSERT_THAT(ret, SyscallFailsWithErrno(EAGAIN));
+ break;
+ }
+}
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/syscalls/linux/poll.cc b/test/syscalls/linux/poll.cc
index 1e35a4a8b..7a316427d 100644
--- a/test/syscalls/linux/poll.cc
+++ b/test/syscalls/linux/poll.cc
@@ -259,9 +259,9 @@ TEST_F(PollTest, Nfds) {
TEST_PCHECK(getrlimit(RLIMIT_NOFILE, &rlim) == 0);
// gVisor caps the number of FDs that epoll can use beyond RLIMIT_NOFILE.
- constexpr rlim_t gVisorMax = 1048576;
- if (rlim.rlim_cur > gVisorMax) {
- rlim.rlim_cur = gVisorMax;
+ constexpr rlim_t maxFD = 4096;
+ if (rlim.rlim_cur > maxFD) {
+ rlim.rlim_cur = maxFD;
TEST_PCHECK(setrlimit(RLIMIT_NOFILE, &rlim) == 0);
}
diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc
index 63642880a..d6b875dbf 100644
--- a/test/syscalls/linux/proc.cc
+++ b/test/syscalls/linux/proc.cc
@@ -754,8 +754,53 @@ TEST(ProcCpuinfo, RequiredFieldsArePresent) {
}
}
-TEST(ProcCpuinfo, DeniesWrite) {
- EXPECT_THAT(open("/proc/cpuinfo", O_WRONLY), SyscallFailsWithErrno(EACCES));
+TEST(ProcCpuinfo, DeniesWriteNonRoot) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_FOWNER)));
+
+ // Do setuid in a separate thread so that after finishing this test, the
+ // process can still open files the test harness created before starting this
+ // test. Otherwise, the files are created by root (UID before the test), but
+ // cannot be opened by the `uid` set below after the test. After calling
+ // setuid(non-zero-UID), there is no way to get root privileges back.
+ ScopedThread([&] {
+ // Use syscall instead of glibc setuid wrapper because we want this setuid
+ // call to only apply to this task. POSIX threads, however, require that all
+ // threads have the same UIDs, so using the setuid wrapper sets all threads'
+ // real UID.
+ // Also drops capabilities.
+ constexpr int kNobody = 65534;
+ EXPECT_THAT(syscall(SYS_setuid, kNobody), SyscallSucceeds());
+ EXPECT_THAT(open("/proc/cpuinfo", O_WRONLY), SyscallFailsWithErrno(EACCES));
+ // TODO(gvisor.dev/issue/1193): Properly support setting size attributes in
+ // kernfs.
+ if (!IsRunningOnGvisor() || IsRunningWithVFS1()) {
+ EXPECT_THAT(truncate("/proc/cpuinfo", 123),
+ SyscallFailsWithErrno(EACCES));
+ }
+ });
+}
+
+// With root privileges, it is possible to open /proc/cpuinfo with write mode,
+// but all write operations will return EIO.
+TEST(ProcCpuinfo, DeniesWriteRoot) {
+ // VFS1 does not behave differently for root/non-root.
+ SKIP_IF(IsRunningWithVFS1());
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_FOWNER)));
+
+ int fd;
+ EXPECT_THAT(fd = open("/proc/cpuinfo", O_WRONLY), SyscallSucceeds());
+ if (fd > 0) {
+ EXPECT_THAT(write(fd, "x", 1), SyscallFailsWithErrno(EIO));
+ EXPECT_THAT(pwrite(fd, "x", 1, 123), SyscallFailsWithErrno(EIO));
+ }
+ // TODO(gvisor.dev/issue/1193): Properly support setting size attributes in
+ // kernfs.
+ if (!IsRunningOnGvisor() || IsRunningWithVFS1()) {
+ if (fd > 0) {
+ EXPECT_THAT(ftruncate(fd, 123), SyscallFailsWithErrno(EIO));
+ }
+ EXPECT_THAT(truncate("/proc/cpuinfo", 123), SyscallFailsWithErrno(EIO));
+ }
}
// Sanity checks that uptime is present.
@@ -1921,7 +1966,7 @@ TEST(ProcSelfMountinfo, RequiredFieldsArePresent) {
AllOf(
// Root mount.
ContainsRegex(
- R"([0-9]+ [0-9]+ [0-9]+:[0-9]+ / / (rw|ro).*- \S+ \S+ (rw|ro)\S*)"),
+ R"([0-9]+ [0-9]+ [0-9]+:[0-9]+ /\S* / (rw|ro).*- \S+ \S+ (rw|ro)\S*)"),
// Proc mount - always rw.
ContainsRegex(
R"([0-9]+ [0-9]+ [0-9]+:[0-9]+ / /proc rw.*- \S+ \S+ rw\S*)")));
@@ -1967,30 +2012,41 @@ void CheckDuplicatesRecursively(std::string path) {
errno = 0;
struct dirent* dp = readdir(dir);
if (dp == nullptr) {
+ // Linux will return EINVAL when calling getdents on a /proc/tid/net
+ // file corresponding to a zombie task.
+ // See fs/proc/proc_net.c:proc_tgid_net_readdir().
+ //
+ // We just ignore the directory in this case.
+ if (errno == EINVAL && absl::StartsWith(path, "/proc/") &&
+ absl::EndsWith(path, "/net")) {
+ break;
+ }
+
+ // Otherwise, no errors are allowed.
ASSERT_EQ(errno, 0) << path;
break; // We're done.
}
- if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) {
+ const std::string name = dp->d_name;
+
+ if (name == "." || name == "..") {
continue;
}
// Ignore a duplicate entry if it isn't the last attempt.
if (i == max_attempts - 1) {
- ASSERT_EQ(children.find(std::string(dp->d_name)), children.end())
- << absl::StrCat(path, "/", dp->d_name);
- } else if (children.find(std::string(dp->d_name)) != children.end()) {
+ ASSERT_EQ(children.find(name), children.end())
+ << absl::StrCat(path, "/", name);
+ } else if (children.find(name) != children.end()) {
std::cerr << "Duplicate entry: " << i << ":"
- << absl::StrCat(path, "/", dp->d_name) << std::endl;
+ << absl::StrCat(path, "/", name) << std::endl;
success = false;
break;
}
- children.insert(std::string(dp->d_name));
-
- ASSERT_NE(dp->d_type, DT_UNKNOWN);
+ children.insert(name);
if (dp->d_type == DT_DIR) {
- child_dirs.push_back(std::string(dp->d_name));
+ child_dirs.push_back(name);
}
}
if (success) {
diff --git a/test/syscalls/linux/proc_net.cc b/test/syscalls/linux/proc_net.cc
index cac394910..3377b65cf 100644
--- a/test/syscalls/linux/proc_net.cc
+++ b/test/syscalls/linux/proc_net.cc
@@ -20,8 +20,13 @@
#include <sys/syscall.h>
#include <sys/types.h>
+#include <vector>
+
#include "gtest/gtest.h"
+#include "absl/strings/numbers.h"
+#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
+#include "absl/strings/string_view.h"
#include "absl/time/clock.h"
#include "test/syscalls/linux/socket_test_util.h"
#include "test/util/capability_util.h"
@@ -404,6 +409,74 @@ TEST(ProcNetSnmp, UdpIn_NoRandomSave) {
EXPECT_EQ(oldInDatagrams, newInDatagrams - 1);
}
+TEST(ProcNetSnmp, CheckNetStat) {
+ // TODO(b/155123175): SNMP and netstat don't work on gVisor.
+ SKIP_IF(IsRunningOnGvisor());
+
+ std::string contents =
+ ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/netstat"));
+
+ int name_count = 0;
+ int value_count = 0;
+ std::vector<absl::string_view> lines = absl::StrSplit(contents, '\n');
+ for (int i = 0; i + 1 < lines.size(); i += 2) {
+ std::vector<absl::string_view> names =
+ absl::StrSplit(lines[i], absl::ByAnyChar("\t "));
+ std::vector<absl::string_view> values =
+ absl::StrSplit(lines[i + 1], absl::ByAnyChar("\t "));
+ EXPECT_EQ(names.size(), values.size()) << " mismatch in lines '" << lines[i]
+ << "' and '" << lines[i + 1] << "'";
+ for (int j = 0; j < names.size() && j < values.size(); ++j) {
+ if (names[j] == "TCPOrigDataSent" || names[j] == "TCPSynRetrans" ||
+ names[j] == "TCPDSACKRecv" || names[j] == "TCPDSACKOfoRecv") {
+ ++name_count;
+ int64_t val;
+ if (absl::SimpleAtoi(values[j], &val)) {
+ ++value_count;
+ }
+ }
+ }
+ }
+ EXPECT_EQ(name_count, 4);
+ EXPECT_EQ(value_count, 4);
+}
+
+TEST(ProcNetSnmp, Stat) {
+ struct stat st = {};
+ ASSERT_THAT(stat("/proc/net/snmp", &st), SyscallSucceeds());
+}
+
+TEST(ProcNetSnmp, CheckSnmp) {
+ // TODO(b/155123175): SNMP and netstat don't work on gVisor.
+ SKIP_IF(IsRunningOnGvisor());
+
+ std::string contents =
+ ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/snmp"));
+
+ int name_count = 0;
+ int value_count = 0;
+ std::vector<absl::string_view> lines = absl::StrSplit(contents, '\n');
+ for (int i = 0; i + 1 < lines.size(); i += 2) {
+ std::vector<absl::string_view> names =
+ absl::StrSplit(lines[i], absl::ByAnyChar("\t "));
+ std::vector<absl::string_view> values =
+ absl::StrSplit(lines[i + 1], absl::ByAnyChar("\t "));
+ EXPECT_EQ(names.size(), values.size()) << " mismatch in lines '" << lines[i]
+ << "' and '" << lines[i + 1] << "'";
+ for (int j = 0; j < names.size() && j < values.size(); ++j) {
+ if (names[j] == "RetransSegs") {
+ ++name_count;
+ int64_t val;
+ if (absl::SimpleAtoi(values[j], &val)) {
+ ++value_count;
+ }
+ }
+ }
+ }
+ EXPECT_EQ(name_count, 1);
+ EXPECT_EQ(value_count, 1);
+}
+
} // namespace
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/pty.cc b/test/syscalls/linux/pty.cc
index b8a0159ba..f9392b9e0 100644
--- a/test/syscalls/linux/pty.cc
+++ b/test/syscalls/linux/pty.cc
@@ -364,6 +364,12 @@ PosixErrorOr<size_t> PollAndReadFd(int fd, void* buf, size_t count,
ssize_t n =
ReadFd(fd, static_cast<char*>(buf) + completed, count - completed);
if (n < 0) {
+ if (errno == EAGAIN) {
+ // Linux sometimes returns EAGAIN from this read, despite the fact that
+ // poll returned success. Let's just do what do as we are told and try
+ // again.
+ continue;
+ }
return PosixError(errno, "read failed");
}
completed += n;
@@ -628,6 +634,11 @@ TEST_F(PtyTest, TermiosAffectsSlave) {
// Verify this by setting ICRNL (which rewrites input \r to \n) and verify that
// it has no effect on the master.
TEST_F(PtyTest, MasterTermiosUnchangable) {
+ struct kernel_termios master_termios = {};
+ EXPECT_THAT(ioctl(master_.get(), TCGETS, &master_termios), SyscallSucceeds());
+ master_termios.c_lflag |= ICRNL;
+ EXPECT_THAT(ioctl(master_.get(), TCSETS, &master_termios), SyscallSucceeds());
+
char c = '\r';
ASSERT_THAT(WriteFd(slave_.get(), &c, 1), SyscallSucceedsWithValue(1));
diff --git a/test/syscalls/linux/pty_root.cc b/test/syscalls/linux/pty_root.cc
index 14a4af980..1d7dbefdb 100644
--- a/test/syscalls/linux/pty_root.cc
+++ b/test/syscalls/linux/pty_root.cc
@@ -25,16 +25,26 @@
namespace gvisor {
namespace testing {
-// These tests should be run as root.
namespace {
+// StealTTY tests whether privileged processes can steal controlling terminals.
+// If the stealing process has CAP_SYS_ADMIN in the root user namespace, the
+// test ensures that stealing works. If it has non-root CAP_SYS_ADMIN, it
+// ensures stealing fails.
TEST(JobControlRootTest, StealTTY) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
- // Make this a session leader, which also drops the controlling terminal.
- // In the gVisor test environment, this test will be run as the session
- // leader already (as the sentry init process).
+ bool true_root = true;
if (!IsRunningOnGvisor()) {
+ // If running in Linux, we may only have CAP_SYS_ADMIN in a non-root user
+ // namespace (i.e. we are not truly root). We use init_module as a proxy for
+ // whether we are true root, as it returns EPERM immediately.
+ ASSERT_THAT(syscall(SYS_init_module, nullptr, 0, nullptr), SyscallFails());
+ true_root = errno != EPERM;
+
+ // Make this a session leader, which also drops the controlling terminal.
+ // In the gVisor test environment, this test will be run as the session
+ // leader already (as the sentry init process).
ASSERT_THAT(setsid(), SyscallSucceeds());
}
@@ -53,8 +63,8 @@ TEST(JobControlRootTest, StealTTY) {
ASSERT_THAT(setsid(), SyscallSucceeds());
// We shouldn't be able to steal the terminal with the wrong arg value.
TEST_PCHECK(ioctl(slave.get(), TIOCSCTTY, 0));
- // We should be able to steal it here.
- TEST_PCHECK(!ioctl(slave.get(), TIOCSCTTY, 1));
+ // We should be able to steal it if we are true root.
+ TEST_PCHECK(true_root == !ioctl(slave.get(), TIOCSCTTY, 1));
_exit(0);
}
diff --git a/test/syscalls/linux/pwritev2.cc b/test/syscalls/linux/pwritev2.cc
index 3fe5a600f..63b686c62 100644
--- a/test/syscalls/linux/pwritev2.cc
+++ b/test/syscalls/linux/pwritev2.cc
@@ -69,7 +69,7 @@ ssize_t pwritev2(unsigned long fd, const struct iovec* iov,
}
// This test is the base case where we call pwritev (no offset, no flags).
-TEST(Writev2Test, TestBaseCall) {
+TEST(Writev2Test, BaseCall) {
SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
@@ -97,7 +97,7 @@ TEST(Writev2Test, TestBaseCall) {
}
// This test is where we call pwritev2 with a positive offset and no flags.
-TEST(Pwritev2Test, TestValidPositiveOffset) {
+TEST(Pwritev2Test, ValidPositiveOffset) {
SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
std::string prefix(kBufSize, '0');
@@ -129,7 +129,7 @@ TEST(Pwritev2Test, TestValidPositiveOffset) {
// This test is the base case where we call writev by using -1 as the offset.
// The write should use the file offset, so the test increments the file offset
// prior to call pwritev2.
-TEST(Pwritev2Test, TestNegativeOneOffset) {
+TEST(Pwritev2Test, NegativeOneOffset) {
SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
const std::string prefix = "00";
@@ -164,7 +164,7 @@ TEST(Pwritev2Test, TestNegativeOneOffset) {
// pwritev2 requires if the RWF_HIPRI flag is passed, the fd must be opened with
// O_DIRECT. This test implements a correct call with the RWF_HIPRI flag.
-TEST(Pwritev2Test, TestCallWithRWF_HIPRI) {
+TEST(Pwritev2Test, CallWithRWF_HIPRI) {
SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
@@ -189,47 +189,8 @@ TEST(Pwritev2Test, TestCallWithRWF_HIPRI) {
EXPECT_EQ(buf, content);
}
-// This test checks that pwritev2 can be called with valid flags
-TEST(Pwritev2Test, TestCallWithValidFlags) {
- SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
-
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
-
- std::vector<char> content(kBufSize, '0');
- struct iovec iov;
- iov.iov_base = content.data();
- iov.iov_len = content.size();
-
- EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1,
- /*offset=*/0, /*flags=*/RWF_DSYNC),
- SyscallSucceedsWithValue(kBufSize));
-
- std::vector<char> buf(content.size());
- EXPECT_THAT(read(fd.get(), buf.data(), buf.size()),
- SyscallSucceedsWithValue(buf.size()));
-
- EXPECT_EQ(buf, content);
-
- SetContent(content);
-
- EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1,
- /*offset=*/0, /*flags=*/0x4),
- SyscallSucceedsWithValue(kBufSize));
-
- ASSERT_THAT(lseek(fd.get(), 0, SEEK_CUR),
- SyscallSucceedsWithValue(content.size()));
-
- EXPECT_THAT(pread(fd.get(), buf.data(), buf.size(), /*offset=*/0),
- SyscallSucceedsWithValue(buf.size()));
-
- EXPECT_EQ(buf, content);
-}
-
// This test calls pwritev2 with a bad file descriptor.
-TEST(Writev2Test, TestBadFile) {
+TEST(Writev2Test, BadFile) {
SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
ASSERT_THAT(pwritev2(/*fd=*/-1, /*iov=*/nullptr, /*iovcnt=*/0,
/*offset=*/0, /*flags=*/0),
@@ -237,7 +198,7 @@ TEST(Writev2Test, TestBadFile) {
}
// This test calls pwrite2 with an invalid offset.
-TEST(Pwritev2Test, TestInvalidOffset) {
+TEST(Pwritev2Test, InvalidOffset) {
SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
@@ -255,7 +216,7 @@ TEST(Pwritev2Test, TestInvalidOffset) {
SyscallFailsWithErrno(EINVAL));
}
-TEST(Pwritev2Test, TestUnseekableFileValid) {
+TEST(Pwritev2Test, UnseekableFileValid) {
SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
int pipe_fds[2];
@@ -285,7 +246,7 @@ TEST(Pwritev2Test, TestUnseekableFileValid) {
// Calling pwritev2 with a non-negative offset calls pwritev. Calling pwritev
// with an unseekable file is not allowed. A pipe is used for an unseekable
// file.
-TEST(Pwritev2Test, TestUnseekableFileInValid) {
+TEST(Pwritev2Test, UnseekableFileInvalid) {
SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
int pipe_fds[2];
@@ -304,7 +265,7 @@ TEST(Pwritev2Test, TestUnseekableFileInValid) {
EXPECT_THAT(close(pipe_fds[1]), SyscallSucceeds());
}
-TEST(Pwritev2Test, TestReadOnlyFile) {
+TEST(Pwritev2Test, ReadOnlyFile) {
SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
@@ -323,7 +284,7 @@ TEST(Pwritev2Test, TestReadOnlyFile) {
}
// This test calls pwritev2 with an invalid flag.
-TEST(Pwritev2Test, TestInvalidFlag) {
+TEST(Pwritev2Test, InvalidFlag) {
SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS);
const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
diff --git a/test/syscalls/linux/raw_socket.cc b/test/syscalls/linux/raw_socket.cc
new file mode 100644
index 000000000..8d6e5c913
--- /dev/null
+++ b/test/syscalls/linux/raw_socket.cc
@@ -0,0 +1,869 @@
+// 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 <linux/capability.h>
+#ifndef __fuchsia__
+#include <linux/filter.h>
+#endif // __fuchsia__
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/ip_icmp.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+
+#include "gtest/gtest.h"
+#include "test/syscalls/linux/socket_test_util.h"
+#include "test/syscalls/linux/unix_domain_socket_test_util.h"
+#include "test/util/capability_util.h"
+#include "test/util/file_descriptor.h"
+#include "test/util/test_util.h"
+
+// Note: in order to run these tests, /proc/sys/net/ipv4/ping_group_range will
+// need to be configured to let the superuser create ping sockets (see icmp(7)).
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+// Fixture for tests parameterized by protocol.
+class RawSocketTest : public ::testing::TestWithParam<std::tuple<int, int>> {
+ protected:
+ // Creates a socket to be used in tests.
+ void SetUp() override;
+
+ // Closes the socket created by SetUp().
+ void TearDown() override;
+
+ // Sends buf via s_.
+ void SendBuf(const char* buf, int buf_len);
+
+ // Reads from s_ into recv_buf.
+ void ReceiveBuf(char* recv_buf, size_t recv_buf_len);
+
+ void ReceiveBufFrom(int sock, char* recv_buf, size_t recv_buf_len);
+
+ int Protocol() { return std::get<0>(GetParam()); }
+
+ int Family() { return std::get<1>(GetParam()); }
+
+ socklen_t AddrLen() {
+ if (Family() == AF_INET) {
+ return sizeof(sockaddr_in);
+ }
+ return sizeof(sockaddr_in6);
+ }
+
+ int HdrLen() {
+ if (Family() == AF_INET) {
+ return sizeof(struct iphdr);
+ }
+ // IPv6 raw sockets don't include the header.
+ return 0;
+ }
+
+ // The socket used for both reading and writing.
+ int s_;
+
+ // The loopback address.
+ struct sockaddr_storage addr_;
+};
+
+void RawSocketTest::SetUp() {
+ if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) {
+ ASSERT_THAT(socket(Family(), SOCK_RAW, Protocol()),
+ SyscallFailsWithErrno(EPERM));
+ GTEST_SKIP();
+ }
+
+ ASSERT_THAT(s_ = socket(Family(), SOCK_RAW, Protocol()), SyscallSucceeds());
+
+ addr_ = {};
+
+ // We don't set ports because raw sockets don't have a notion of ports.
+ if (Family() == AF_INET) {
+ struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(&addr_);
+ sin->sin_family = AF_INET;
+ sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ } else {
+ struct sockaddr_in6* sin6 = reinterpret_cast<struct sockaddr_in6*>(&addr_);
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_addr = in6addr_loopback;
+ }
+}
+
+void RawSocketTest::TearDown() {
+ // TearDown will be run even if we skip the test.
+ if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) {
+ EXPECT_THAT(close(s_), SyscallSucceeds());
+ }
+}
+
+// We should be able to create multiple raw sockets for the same protocol.
+// BasicRawSocket::Setup creates the first one, so we only have to create one
+// more here.
+TEST_P(RawSocketTest, MultipleCreation) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ int s2;
+ ASSERT_THAT(s2 = socket(Family(), SOCK_RAW, Protocol()), SyscallSucceeds());
+
+ ASSERT_THAT(close(s2), SyscallSucceeds());
+}
+
+// Test that shutting down an unconnected socket fails.
+TEST_P(RawSocketTest, FailShutdownWithoutConnect) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ ASSERT_THAT(shutdown(s_, SHUT_WR), SyscallFailsWithErrno(ENOTCONN));
+ ASSERT_THAT(shutdown(s_, SHUT_RD), SyscallFailsWithErrno(ENOTCONN));
+}
+
+// Shutdown is a no-op for raw sockets (and datagram sockets in general).
+TEST_P(RawSocketTest, ShutdownWriteNoop) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ ASSERT_THAT(
+ connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()),
+ SyscallSucceeds());
+ ASSERT_THAT(shutdown(s_, SHUT_WR), SyscallSucceeds());
+
+ // Arbitrary.
+ constexpr char kBuf[] = "noop";
+ ASSERT_THAT(RetryEINTR(write)(s_, kBuf, sizeof(kBuf)),
+ SyscallSucceedsWithValue(sizeof(kBuf)));
+}
+
+// Shutdown is a no-op for raw sockets (and datagram sockets in general).
+TEST_P(RawSocketTest, ShutdownReadNoop) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ ASSERT_THAT(
+ connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()),
+ SyscallSucceeds());
+ ASSERT_THAT(shutdown(s_, SHUT_RD), SyscallSucceeds());
+
+ // Arbitrary.
+ constexpr char kBuf[] = "gdg";
+ ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf)));
+
+ std::vector<char> c(sizeof(kBuf) + HdrLen());
+ ASSERT_THAT(read(s_, c.data(), c.size()), SyscallSucceedsWithValue(c.size()));
+}
+
+// Test that listen() fails.
+TEST_P(RawSocketTest, FailListen) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ ASSERT_THAT(listen(s_, 1), SyscallFailsWithErrno(ENOTSUP));
+}
+
+// Test that accept() fails.
+TEST_P(RawSocketTest, FailAccept) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ struct sockaddr saddr;
+ socklen_t addrlen;
+ ASSERT_THAT(accept(s_, &saddr, &addrlen), SyscallFailsWithErrno(ENOTSUP));
+}
+
+// Test that getpeername() returns nothing before connect().
+TEST_P(RawSocketTest, FailGetPeerNameBeforeConnect) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ struct sockaddr saddr;
+ socklen_t addrlen = sizeof(saddr);
+ ASSERT_THAT(getpeername(s_, &saddr, &addrlen),
+ SyscallFailsWithErrno(ENOTCONN));
+}
+
+// Test that getpeername() returns something after connect().
+TEST_P(RawSocketTest, GetPeerName) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ ASSERT_THAT(
+ connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()),
+ SyscallSucceeds());
+ struct sockaddr saddr;
+ socklen_t addrlen = sizeof(saddr);
+ ASSERT_THAT(getpeername(s_, &saddr, &addrlen),
+ SyscallFailsWithErrno(ENOTCONN));
+ ASSERT_GT(addrlen, 0);
+}
+
+// Test that the socket is writable immediately.
+TEST_P(RawSocketTest, PollWritableImmediately) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ struct pollfd pfd = {};
+ pfd.fd = s_;
+ pfd.events = POLLOUT;
+ ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 10000), SyscallSucceedsWithValue(1));
+}
+
+// Test that the socket isn't readable before receiving anything.
+TEST_P(RawSocketTest, PollNotReadableInitially) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ // Try to receive data with MSG_DONTWAIT, which returns immediately if there's
+ // nothing to be read.
+ char buf[117];
+ ASSERT_THAT(RetryEINTR(recv)(s_, buf, sizeof(buf), MSG_DONTWAIT),
+ SyscallFailsWithErrno(EAGAIN));
+}
+
+// Test that the socket becomes readable once something is written to it.
+TEST_P(RawSocketTest, PollTriggeredOnWrite) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ // Write something so that there's data to be read.
+ // Arbitrary.
+ constexpr char kBuf[] = "JP5";
+ ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf)));
+
+ struct pollfd pfd = {};
+ pfd.fd = s_;
+ pfd.events = POLLIN;
+ ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 10000), SyscallSucceedsWithValue(1));
+}
+
+// Test that we can connect() to a valid IP (loopback).
+TEST_P(RawSocketTest, ConnectToLoopback) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ ASSERT_THAT(
+ connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()),
+ SyscallSucceeds());
+}
+
+// Test that calling send() without connect() fails.
+TEST_P(RawSocketTest, SendWithoutConnectFails) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ // Arbitrary.
+ constexpr char kBuf[] = "Endgame was good";
+ ASSERT_THAT(send(s_, kBuf, sizeof(kBuf), 0),
+ SyscallFailsWithErrno(EDESTADDRREQ));
+}
+
+// Wildcard Bind.
+TEST_P(RawSocketTest, BindToWildcard) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+ struct sockaddr_storage addr;
+ addr = {};
+
+ // We don't set ports because raw sockets don't have a notion of ports.
+ if (Family() == AF_INET) {
+ struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(&addr);
+ sin->sin_family = AF_INET;
+ sin->sin_addr.s_addr = htonl(INADDR_ANY);
+ } else {
+ struct sockaddr_in6* sin6 = reinterpret_cast<struct sockaddr_in6*>(&addr);
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_addr = in6addr_any;
+ }
+
+ ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()),
+ SyscallSucceeds());
+}
+
+// Bind to localhost.
+TEST_P(RawSocketTest, BindToLocalhost) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ ASSERT_THAT(
+ bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()),
+ SyscallSucceeds());
+}
+
+// Bind to a different address.
+TEST_P(RawSocketTest, BindToInvalid) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ struct sockaddr_storage bind_addr = addr_;
+ if (Family() == AF_INET) {
+ struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(&bind_addr);
+ sin->sin_addr = {1}; // 1.0.0.0 - An address that we can't bind to.
+ } else {
+ struct sockaddr_in6* sin6 =
+ reinterpret_cast<struct sockaddr_in6*>(&bind_addr);
+ memset(&sin6->sin6_addr.s6_addr, 0, sizeof(sin6->sin6_addr.s6_addr));
+ sin6->sin6_addr.s6_addr[0] = 1; // 1: - An address that we can't bind to.
+ }
+ ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&bind_addr),
+ AddrLen()), SyscallFailsWithErrno(EADDRNOTAVAIL));
+}
+
+// Send and receive an packet.
+TEST_P(RawSocketTest, SendAndReceive) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ // Arbitrary.
+ constexpr char kBuf[] = "TB12";
+ ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf)));
+
+ // Receive the packet and make sure it's identical.
+ std::vector<char> recv_buf(sizeof(kBuf) + HdrLen());
+ ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf.data(), recv_buf.size()));
+ EXPECT_EQ(memcmp(recv_buf.data() + HdrLen(), kBuf, sizeof(kBuf)), 0);
+}
+
+// We should be able to create multiple raw sockets for the same protocol and
+// receive the same packet on both.
+TEST_P(RawSocketTest, MultipleSocketReceive) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ int s2;
+ ASSERT_THAT(s2 = socket(Family(), SOCK_RAW, Protocol()), SyscallSucceeds());
+
+ // Arbitrary.
+ constexpr char kBuf[] = "TB10";
+ ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf)));
+
+ // Receive it on socket 1.
+ std::vector<char> recv_buf1(sizeof(kBuf) + HdrLen());
+ ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf1.data(), recv_buf1.size()));
+
+ // Receive it on socket 2.
+ std::vector<char> recv_buf2(sizeof(kBuf) + HdrLen());
+ ASSERT_NO_FATAL_FAILURE(ReceiveBufFrom(s2, recv_buf2.data(),
+ recv_buf2.size()));
+
+ EXPECT_EQ(memcmp(recv_buf1.data() + HdrLen(),
+ recv_buf2.data() + HdrLen(), sizeof(kBuf)),
+ 0);
+
+ ASSERT_THAT(close(s2), SyscallSucceeds());
+}
+
+// Test that connect sends packets to the right place.
+TEST_P(RawSocketTest, SendAndReceiveViaConnect) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ ASSERT_THAT(
+ connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()),
+ SyscallSucceeds());
+
+ // Arbitrary.
+ constexpr char kBuf[] = "JH4";
+ ASSERT_THAT(send(s_, kBuf, sizeof(kBuf), 0),
+ SyscallSucceedsWithValue(sizeof(kBuf)));
+
+ // Receive the packet and make sure it's identical.
+ std::vector<char> recv_buf(sizeof(kBuf) + HdrLen());
+ ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf.data(), recv_buf.size()));
+ EXPECT_EQ(memcmp(recv_buf.data() + HdrLen(), kBuf, sizeof(kBuf)), 0);
+}
+
+// Bind to localhost, then send and receive packets.
+TEST_P(RawSocketTest, BindSendAndReceive) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ ASSERT_THAT(
+ bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()),
+ SyscallSucceeds());
+
+ // Arbitrary.
+ constexpr char kBuf[] = "DR16";
+ ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf)));
+
+ // Receive the packet and make sure it's identical.
+ std::vector<char> recv_buf(sizeof(kBuf) + HdrLen());
+ ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf.data(), recv_buf.size()));
+ EXPECT_EQ(memcmp(recv_buf.data() + HdrLen(), kBuf, sizeof(kBuf)), 0);
+}
+
+// Bind and connect to localhost and send/receive packets.
+TEST_P(RawSocketTest, BindConnectSendAndReceive) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ ASSERT_THAT(
+ bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()),
+ SyscallSucceeds());
+ ASSERT_THAT(
+ connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()),
+ SyscallSucceeds());
+
+ // Arbitrary.
+ constexpr char kBuf[] = "DG88";
+ ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf)));
+
+ // Receive the packet and make sure it's identical.
+ std::vector<char> recv_buf(sizeof(kBuf) + HdrLen());
+ ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf.data(), recv_buf.size()));
+ EXPECT_EQ(memcmp(recv_buf.data() + HdrLen(), kBuf, sizeof(kBuf)), 0);
+}
+
+// Check that setting SO_RCVBUF below min is clamped to the minimum
+// receive buffer size.
+TEST_P(RawSocketTest, SetSocketRecvBufBelowMin) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ // Discover minimum receive buf size by trying to set it to zero.
+ // See:
+ // https://github.com/torvalds/linux/blob/a5dc8300df75e8b8384b4c82225f1e4a0b4d9b55/net/core/sock.c#L820
+ constexpr int kRcvBufSz = 0;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, sizeof(kRcvBufSz)),
+ SyscallSucceeds());
+
+ int min = 0;
+ socklen_t min_len = sizeof(min);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &min, &min_len),
+ SyscallSucceeds());
+
+ // Linux doubles the value so let's use a value that when doubled will still
+ // be smaller than min.
+ int below_min = min / 2 - 1;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &below_min, sizeof(below_min)),
+ SyscallSucceeds());
+
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &val, &val_len),
+ SyscallSucceeds());
+
+ ASSERT_EQ(min, val);
+}
+
+// Check that setting SO_RCVBUF above max is clamped to the maximum
+// receive buffer size.
+TEST_P(RawSocketTest, SetSocketRecvBufAboveMax) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ // Discover max buf size by trying to set the largest possible buffer size.
+ constexpr int kRcvBufSz = 0xffffffff;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, sizeof(kRcvBufSz)),
+ SyscallSucceeds());
+
+ int max = 0;
+ socklen_t max_len = sizeof(max);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &max, &max_len),
+ SyscallSucceeds());
+
+ int above_max = max + 1;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &above_max, sizeof(above_max)),
+ SyscallSucceeds());
+
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &val, &val_len),
+ SyscallSucceeds());
+ ASSERT_EQ(max, val);
+}
+
+// Check that setting SO_RCVBUF min <= kRcvBufSz <= max is honored.
+TEST_P(RawSocketTest, SetSocketRecvBuf) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ int max = 0;
+ int min = 0;
+ {
+ // Discover max buf size by trying to set a really large buffer size.
+ constexpr int kRcvBufSz = 0xffffffff;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, sizeof(kRcvBufSz)),
+ SyscallSucceeds());
+
+ max = 0;
+ socklen_t max_len = sizeof(max);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &max, &max_len),
+ SyscallSucceeds());
+ }
+
+ {
+ // Discover minimum buffer size by trying to set a zero size receive buffer
+ // size.
+ // See:
+ // https://github.com/torvalds/linux/blob/a5dc8300df75e8b8384b4c82225f1e4a0b4d9b55/net/core/sock.c#L820
+ constexpr int kRcvBufSz = 0;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, sizeof(kRcvBufSz)),
+ SyscallSucceeds());
+
+ socklen_t min_len = sizeof(min);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &min, &min_len),
+ SyscallSucceeds());
+ }
+
+ int quarter_sz = min + (max - min) / 4;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &quarter_sz, sizeof(quarter_sz)),
+ SyscallSucceeds());
+
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &val, &val_len),
+ SyscallSucceeds());
+
+ // Linux doubles the value set by SO_SNDBUF/SO_RCVBUF.
+ // TODO(gvisor.dev/issue/2926): Remove when Netstack matches linux behavior.
+ if (!IsRunningOnGvisor()) {
+ quarter_sz *= 2;
+ }
+ ASSERT_EQ(quarter_sz, val);
+}
+
+// Check that setting SO_SNDBUF below min is clamped to the minimum
+// receive buffer size.
+TEST_P(RawSocketTest, SetSocketSendBufBelowMin) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ // Discover minimum buffer size by trying to set it to zero.
+ constexpr int kSndBufSz = 0;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &kSndBufSz, sizeof(kSndBufSz)),
+ SyscallSucceeds());
+
+ int min = 0;
+ socklen_t min_len = sizeof(min);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &min, &min_len),
+ SyscallSucceeds());
+
+ // Linux doubles the value so let's use a value that when doubled will still
+ // be smaller than min.
+ int below_min = min / 2 - 1;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &below_min, sizeof(below_min)),
+ SyscallSucceeds());
+
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &val, &val_len),
+ SyscallSucceeds());
+
+ ASSERT_EQ(min, val);
+}
+
+// Check that setting SO_SNDBUF above max is clamped to the maximum
+// send buffer size.
+TEST_P(RawSocketTest, SetSocketSendBufAboveMax) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ // Discover maximum buffer size by trying to set it to a large value.
+ constexpr int kSndBufSz = 0xffffffff;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &kSndBufSz, sizeof(kSndBufSz)),
+ SyscallSucceeds());
+
+ int max = 0;
+ socklen_t max_len = sizeof(max);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &max, &max_len),
+ SyscallSucceeds());
+
+ int above_max = max + 1;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &above_max, sizeof(above_max)),
+ SyscallSucceeds());
+
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &val, &val_len),
+ SyscallSucceeds());
+ ASSERT_EQ(max, val);
+}
+
+// Check that setting SO_SNDBUF min <= kSndBufSz <= max is honored.
+TEST_P(RawSocketTest, SetSocketSendBuf) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ int max = 0;
+ int min = 0;
+ {
+ // Discover maximum buffer size by trying to set it to a large value.
+ constexpr int kSndBufSz = 0xffffffff;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &kSndBufSz, sizeof(kSndBufSz)),
+ 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 trying to set it to zero.
+ constexpr int kSndBufSz = 0;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &kSndBufSz, sizeof(kSndBufSz)),
+ 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_RCVBUF.
+ // TODO(gvisor.dev/issue/2926): Remove the gvisor special casing when Netstack
+ // matches linux behavior.
+ if (!IsRunningOnGvisor()) {
+ quarter_sz *= 2;
+ }
+
+ ASSERT_EQ(quarter_sz, val);
+}
+
+// Test that receive buffer limits are not enforced when the recv buffer is
+// empty.
+TEST_P(RawSocketTest, RecvBufLimitsEmptyRecvBuffer) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ ASSERT_THAT(
+ bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()),
+ SyscallSucceeds());
+ ASSERT_THAT(
+ connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()),
+ SyscallSucceeds());
+
+ int min = 0;
+ {
+ // Discover minimum buffer size by trying to set it to zero.
+ constexpr int kRcvBufSz = 0;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, sizeof(kRcvBufSz)),
+ SyscallSucceeds());
+
+ socklen_t min_len = sizeof(min);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &min, &min_len),
+ SyscallSucceeds());
+ }
+
+ {
+ // Send data of size min and verify that it's received.
+ std::vector<char> buf(min);
+ RandomizeBuffer(buf.data(), buf.size());
+ ASSERT_NO_FATAL_FAILURE(SendBuf(buf.data(), buf.size()));
+
+ // Receive the packet and make sure it's identical.
+ std::vector<char> recv_buf(buf.size() + HdrLen());
+ ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf.data(), recv_buf.size()));
+ EXPECT_EQ(
+ memcmp(recv_buf.data() + HdrLen(), buf.data(), buf.size()),
+ 0);
+ }
+
+ {
+ // Send data of size min + 1 and verify that its received. Both linux and
+ // Netstack accept a dgram that exceeds rcvBuf limits if the receive buffer
+ // is currently empty.
+ std::vector<char> buf(min + 1);
+ RandomizeBuffer(buf.data(), buf.size());
+ ASSERT_NO_FATAL_FAILURE(SendBuf(buf.data(), buf.size()));
+ // Receive the packet and make sure it's identical.
+ std::vector<char> recv_buf(buf.size() + HdrLen());
+ ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf.data(), recv_buf.size()));
+ EXPECT_EQ(
+ memcmp(recv_buf.data() + HdrLen(), buf.data(), buf.size()),
+ 0);
+ }
+}
+
+TEST_P(RawSocketTest, RecvBufLimits) {
+ // TCP stack generates RSTs for unknown endpoints and it complicates the test
+ // as we have to deal with the RST packets as well. For testing the raw socket
+ // endpoints buffer limit enforcement we can just test for UDP.
+ //
+ // We don't use SKIP_IF here because root_test_runner explicitly fails if a
+ // test is skipped.
+ if (Protocol() == IPPROTO_TCP) {
+ return;
+ }
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ ASSERT_THAT(
+ bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()),
+ SyscallSucceeds());
+ ASSERT_THAT(
+ connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()),
+ SyscallSucceeds());
+
+ int min = 0;
+ {
+ // Discover minimum buffer size by trying to set it to zero.
+ constexpr int kRcvBufSz = 0;
+ ASSERT_THAT(
+ setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &kRcvBufSz, sizeof(kRcvBufSz)),
+ SyscallSucceeds());
+
+ socklen_t min_len = sizeof(min);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &min, &min_len),
+ SyscallSucceeds());
+ }
+
+ // Now set the limit to min * 2.
+ int new_rcv_buf_sz = min * 4;
+ if (!IsRunningOnGvisor()) {
+ // Linux doubles the value specified so just set to min.
+ new_rcv_buf_sz = min * 2;
+ }
+
+ ASSERT_THAT(setsockopt(s_, SOL_SOCKET, SO_RCVBUF, &new_rcv_buf_sz,
+ sizeof(new_rcv_buf_sz)),
+ SyscallSucceeds());
+ int rcv_buf_sz = 0;
+ {
+ socklen_t rcv_buf_len = sizeof(rcv_buf_sz);
+ ASSERT_THAT(
+ getsockopt(s_, SOL_SOCKET, SO_RCVBUF, &rcv_buf_sz, &rcv_buf_len),
+ SyscallSucceeds());
+ }
+
+ // Set a receive timeout so that we don't block forever on reads if the test
+ // fails.
+ struct timeval tv {
+ .tv_sec = 1, .tv_usec = 0,
+ };
+ ASSERT_THAT(setsockopt(s_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),
+ SyscallSucceeds());
+
+ {
+ std::vector<char> buf(min);
+ RandomizeBuffer(buf.data(), buf.size());
+
+ ASSERT_NO_FATAL_FAILURE(SendBuf(buf.data(), buf.size()));
+ ASSERT_NO_FATAL_FAILURE(SendBuf(buf.data(), buf.size()));
+ ASSERT_NO_FATAL_FAILURE(SendBuf(buf.data(), buf.size()));
+ ASSERT_NO_FATAL_FAILURE(SendBuf(buf.data(), buf.size()));
+ int sent = 4;
+ if (IsRunningOnGvisor()) {
+ // Linux seems to drop the 4th packet even though technically it should
+ // fit in the receive buffer.
+ ASSERT_NO_FATAL_FAILURE(SendBuf(buf.data(), buf.size()));
+ sent++;
+ }
+
+ // Verify that the expected number of packets are available to be read.
+ for (int i = 0; i < sent - 1; i++) {
+ // Receive the packet and make sure it's identical.
+ std::vector<char> recv_buf(buf.size() + HdrLen());
+ ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf.data(), recv_buf.size()));
+ EXPECT_EQ(memcmp(recv_buf.data() + HdrLen(), buf.data(),
+ buf.size()),
+ 0);
+ }
+
+ // Assert that the last packet is dropped because the receive buffer should
+ // be full after the first four packets.
+ std::vector<char> recv_buf(buf.size() + HdrLen());
+ struct iovec iov = {};
+ iov.iov_base = static_cast<void*>(const_cast<char*>(recv_buf.data()));
+ iov.iov_len = buf.size();
+ struct msghdr msg = {};
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0;
+ ASSERT_THAT(RetryEINTR(recvmsg)(s_, &msg, MSG_DONTWAIT),
+ SyscallFailsWithErrno(EAGAIN));
+ }
+}
+
+void RawSocketTest::SendBuf(const char* buf, int buf_len) {
+ // It's safe to use const_cast here because sendmsg won't modify the iovec or
+ // address.
+ struct iovec iov = {};
+ iov.iov_base = static_cast<void*>(const_cast<char*>(buf));
+ iov.iov_len = static_cast<size_t>(buf_len);
+ struct msghdr msg = {};
+ msg.msg_name = static_cast<void*>(&addr_);
+ msg.msg_namelen = AddrLen();
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0;
+ ASSERT_THAT(sendmsg(s_, &msg, 0), SyscallSucceedsWithValue(buf_len));
+}
+
+void RawSocketTest::ReceiveBuf(char* recv_buf, size_t recv_buf_len) {
+ ASSERT_NO_FATAL_FAILURE(ReceiveBufFrom(s_, recv_buf, recv_buf_len));
+}
+
+void RawSocketTest::ReceiveBufFrom(int sock, char* recv_buf,
+ size_t recv_buf_len) {
+ ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(sock, recv_buf, recv_buf_len));
+}
+
+#ifndef __fuchsia__
+
+TEST_P(RawSocketTest, SetSocketDetachFilterNoInstalledFilter) {
+ // TODO(gvisor.dev/2746): Support SO_ATTACH_FILTER/SO_DETACH_FILTER.
+ if (IsRunningOnGvisor()) {
+ constexpr int val = 0;
+ ASSERT_THAT(setsockopt(s_, SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val)),
+ SyscallSucceeds());
+ return;
+ }
+
+ constexpr int val = 0;
+ ASSERT_THAT(setsockopt(s_, SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val)),
+ SyscallFailsWithErrno(ENOENT));
+}
+
+TEST_P(RawSocketTest, GetSocketDetachFilter) {
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_DETACH_FILTER, &val, &val_len),
+ SyscallFailsWithErrno(ENOPROTOOPT));
+}
+
+#endif // __fuchsia__
+
+// AF_INET6+SOCK_RAW+IPPROTO_RAW sockets can be created, but not written to.
+TEST(RawSocketTest, IPv6ProtoRaw) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ int sock;
+ ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW),
+ SyscallSucceeds());
+
+ // Verify that writing yields EINVAL.
+ char buf[] = "This is such a weird little edge case";
+ struct sockaddr_in6 sin6 = {};
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_addr = in6addr_loopback;
+ ASSERT_THAT(sendto(sock, buf, sizeof(buf), 0 /* flags */,
+ reinterpret_cast<struct sockaddr*>(&sin6), sizeof(sin6)),
+ SyscallFailsWithErrno(EINVAL));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ AllInetTests, RawSocketTest,
+ ::testing::Combine(::testing::Values(IPPROTO_TCP, IPPROTO_UDP),
+ ::testing::Values(AF_INET, AF_INET6)));
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/syscalls/linux/raw_socket_hdrincl.cc b/test/syscalls/linux/raw_socket_hdrincl.cc
index 0a27506aa..2f25aceb2 100644
--- a/test/syscalls/linux/raw_socket_hdrincl.cc
+++ b/test/syscalls/linux/raw_socket_hdrincl.cc
@@ -167,7 +167,7 @@ TEST_F(RawHDRINCL, NotReadable) {
// nothing to be read.
char buf[117];
ASSERT_THAT(RetryEINTR(recv)(socket_, buf, sizeof(buf), MSG_DONTWAIT),
- SyscallFailsWithErrno(EINVAL));
+ SyscallFailsWithErrno(EAGAIN));
}
// Test that we can connect() to a valid IP (loopback).
@@ -178,6 +178,9 @@ TEST_F(RawHDRINCL, ConnectToLoopback) {
}
TEST_F(RawHDRINCL, SendWithoutConnectSucceeds) {
+ // FIXME(gvisor.dev/issue/3159): Test currently flaky.
+ SKIP_IF(true);
+
struct iphdr hdr = LoopbackHeader();
ASSERT_THAT(send(socket_, &hdr, sizeof(hdr), 0),
SyscallSucceedsWithValue(sizeof(hdr)));
@@ -273,14 +276,17 @@ TEST_F(RawHDRINCL, SendAndReceive) {
// The network stack should have set the source address.
EXPECT_EQ(src.sin_family, AF_INET);
EXPECT_EQ(absl::gbswap_32(src.sin_addr.s_addr), INADDR_LOOPBACK);
- // The packet ID should be 0, as the packet is less than 68 bytes.
- struct iphdr iphdr = {};
- memcpy(&iphdr, recv_buf, sizeof(iphdr));
- EXPECT_EQ(iphdr.id, 0);
+ // The packet ID should not be 0, as the packet has DF=0.
+ struct iphdr* iphdr = reinterpret_cast<struct iphdr*>(recv_buf);
+ EXPECT_NE(iphdr->id, 0);
}
-// Send and receive a packet with nonzero IP ID.
-TEST_F(RawHDRINCL, SendAndReceiveNonzeroID) {
+// Send and receive a packet where the sendto address is not the same as the
+// provided destination.
+TEST_F(RawHDRINCL, SendAndReceiveDifferentAddress) {
+ // FIXME(gvisor.dev/issue/3160): Test currently flaky.
+ SKIP_IF(true);
+
int port = 40000;
if (!IsRunningOnGvisor()) {
port = static_cast<short>(ASSERT_NO_ERRNO_AND_VALUE(
@@ -292,19 +298,24 @@ TEST_F(RawHDRINCL, SendAndReceiveNonzeroID) {
FileDescriptor udp_sock =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_RAW, IPPROTO_UDP));
- // Construct a packet with an IP header, UDP header, and payload. Make the
- // payload large enough to force an IP ID to be assigned.
- constexpr char kPayload[128] = {};
+ // Construct a packet with an IP header, UDP header, and payload.
+ constexpr char kPayload[] = "toto";
char packet[sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kPayload)];
ASSERT_TRUE(
FillPacket(packet, sizeof(packet), port, kPayload, sizeof(kPayload)));
+ // Overwrite the IP destination address with an IP we can't get to.
+ struct iphdr iphdr = {};
+ memcpy(&iphdr, packet, sizeof(iphdr));
+ iphdr.daddr = 42;
+ memcpy(packet, &iphdr, sizeof(iphdr));
socklen_t addrlen = sizeof(addr_);
ASSERT_NO_FATAL_FAILURE(sendto(socket_, &packet, sizeof(packet), 0,
reinterpret_cast<struct sockaddr*>(&addr_),
addrlen));
- // Receive the payload.
+ // Receive the payload, since sendto should replace the bad destination with
+ // localhost.
char recv_buf[sizeof(packet)];
struct sockaddr_in src;
socklen_t src_size = sizeof(src);
@@ -318,47 +329,58 @@ TEST_F(RawHDRINCL, SendAndReceiveNonzeroID) {
// The network stack should have set the source address.
EXPECT_EQ(src.sin_family, AF_INET);
EXPECT_EQ(absl::gbswap_32(src.sin_addr.s_addr), INADDR_LOOPBACK);
- // The packet ID should not be 0, as the packet was more than 68 bytes.
- struct iphdr* iphdr = reinterpret_cast<struct iphdr*>(recv_buf);
- EXPECT_NE(iphdr->id, 0);
+ // The packet ID should not be 0, as the packet has DF=0.
+ struct iphdr recv_iphdr = {};
+ memcpy(&recv_iphdr, recv_buf, sizeof(recv_iphdr));
+ EXPECT_NE(recv_iphdr.id, 0);
+ // The destination address should be localhost, not the bad IP we set
+ // initially.
+ EXPECT_EQ(absl::gbswap_32(recv_iphdr.daddr), INADDR_LOOPBACK);
}
-// Send and receive a packet where the sendto address is not the same as the
-// provided destination.
-TEST_F(RawHDRINCL, SendAndReceiveDifferentAddress) {
+// Send and receive a packet w/ the IP_HDRINCL option set.
+TEST_F(RawHDRINCL, SendAndReceiveIPHdrIncl) {
int port = 40000;
if (!IsRunningOnGvisor()) {
port = static_cast<short>(ASSERT_NO_ERRNO_AND_VALUE(
PortAvailable(0, AddressFamily::kIpv4, SocketType::kUdp, false)));
}
- // IPPROTO_RAW sockets are write-only. We'll have to open another socket to
- // read what we write.
- FileDescriptor udp_sock =
+ FileDescriptor recv_sock =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_RAW, IPPROTO_UDP));
+
+ FileDescriptor send_sock =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_RAW, IPPROTO_UDP));
+ // Enable IP_HDRINCL option so that we can build and send w/ an IP
+ // header.
+ constexpr int kSockOptOn = 1;
+ ASSERT_THAT(setsockopt(send_sock.get(), SOL_IP, IP_HDRINCL, &kSockOptOn,
+ sizeof(kSockOptOn)),
+ SyscallSucceeds());
+ // This is not strictly required but we do it to make sure that setting
+ // IP_HDRINCL on a non IPPROTO_RAW socket does not prevent it from receiving
+ // packets.
+ ASSERT_THAT(setsockopt(recv_sock.get(), SOL_IP, IP_HDRINCL, &kSockOptOn,
+ sizeof(kSockOptOn)),
+ SyscallSucceeds());
+
// Construct a packet with an IP header, UDP header, and payload.
constexpr char kPayload[] = "toto";
char packet[sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kPayload)];
ASSERT_TRUE(
FillPacket(packet, sizeof(packet), port, kPayload, sizeof(kPayload)));
- // Overwrite the IP destination address with an IP we can't get to.
- struct iphdr iphdr = {};
- memcpy(&iphdr, packet, sizeof(iphdr));
- iphdr.daddr = 42;
- memcpy(packet, &iphdr, sizeof(iphdr));
socklen_t addrlen = sizeof(addr_);
- ASSERT_NO_FATAL_FAILURE(sendto(socket_, &packet, sizeof(packet), 0,
+ ASSERT_NO_FATAL_FAILURE(sendto(send_sock.get(), &packet, sizeof(packet), 0,
reinterpret_cast<struct sockaddr*>(&addr_),
addrlen));
- // Receive the payload, since sendto should replace the bad destination with
- // localhost.
+ // Receive the payload.
char recv_buf[sizeof(packet)];
struct sockaddr_in src;
socklen_t src_size = sizeof(src);
- ASSERT_THAT(recvfrom(udp_sock.get(), recv_buf, sizeof(recv_buf), 0,
+ ASSERT_THAT(recvfrom(recv_sock.get(), recv_buf, sizeof(recv_buf), 0,
reinterpret_cast<struct sockaddr*>(&src), &src_size),
SyscallSucceedsWithValue(sizeof(packet)));
EXPECT_EQ(
@@ -368,13 +390,20 @@ TEST_F(RawHDRINCL, SendAndReceiveDifferentAddress) {
// The network stack should have set the source address.
EXPECT_EQ(src.sin_family, AF_INET);
EXPECT_EQ(absl::gbswap_32(src.sin_addr.s_addr), INADDR_LOOPBACK);
- // The packet ID should be 0, as the packet is less than 68 bytes.
- struct iphdr recv_iphdr = {};
- memcpy(&recv_iphdr, recv_buf, sizeof(recv_iphdr));
- EXPECT_EQ(recv_iphdr.id, 0);
- // The destination address should be localhost, not the bad IP we set
- // initially.
- EXPECT_EQ(absl::gbswap_32(recv_iphdr.daddr), INADDR_LOOPBACK);
+ struct iphdr iphdr = {};
+ memcpy(&iphdr, recv_buf, sizeof(iphdr));
+ EXPECT_NE(iphdr.id, 0);
+
+ // Also verify that the packet we just sent was not delivered to the
+ // IPPROTO_RAW socket.
+ {
+ char recv_buf[sizeof(packet)];
+ struct sockaddr_in src;
+ socklen_t src_size = sizeof(src);
+ ASSERT_THAT(recvfrom(socket_, recv_buf, sizeof(recv_buf), MSG_DONTWAIT,
+ reinterpret_cast<struct sockaddr*>(&src), &src_size),
+ SyscallFailsWithErrno(EAGAIN));
+ }
}
} // namespace
diff --git a/test/syscalls/linux/raw_socket_ipv4.cc b/test/syscalls/linux/raw_socket_ipv4.cc
deleted file mode 100644
index cde2f07c9..000000000
--- a/test/syscalls/linux/raw_socket_ipv4.cc
+++ /dev/null
@@ -1,392 +0,0 @@
-// 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 <linux/capability.h>
-#include <netinet/in.h>
-#include <netinet/ip.h>
-#include <netinet/ip_icmp.h>
-#include <poll.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <algorithm>
-
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/syscalls/linux/unix_domain_socket_test_util.h"
-#include "test/util/capability_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/test_util.h"
-
-// Note: in order to run these tests, /proc/sys/net/ipv4/ping_group_range will
-// need to be configured to let the superuser create ping sockets (see icmp(7)).
-
-namespace gvisor {
-namespace testing {
-
-namespace {
-
-// Fixture for tests parameterized by protocol.
-class RawSocketTest : public ::testing::TestWithParam<int> {
- protected:
- // Creates a socket to be used in tests.
- void SetUp() override;
-
- // Closes the socket created by SetUp().
- void TearDown() override;
-
- // Sends buf via s_.
- void SendBuf(const char* buf, int buf_len);
-
- // Sends buf to the provided address via the provided socket.
- void SendBufTo(int sock, const struct sockaddr_in& addr, const char* buf,
- int buf_len);
-
- // Reads from s_ into recv_buf.
- void ReceiveBuf(char* recv_buf, size_t recv_buf_len);
-
- int Protocol() { return GetParam(); }
-
- // The socket used for both reading and writing.
- int s_;
-
- // The loopback address.
- struct sockaddr_in addr_;
-};
-
-void RawSocketTest::SetUp() {
- if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) {
- ASSERT_THAT(socket(AF_INET, SOCK_RAW, Protocol()),
- SyscallFailsWithErrno(EPERM));
- GTEST_SKIP();
- }
-
- ASSERT_THAT(s_ = socket(AF_INET, SOCK_RAW, Protocol()), SyscallSucceeds());
-
- addr_ = {};
-
- // We don't set ports because raw sockets don't have a notion of ports.
- addr_.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- addr_.sin_family = AF_INET;
-}
-
-void RawSocketTest::TearDown() {
- // TearDown will be run even if we skip the test.
- if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) {
- EXPECT_THAT(close(s_), SyscallSucceeds());
- }
-}
-
-// We should be able to create multiple raw sockets for the same protocol.
-// BasicRawSocket::Setup creates the first one, so we only have to create one
-// more here.
-TEST_P(RawSocketTest, MultipleCreation) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- int s2;
- ASSERT_THAT(s2 = socket(AF_INET, SOCK_RAW, Protocol()), SyscallSucceeds());
-
- ASSERT_THAT(close(s2), SyscallSucceeds());
-}
-
-// Test that shutting down an unconnected socket fails.
-TEST_P(RawSocketTest, FailShutdownWithoutConnect) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(shutdown(s_, SHUT_WR), SyscallFailsWithErrno(ENOTCONN));
- ASSERT_THAT(shutdown(s_, SHUT_RD), SyscallFailsWithErrno(ENOTCONN));
-}
-
-// Shutdown is a no-op for raw sockets (and datagram sockets in general).
-TEST_P(RawSocketTest, ShutdownWriteNoop) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(
- connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
- ASSERT_THAT(shutdown(s_, SHUT_WR), SyscallSucceeds());
-
- // Arbitrary.
- constexpr char kBuf[] = "noop";
- ASSERT_THAT(RetryEINTR(write)(s_, kBuf, sizeof(kBuf)),
- SyscallSucceedsWithValue(sizeof(kBuf)));
-}
-
-// Shutdown is a no-op for raw sockets (and datagram sockets in general).
-TEST_P(RawSocketTest, ShutdownReadNoop) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(
- connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
- ASSERT_THAT(shutdown(s_, SHUT_RD), SyscallSucceeds());
-
- // Arbitrary.
- constexpr char kBuf[] = "gdg";
- ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf)));
-
- constexpr size_t kReadSize = sizeof(kBuf) + sizeof(struct iphdr);
- char c[kReadSize];
- ASSERT_THAT(read(s_, &c, sizeof(c)), SyscallSucceedsWithValue(kReadSize));
-}
-
-// Test that listen() fails.
-TEST_P(RawSocketTest, FailListen) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(listen(s_, 1), SyscallFailsWithErrno(ENOTSUP));
-}
-
-// Test that accept() fails.
-TEST_P(RawSocketTest, FailAccept) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- struct sockaddr saddr;
- socklen_t addrlen;
- ASSERT_THAT(accept(s_, &saddr, &addrlen), SyscallFailsWithErrno(ENOTSUP));
-}
-
-// Test that getpeername() returns nothing before connect().
-TEST_P(RawSocketTest, FailGetPeerNameBeforeConnect) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- struct sockaddr saddr;
- socklen_t addrlen = sizeof(saddr);
- ASSERT_THAT(getpeername(s_, &saddr, &addrlen),
- SyscallFailsWithErrno(ENOTCONN));
-}
-
-// Test that getpeername() returns something after connect().
-TEST_P(RawSocketTest, GetPeerName) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(
- connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
- struct sockaddr saddr;
- socklen_t addrlen = sizeof(saddr);
- ASSERT_THAT(getpeername(s_, &saddr, &addrlen),
- SyscallFailsWithErrno(ENOTCONN));
- ASSERT_GT(addrlen, 0);
-}
-
-// Test that the socket is writable immediately.
-TEST_P(RawSocketTest, PollWritableImmediately) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- struct pollfd pfd = {};
- pfd.fd = s_;
- pfd.events = POLLOUT;
- ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 10000), SyscallSucceedsWithValue(1));
-}
-
-// Test that the socket isn't readable before receiving anything.
-TEST_P(RawSocketTest, PollNotReadableInitially) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- // Try to receive data with MSG_DONTWAIT, which returns immediately if there's
- // nothing to be read.
- char buf[117];
- ASSERT_THAT(RetryEINTR(recv)(s_, buf, sizeof(buf), MSG_DONTWAIT),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-// Test that the socket becomes readable once something is written to it.
-TEST_P(RawSocketTest, PollTriggeredOnWrite) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- // Write something so that there's data to be read.
- // Arbitrary.
- constexpr char kBuf[] = "JP5";
- ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf)));
-
- struct pollfd pfd = {};
- pfd.fd = s_;
- pfd.events = POLLIN;
- ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 10000), SyscallSucceedsWithValue(1));
-}
-
-// Test that we can connect() to a valid IP (loopback).
-TEST_P(RawSocketTest, ConnectToLoopback) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(
- connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
-}
-
-// Test that calling send() without connect() fails.
-TEST_P(RawSocketTest, SendWithoutConnectFails) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- // Arbitrary.
- constexpr char kBuf[] = "Endgame was good";
- ASSERT_THAT(send(s_, kBuf, sizeof(kBuf), 0),
- SyscallFailsWithErrno(EDESTADDRREQ));
-}
-
-// Bind to localhost.
-TEST_P(RawSocketTest, BindToLocalhost) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(
- bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
-}
-
-// Bind to a different address.
-TEST_P(RawSocketTest, BindToInvalid) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- struct sockaddr_in bind_addr = {};
- bind_addr.sin_family = AF_INET;
- bind_addr.sin_addr = {1}; // 1.0.0.0 - An address that we can't bind to.
- ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&bind_addr),
- sizeof(bind_addr)),
- SyscallFailsWithErrno(EADDRNOTAVAIL));
-}
-
-// Send and receive an packet.
-TEST_P(RawSocketTest, SendAndReceive) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- // Arbitrary.
- constexpr char kBuf[] = "TB12";
- ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf)));
-
- // Receive the packet and make sure it's identical.
- char recv_buf[sizeof(kBuf) + sizeof(struct iphdr)];
- ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf, sizeof(recv_buf)));
- EXPECT_EQ(memcmp(recv_buf + sizeof(struct iphdr), kBuf, sizeof(kBuf)), 0);
-}
-
-// We should be able to create multiple raw sockets for the same protocol and
-// receive the same packet on both.
-TEST_P(RawSocketTest, MultipleSocketReceive) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- int s2;
- ASSERT_THAT(s2 = socket(AF_INET, SOCK_RAW, Protocol()), SyscallSucceeds());
-
- // Arbitrary.
- constexpr char kBuf[] = "TB10";
- ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf)));
-
- // Receive it on socket 1.
- char recv_buf1[sizeof(kBuf) + sizeof(struct iphdr)];
- ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf1, sizeof(recv_buf1)));
-
- // Receive it on socket 2.
- char recv_buf2[sizeof(kBuf) + sizeof(struct iphdr)];
- ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(s2, recv_buf2, sizeof(recv_buf2)));
-
- EXPECT_EQ(memcmp(recv_buf1 + sizeof(struct iphdr),
- recv_buf2 + sizeof(struct iphdr), sizeof(kBuf)),
- 0);
-
- ASSERT_THAT(close(s2), SyscallSucceeds());
-}
-
-// Test that connect sends packets to the right place.
-TEST_P(RawSocketTest, SendAndReceiveViaConnect) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(
- connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
-
- // Arbitrary.
- constexpr char kBuf[] = "JH4";
- ASSERT_THAT(send(s_, kBuf, sizeof(kBuf), 0),
- SyscallSucceedsWithValue(sizeof(kBuf)));
-
- // Receive the packet and make sure it's identical.
- char recv_buf[sizeof(kBuf) + sizeof(struct iphdr)];
- ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf, sizeof(recv_buf)));
- EXPECT_EQ(memcmp(recv_buf + sizeof(struct iphdr), kBuf, sizeof(kBuf)), 0);
-}
-
-// Bind to localhost, then send and receive packets.
-TEST_P(RawSocketTest, BindSendAndReceive) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(
- bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
-
- // Arbitrary.
- constexpr char kBuf[] = "DR16";
- ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf)));
-
- // Receive the packet and make sure it's identical.
- char recv_buf[sizeof(kBuf) + sizeof(struct iphdr)];
- ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf, sizeof(recv_buf)));
- EXPECT_EQ(memcmp(recv_buf + sizeof(struct iphdr), kBuf, sizeof(kBuf)), 0);
-}
-
-// Bind and connect to localhost and send/receive packets.
-TEST_P(RawSocketTest, BindConnectSendAndReceive) {
- SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
-
- ASSERT_THAT(
- bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
- ASSERT_THAT(
- connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)),
- SyscallSucceeds());
-
- // Arbitrary.
- constexpr char kBuf[] = "DG88";
- ASSERT_NO_FATAL_FAILURE(SendBuf(kBuf, sizeof(kBuf)));
-
- // Receive the packet and make sure it's identical.
- char recv_buf[sizeof(kBuf) + sizeof(struct iphdr)];
- ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf, sizeof(recv_buf)));
- EXPECT_EQ(memcmp(recv_buf + sizeof(struct iphdr), kBuf, sizeof(kBuf)), 0);
-}
-
-void RawSocketTest::SendBuf(const char* buf, int buf_len) {
- ASSERT_NO_FATAL_FAILURE(SendBufTo(s_, addr_, buf, buf_len));
-}
-
-void RawSocketTest::SendBufTo(int sock, const struct sockaddr_in& addr,
- const char* buf, int buf_len) {
- // It's safe to use const_cast here because sendmsg won't modify the iovec or
- // address.
- struct iovec iov = {};
- iov.iov_base = static_cast<void*>(const_cast<char*>(buf));
- iov.iov_len = static_cast<size_t>(buf_len);
- struct msghdr msg = {};
- msg.msg_name = static_cast<void*>(const_cast<struct sockaddr_in*>(&addr));
- msg.msg_namelen = sizeof(addr);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
- msg.msg_control = NULL;
- msg.msg_controllen = 0;
- msg.msg_flags = 0;
- ASSERT_THAT(sendmsg(sock, &msg, 0), SyscallSucceedsWithValue(buf_len));
-}
-
-void RawSocketTest::ReceiveBuf(char* recv_buf, size_t recv_buf_len) {
- ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(s_, recv_buf, recv_buf_len));
-}
-
-INSTANTIATE_TEST_SUITE_P(AllInetTests, RawSocketTest,
- ::testing::Values(IPPROTO_TCP, IPPROTO_UDP));
-
-} // namespace
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket.cc b/test/syscalls/linux/socket.cc
index e0a4d0985..c20cd3fcc 100644
--- a/test/syscalls/linux/socket.cc
+++ b/test/syscalls/linux/socket.cc
@@ -86,37 +86,12 @@ TEST(SocketTest, UnixSocketStat) {
EXPECT_EQ(statbuf.st_mode, S_IFSOCK | sock_perm & ~mask);
// Timestamps should be equal and non-zero.
- EXPECT_NE(statbuf.st_atime, 0);
- EXPECT_EQ(statbuf.st_atime, statbuf.st_mtime);
- EXPECT_EQ(statbuf.st_atime, statbuf.st_ctime);
-}
-
-TEST(SocketTest, UnixConnectNeedsWritePerm) {
- SKIP_IF(IsRunningWithVFS1());
-
- FileDescriptor bound =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, SOCK_STREAM, PF_UNIX));
-
- struct sockaddr_un addr =
- ASSERT_NO_ERRNO_AND_VALUE(UniqueUnixAddr(/*abstract=*/false, AF_UNIX));
- ASSERT_THAT(bind(bound.get(), reinterpret_cast<struct sockaddr*>(&addr),
- sizeof(addr)),
- SyscallSucceeds());
- ASSERT_THAT(listen(bound.get(), 1), SyscallSucceeds());
-
- // Connect should fail without write perms.
- ASSERT_THAT(chmod(addr.sun_path, 0500), SyscallSucceeds());
- FileDescriptor client =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, SOCK_STREAM, PF_UNIX));
- EXPECT_THAT(connect(client.get(), reinterpret_cast<struct sockaddr*>(&addr),
- sizeof(addr)),
- SyscallFailsWithErrno(EACCES));
-
- // Connect should succeed with write perms.
- ASSERT_THAT(chmod(addr.sun_path, 0200), SyscallSucceeds());
- EXPECT_THAT(connect(client.get(), reinterpret_cast<struct sockaddr*>(&addr),
- sizeof(addr)),
- SyscallSucceeds());
+ // TODO(b/158882152): Sockets currently don't implement timestamps.
+ if (!IsRunningOnGvisor()) {
+ EXPECT_NE(statbuf.st_atime, 0);
+ EXPECT_EQ(statbuf.st_atime, statbuf.st_mtime);
+ EXPECT_EQ(statbuf.st_atime, statbuf.st_ctime);
+ }
}
using SocketOpenTest = ::testing::TestWithParam<int>;
diff --git a/test/syscalls/linux/socket_bind_to_device_sequence.cc b/test/syscalls/linux/socket_bind_to_device_sequence.cc
index 1967329ee..d3cc71dbf 100644
--- a/test/syscalls/linux/socket_bind_to_device_sequence.cc
+++ b/test/syscalls/linux/socket_bind_to_device_sequence.cc
@@ -180,7 +180,7 @@ class BindToDeviceSequenceTest : public ::testing::TestWithParam<SocketKind> {
private:
SocketKind socket_factory_;
// devices maps from the device id in the test case to the name of the device.
- std::unordered_map<int, string> devices_;
+ absl::node_hash_map<int, string> devices_;
// These are the tunnels that were created for the test and will be destroyed
// by the destructor.
vector<std::unique_ptr<Tunnel>> tunnels_;
@@ -363,9 +363,6 @@ TEST_P(BindToDeviceSequenceTest, BindTwiceWithReuseOnce) {
}
TEST_P(BindToDeviceSequenceTest, BindWithReuseAddr) {
- // FIXME(b/129164367): Support SO_REUSEADDR on UDP sockets.
- SKIP_IF(IsRunningOnGvisor());
-
ASSERT_NO_FATAL_FAILURE(
BindSocket(/* reusePort */ false, /* reuse_addr */ true));
ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ false,
@@ -405,9 +402,6 @@ TEST_P(BindToDeviceSequenceTest, BindReuseAddrReusePortThenReusePort) {
}
TEST_P(BindToDeviceSequenceTest, BindReuseAddrReusePortThenReuseAddr) {
- // FIXME(b/129164367): Support SO_REUSEADDR on UDP sockets.
- SKIP_IF(IsRunningOnGvisor());
-
ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true,
/* reuse_addr */ true,
/* bind_to_device */ 0));
@@ -434,9 +428,6 @@ TEST_P(BindToDeviceSequenceTest, BindDoubleReuseAddrReusePortThenReusePort) {
}
TEST_P(BindToDeviceSequenceTest, BindDoubleReuseAddrReusePortThenReuseAddr) {
- // FIXME(b/129164367): Support SO_REUSEADDR on UDP sockets.
- SKIP_IF(IsRunningOnGvisor());
-
ASSERT_NO_FATAL_FAILURE(BindSocket(
/* reuse_port */ true, /* reuse_addr */ true, /* bind_to_device */ 0));
ASSERT_NO_FATAL_FAILURE(BindSocket(/* reuse_port */ true,
@@ -469,10 +460,20 @@ TEST_P(BindToDeviceSequenceTest, BindReuseAddrThenReuseAddr) {
/* bind_to_device */ 0, EADDRINUSE));
}
-// This behavior seems like a bug?
TEST_P(BindToDeviceSequenceTest,
BindReuseAddrThenReuseAddrReusePortThenReuseAddr) {
- // FIXME(b/129164367): Support SO_REUSEADDR on UDP sockets.
+ // The behavior described in this test seems like a Linux bug. It doesn't
+ // make any sense and it is unlikely that any applications rely on it.
+ //
+ // Both SO_REUSEADDR and SO_REUSEPORT allow binding multiple UDP sockets to
+ // the same address and deliver each packet to exactly one of the bound
+ // sockets. If both are enabled, one of the strategies is selected to route
+ // packets. The strategy is selected dynamically based on the settings of the
+ // currently bound sockets. Usually, the strategy is selected based on the
+ // common setting (SO_REUSEADDR or SO_REUSEPORT) amongst the sockets, but for
+ // some reason, Linux allows binding sets of sockets with no overlapping
+ // settings in some situations. In this case, it is not obvious which strategy
+ // would be selected as the configured setting is a contradiction.
SKIP_IF(IsRunningOnGvisor());
ASSERT_NO_FATAL_FAILURE(BindSocket(
diff --git a/test/syscalls/linux/socket_capability.cc b/test/syscalls/linux/socket_capability.cc
new file mode 100644
index 000000000..84b5b2b21
--- /dev/null
+++ b/test/syscalls/linux/socket_capability.cc
@@ -0,0 +1,61 @@
+// Copyright 2018 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.
+
+// Subset of socket tests that need Linux-specific headers (compared to POSIX
+// headers).
+
+#include "gtest/gtest.h"
+#include "test/syscalls/linux/socket_test_util.h"
+#include "test/util/capability_util.h"
+#include "test/util/file_descriptor.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+TEST(SocketTest, UnixConnectNeedsWritePerm) {
+ SKIP_IF(IsRunningWithVFS1());
+
+ FileDescriptor bound =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, SOCK_STREAM, PF_UNIX));
+
+ struct sockaddr_un addr =
+ ASSERT_NO_ERRNO_AND_VALUE(UniqueUnixAddr(/*abstract=*/false, AF_UNIX));
+ ASSERT_THAT(bind(bound.get(), reinterpret_cast<struct sockaddr*>(&addr),
+ sizeof(addr)),
+ SyscallSucceeds());
+ ASSERT_THAT(listen(bound.get(), 1), SyscallSucceeds());
+
+ // Drop capabilites that allow us to override permision checks. Otherwise if
+ // the test is run as root, the connect below will bypass permission checks
+ // and succeed unexpectedly.
+ ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
+
+ // Connect should fail without write perms.
+ ASSERT_THAT(chmod(addr.sun_path, 0500), SyscallSucceeds());
+ FileDescriptor client =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, SOCK_STREAM, PF_UNIX));
+ ASSERT_THAT(connect(client.get(), reinterpret_cast<struct sockaddr*>(&addr),
+ sizeof(addr)),
+ SyscallFailsWithErrno(EACCES));
+
+ // Connect should succeed with write perms.
+ ASSERT_THAT(chmod(addr.sun_path, 0200), SyscallSucceeds());
+ EXPECT_THAT(connect(client.get(), reinterpret_cast<struct sockaddr*>(&addr),
+ sizeof(addr)),
+ SyscallSucceeds());
+}
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/syscalls/linux/socket_inet_loopback.cc b/test/syscalls/linux/socket_inet_loopback.cc
index fa890ec98..18b9e4b70 100644
--- a/test/syscalls/linux/socket_inet_loopback.cc
+++ b/test/syscalls/linux/socket_inet_loopback.cc
@@ -17,6 +17,7 @@
#include <netinet/tcp.h>
#include <poll.h>
#include <string.h>
+#include <sys/socket.h>
#include <atomic>
#include <iostream>
@@ -686,24 +687,9 @@ TEST_P(SocketInetLoopbackTest, TCPFinWait2Test_NoRandomSave) {
// be restarted causing the final bind/connect to fail.
DisableSave ds;
- // TODO(gvisor.dev/issue/1030): Portmanager does not track all 5 tuple
- // reservations which causes the bind() to succeed on gVisor but connect
- // correctly fails.
- if (IsRunningOnGvisor()) {
- ASSERT_THAT(
- bind(conn_fd2.get(), reinterpret_cast<sockaddr*>(&conn_bound_addr),
- conn_addrlen),
- SyscallSucceeds());
- ASSERT_THAT(RetryEINTR(connect)(conn_fd2.get(),
- reinterpret_cast<sockaddr*>(&conn_addr),
- conn_addrlen),
- SyscallFailsWithErrno(EADDRINUSE));
- } else {
- ASSERT_THAT(
- bind(conn_fd2.get(), reinterpret_cast<sockaddr*>(&conn_bound_addr),
- conn_addrlen),
- SyscallFailsWithErrno(EADDRINUSE));
- }
+ ASSERT_THAT(bind(conn_fd2.get(),
+ reinterpret_cast<sockaddr*>(&conn_bound_addr), conn_addrlen),
+ SyscallFailsWithErrno(EADDRINUSE));
// Sleep for a little over the linger timeout to reduce flakiness in
// save/restore tests.
@@ -1737,6 +1723,171 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, DualStackV6AnyReservesEverything) {
ASSERT_THAT(bind(fd_v4.get(), reinterpret_cast<sockaddr*>(&addr_v4),
test_addr_v4.addr_len),
SyscallFailsWithErrno(EADDRINUSE));
+
+ // Verify that binding the v4 any on the same port with a v4 socket
+ // fails.
+ TestAddress const& test_addr_v4_any = V4Any();
+ sockaddr_storage addr_v4_any = test_addr_v4_any.addr;
+ ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4_any.family(), &addr_v4_any, port));
+ const FileDescriptor fd_v4_any = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(test_addr_v4_any.family(), param.type, 0));
+ ASSERT_THAT(bind(fd_v4_any.get(), reinterpret_cast<sockaddr*>(&addr_v4_any),
+ test_addr_v4_any.addr_len),
+ SyscallFailsWithErrno(EADDRINUSE));
+}
+
+TEST_P(SocketMultiProtocolInetLoopbackTest,
+ DualStackV6AnyReuseAddrDoesNotReserveV4Any) {
+ auto const& param = GetParam();
+
+ // Bind the v6 any on a dual stack socket.
+ TestAddress const& test_addr_dual = V6Any();
+ sockaddr_storage addr_dual = test_addr_dual.addr;
+ const FileDescriptor fd_dual =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_dual.family(), param.type, 0));
+ ASSERT_THAT(setsockopt(fd_dual.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
+ sizeof(kSockOptOn)),
+ SyscallSucceeds());
+ ASSERT_THAT(bind(fd_dual.get(), reinterpret_cast<sockaddr*>(&addr_dual),
+ test_addr_dual.addr_len),
+ SyscallSucceeds());
+
+ // Get the port that we bound.
+ socklen_t addrlen = test_addr_dual.addr_len;
+ ASSERT_THAT(getsockname(fd_dual.get(),
+ reinterpret_cast<sockaddr*>(&addr_dual), &addrlen),
+ SyscallSucceeds());
+ uint16_t const port =
+ ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr_dual.family(), addr_dual));
+
+ // Verify that binding the v4 any on the same port with a v4 socket succeeds.
+ TestAddress const& test_addr_v4_any = V4Any();
+ sockaddr_storage addr_v4_any = test_addr_v4_any.addr;
+ ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4_any.family(), &addr_v4_any, port));
+ const FileDescriptor fd_v4_any = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(test_addr_v4_any.family(), param.type, 0));
+ ASSERT_THAT(setsockopt(fd_v4_any.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
+ sizeof(kSockOptOn)),
+ SyscallSucceeds());
+ ASSERT_THAT(bind(fd_v4_any.get(), reinterpret_cast<sockaddr*>(&addr_v4_any),
+ test_addr_v4_any.addr_len),
+ SyscallSucceeds());
+}
+
+TEST_P(SocketMultiProtocolInetLoopbackTest,
+ DualStackV6AnyReuseAddrListenReservesV4Any) {
+ auto const& param = GetParam();
+
+ // Only TCP sockets are supported.
+ SKIP_IF((param.type & SOCK_STREAM) == 0);
+
+ // Bind the v6 any on a dual stack socket.
+ TestAddress const& test_addr_dual = V6Any();
+ sockaddr_storage addr_dual = test_addr_dual.addr;
+ const FileDescriptor fd_dual =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_dual.family(), param.type, 0));
+ ASSERT_THAT(setsockopt(fd_dual.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
+ sizeof(kSockOptOn)),
+ SyscallSucceeds());
+ ASSERT_THAT(bind(fd_dual.get(), reinterpret_cast<sockaddr*>(&addr_dual),
+ test_addr_dual.addr_len),
+ SyscallSucceeds());
+
+ ASSERT_THAT(listen(fd_dual.get(), 5), SyscallSucceeds());
+
+ // Get the port that we bound.
+ socklen_t addrlen = test_addr_dual.addr_len;
+ ASSERT_THAT(getsockname(fd_dual.get(),
+ reinterpret_cast<sockaddr*>(&addr_dual), &addrlen),
+ SyscallSucceeds());
+ uint16_t const port =
+ ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr_dual.family(), addr_dual));
+
+ // Verify that binding the v4 any on the same port with a v4 socket succeeds.
+ TestAddress const& test_addr_v4_any = V4Any();
+ sockaddr_storage addr_v4_any = test_addr_v4_any.addr;
+ ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4_any.family(), &addr_v4_any, port));
+ const FileDescriptor fd_v4_any = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(test_addr_v4_any.family(), param.type, 0));
+ ASSERT_THAT(setsockopt(fd_v4_any.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
+ sizeof(kSockOptOn)),
+ SyscallSucceeds());
+
+ ASSERT_THAT(bind(fd_v4_any.get(), reinterpret_cast<sockaddr*>(&addr_v4_any),
+ test_addr_v4_any.addr_len),
+ SyscallFailsWithErrno(EADDRINUSE));
+}
+
+TEST_P(SocketMultiProtocolInetLoopbackTest,
+ DualStackV6AnyWithListenReservesEverything) {
+ auto const& param = GetParam();
+
+ // Only TCP sockets are supported.
+ SKIP_IF((param.type & SOCK_STREAM) == 0);
+
+ // Bind the v6 any on a dual stack socket.
+ TestAddress const& test_addr_dual = V6Any();
+ sockaddr_storage addr_dual = test_addr_dual.addr;
+ const FileDescriptor fd_dual =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_dual.family(), param.type, 0));
+ ASSERT_THAT(bind(fd_dual.get(), reinterpret_cast<sockaddr*>(&addr_dual),
+ test_addr_dual.addr_len),
+ SyscallSucceeds());
+
+ ASSERT_THAT(listen(fd_dual.get(), 5), SyscallSucceeds());
+
+ // Get the port that we bound.
+ socklen_t addrlen = test_addr_dual.addr_len;
+ ASSERT_THAT(getsockname(fd_dual.get(),
+ reinterpret_cast<sockaddr*>(&addr_dual), &addrlen),
+ SyscallSucceeds());
+ uint16_t const port =
+ ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr_dual.family(), addr_dual));
+
+ // Verify that binding the v6 loopback with the same port fails.
+ TestAddress const& test_addr_v6 = V6Loopback();
+ sockaddr_storage addr_v6 = test_addr_v6.addr;
+ ASSERT_NO_ERRNO(SetAddrPort(test_addr_v6.family(), &addr_v6, port));
+ const FileDescriptor fd_v6 =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v6.family(), param.type, 0));
+ ASSERT_THAT(bind(fd_v6.get(), reinterpret_cast<sockaddr*>(&addr_v6),
+ test_addr_v6.addr_len),
+ SyscallFailsWithErrno(EADDRINUSE));
+
+ // Verify that binding the v4 loopback on the same port with a v6 socket
+ // fails.
+ TestAddress const& test_addr_v4_mapped = V4MappedLoopback();
+ sockaddr_storage addr_v4_mapped = test_addr_v4_mapped.addr;
+ ASSERT_NO_ERRNO(
+ SetAddrPort(test_addr_v4_mapped.family(), &addr_v4_mapped, port));
+ const FileDescriptor fd_v4_mapped = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(test_addr_v4_mapped.family(), param.type, 0));
+ ASSERT_THAT(
+ bind(fd_v4_mapped.get(), reinterpret_cast<sockaddr*>(&addr_v4_mapped),
+ test_addr_v4_mapped.addr_len),
+ SyscallFailsWithErrno(EADDRINUSE));
+
+ // Verify that binding the v4 loopback on the same port with a v4 socket
+ // fails.
+ TestAddress const& test_addr_v4 = V4Loopback();
+ sockaddr_storage addr_v4 = test_addr_v4.addr;
+ ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4.family(), &addr_v4, port));
+ const FileDescriptor fd_v4 =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr_v4.family(), param.type, 0));
+ ASSERT_THAT(bind(fd_v4.get(), reinterpret_cast<sockaddr*>(&addr_v4),
+ test_addr_v4.addr_len),
+ SyscallFailsWithErrno(EADDRINUSE));
+
+ // Verify that binding the v4 any on the same port with a v4 socket
+ // fails.
+ TestAddress const& test_addr_v4_any = V4Any();
+ sockaddr_storage addr_v4_any = test_addr_v4_any.addr;
+ ASSERT_NO_ERRNO(SetAddrPort(test_addr_v4_any.family(), &addr_v4_any, port));
+ const FileDescriptor fd_v4_any = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(test_addr_v4_any.family(), param.type, 0));
+ ASSERT_THAT(bind(fd_v4_any.get(), reinterpret_cast<sockaddr*>(&addr_v4_any),
+ test_addr_v4_any.addr_len),
+ SyscallFailsWithErrno(EADDRINUSE));
}
TEST_P(SocketMultiProtocolInetLoopbackTest, V6OnlyV6AnyReservesV6) {
@@ -1798,10 +1949,6 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, V6OnlyV6AnyReservesV6) {
TEST_P(SocketMultiProtocolInetLoopbackTest, V6EphemeralPortReserved) {
auto const& param = GetParam();
- // FIXME(b/76031995): Support disabling SO_REUSEADDR for TCP sockets and make
- // it disabled by default.
- SKIP_IF(IsRunningOnGvisor() && param.type == SOCK_STREAM);
-
for (int i = 0; true; i++) {
// Bind the v6 loopback on a dual stack socket.
TestAddress const& test_addr = V6Loopback();
@@ -1864,17 +2011,6 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, V6EphemeralPortReserved) {
test_addr_v6.addr_len),
SyscallFailsWithErrno(EADDRINUSE));
- // Verify that binding the v4 any with the same port fails.
- TestAddress const& test_addr_v4_any = V4Any();
- sockaddr_storage addr_v4_any = test_addr_v4_any.addr;
- ASSERT_NO_ERRNO(
- SetAddrPort(test_addr_v4_any.family(), &addr_v4_any, ephemeral_port));
- const FileDescriptor fd_v4_any = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(test_addr_v4_any.family(), param.type, 0));
- ASSERT_THAT(bind(fd_v4_any.get(), reinterpret_cast<sockaddr*>(&addr_v4_any),
- test_addr_v4_any.addr_len),
- SyscallFailsWithErrno(EADDRINUSE));
-
// Verify that we can still bind the v4 loopback on the same port.
TestAddress const& test_addr_v4_mapped = V4MappedLoopback();
sockaddr_storage addr_v4_mapped = test_addr_v4_mapped.addr;
@@ -1900,9 +2036,6 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, V6EphemeralPortReserved) {
TEST_P(SocketMultiProtocolInetLoopbackTest, V6EphemeralPortReservedReuseAddr) {
auto const& param = GetParam();
- // FIXME(b/129164367): Support SO_REUSEADDR on UDP sockets.
- SKIP_IF(IsRunningOnGvisor() && param.type == SOCK_DGRAM);
-
// Bind the v6 loopback on a dual stack socket.
TestAddress const& test_addr = V6Loopback();
sockaddr_storage bound_addr = test_addr.addr;
@@ -1966,10 +2099,6 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, V6EphemeralPortReservedReuseAddr) {
TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedEphemeralPortReserved) {
auto const& param = GetParam();
- // FIXME(b/76031995): Support disabling SO_REUSEADDR for TCP sockets and make
- // it disabled by default.
- SKIP_IF(IsRunningOnGvisor() && param.type == SOCK_STREAM);
-
for (int i = 0; true; i++) {
// Bind the v4 loopback on a dual stack socket.
TestAddress const& test_addr = V4MappedLoopback();
@@ -2091,9 +2220,6 @@ TEST_P(SocketMultiProtocolInetLoopbackTest,
V4MappedEphemeralPortReservedResueAddr) {
auto const& param = GetParam();
- // FIXME(b/129164367): Support SO_REUSEADDR on UDP sockets.
- SKIP_IF(IsRunningOnGvisor() && param.type == SOCK_DGRAM);
-
// Bind the v4 loopback on a dual stack socket.
TestAddress const& test_addr = V4MappedLoopback();
sockaddr_storage bound_addr = test_addr.addr;
@@ -2158,10 +2284,6 @@ TEST_P(SocketMultiProtocolInetLoopbackTest,
TEST_P(SocketMultiProtocolInetLoopbackTest, V4EphemeralPortReserved) {
auto const& param = GetParam();
- // FIXME(b/76031995): Support disabling SO_REUSEADDR for TCP sockets and make
- // it disabled by default.
- SKIP_IF(IsRunningOnGvisor() && param.type == SOCK_STREAM);
-
for (int i = 0; true; i++) {
// Bind the v4 loopback on a v4 socket.
TestAddress const& test_addr = V4Loopback();
@@ -2283,9 +2405,6 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, V4EphemeralPortReserved) {
TEST_P(SocketMultiProtocolInetLoopbackTest, V4EphemeralPortReservedReuseAddr) {
auto const& param = GetParam();
- // FIXME(b/129164367): Support SO_REUSEADDR on UDP sockets.
- SKIP_IF(IsRunningOnGvisor() && param.type == SOCK_DGRAM);
-
// Bind the v4 loopback on a v4 socket.
TestAddress const& test_addr = V4Loopback();
sockaddr_storage bound_addr = test_addr.addr;
diff --git a/test/syscalls/linux/socket_inet_loopback_nogotsan.cc b/test/syscalls/linux/socket_inet_loopback_nogotsan.cc
new file mode 100644
index 000000000..791e2bd51
--- /dev/null
+++ b/test/syscalls/linux/socket_inet_loopback_nogotsan.cc
@@ -0,0 +1,174 @@
+// Copyright 2018 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 <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <string.h>
+
+#include <iostream>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/strings/str_cat.h"
+#include "test/syscalls/linux/ip_socket_test_util.h"
+#include "test/syscalls/linux/socket_test_util.h"
+#include "test/util/file_descriptor.h"
+#include "test/util/posix_error.h"
+#include "test/util/save_util.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+using ::testing::Gt;
+
+PosixErrorOr<uint16_t> AddrPort(int family, sockaddr_storage const& addr) {
+ switch (family) {
+ case AF_INET:
+ return static_cast<uint16_t>(
+ reinterpret_cast<sockaddr_in const*>(&addr)->sin_port);
+ case AF_INET6:
+ return static_cast<uint16_t>(
+ reinterpret_cast<sockaddr_in6 const*>(&addr)->sin6_port);
+ default:
+ return PosixError(EINVAL,
+ absl::StrCat("unknown socket family: ", family));
+ }
+}
+
+PosixError SetAddrPort(int family, sockaddr_storage* addr, uint16_t port) {
+ switch (family) {
+ case AF_INET:
+ reinterpret_cast<sockaddr_in*>(addr)->sin_port = port;
+ return NoError();
+ case AF_INET6:
+ reinterpret_cast<sockaddr_in6*>(addr)->sin6_port = port;
+ return NoError();
+ default:
+ return PosixError(EINVAL,
+ absl::StrCat("unknown socket family: ", family));
+ }
+}
+
+struct TestParam {
+ TestAddress listener;
+ TestAddress connector;
+};
+
+std::string DescribeTestParam(::testing::TestParamInfo<TestParam> const& info) {
+ return absl::StrCat("Listen", info.param.listener.description, "_Connect",
+ info.param.connector.description);
+}
+
+using SocketInetLoopbackTest = ::testing::TestWithParam<TestParam>;
+
+// This test verifies that connect returns EADDRNOTAVAIL if all local ephemeral
+// ports are already in use for a given destination ip/port.
+//
+// We disable S/R because this test creates a large number of sockets.
+//
+// FIXME(b/162475855): This test is failing reliably.
+TEST_P(SocketInetLoopbackTest, DISABLED_TestTCPPortExhaustion_NoRandomSave) {
+ auto const& param = GetParam();
+ TestAddress const& listener = param.listener;
+ TestAddress const& connector = param.connector;
+
+ constexpr int kBacklog = 10;
+ constexpr int kClients = 65536;
+
+ // Create the listening socket.
+ auto listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP));
+ sockaddr_storage listen_addr = listener.addr;
+ ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr),
+ listener.addr_len),
+ SyscallSucceeds());
+ ASSERT_THAT(listen(listen_fd.get(), kBacklog), SyscallSucceeds());
+
+ // Get the port bound by the listening socket.
+ socklen_t addrlen = listener.addr_len;
+ ASSERT_THAT(getsockname(listen_fd.get(),
+ reinterpret_cast<sockaddr*>(&listen_addr), &addrlen),
+ SyscallSucceeds());
+ uint16_t const port =
+ ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
+
+ // Disable cooperative S/R as we are making too many syscalls.
+ DisableSave ds;
+
+ // Now we keep opening connections till we run out of local ephemeral ports.
+ // and assert the error we get back.
+ sockaddr_storage conn_addr = connector.addr;
+ ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
+ std::vector<FileDescriptor> clients;
+ std::vector<FileDescriptor> servers;
+
+ for (int i = 0; i < kClients; i++) {
+ FileDescriptor client = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
+ int ret = connect(client.get(), reinterpret_cast<sockaddr*>(&conn_addr),
+ connector.addr_len);
+ if (ret == 0) {
+ clients.push_back(std::move(client));
+ FileDescriptor server =
+ ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr));
+ servers.push_back(std::move(server));
+ continue;
+ }
+ ASSERT_THAT(ret, SyscallFailsWithErrno(EADDRNOTAVAIL));
+ break;
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ All, SocketInetLoopbackTest,
+ ::testing::Values(
+ // Listeners bound to IPv4 addresses refuse connections using IPv6
+ // addresses.
+ TestParam{V4Any(), V4Any()}, TestParam{V4Any(), V4Loopback()},
+ TestParam{V4Any(), V4MappedAny()},
+ TestParam{V4Any(), V4MappedLoopback()},
+ TestParam{V4Loopback(), V4Any()}, TestParam{V4Loopback(), V4Loopback()},
+ TestParam{V4Loopback(), V4MappedLoopback()},
+ TestParam{V4MappedAny(), V4Any()},
+ TestParam{V4MappedAny(), V4Loopback()},
+ TestParam{V4MappedAny(), V4MappedAny()},
+ TestParam{V4MappedAny(), V4MappedLoopback()},
+ TestParam{V4MappedLoopback(), V4Any()},
+ TestParam{V4MappedLoopback(), V4Loopback()},
+ TestParam{V4MappedLoopback(), V4MappedLoopback()},
+
+ // Listeners bound to IN6ADDR_ANY accept all connections.
+ TestParam{V6Any(), V4Any()}, TestParam{V6Any(), V4Loopback()},
+ TestParam{V6Any(), V4MappedAny()},
+ TestParam{V6Any(), V4MappedLoopback()}, TestParam{V6Any(), V6Any()},
+ TestParam{V6Any(), V6Loopback()},
+
+ // Listeners bound to IN6ADDR_LOOPBACK refuse connections using IPv4
+ // addresses.
+ TestParam{V6Loopback(), V6Any()},
+ TestParam{V6Loopback(), V6Loopback()}),
+ DescribeTestParam);
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ip_tcp_generic.cc b/test/syscalls/linux/socket_ip_tcp_generic.cc
index fa81845fd..c2ecb639f 100644
--- a/test/syscalls/linux/socket_ip_tcp_generic.cc
+++ b/test/syscalls/linux/socket_ip_tcp_generic.cc
@@ -524,6 +524,7 @@ TEST_P(TCPSocketPairTest, SetTCPKeepintvlZero) {
// Copied from include/net/tcp.h.
constexpr int MAX_TCP_KEEPIDLE = 32767;
constexpr int MAX_TCP_KEEPINTVL = 32767;
+constexpr int MAX_TCP_KEEPCNT = 127;
TEST_P(TCPSocketPairTest, SetTCPKeepidleAboveMax) {
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
@@ -575,6 +576,78 @@ TEST_P(TCPSocketPairTest, SetTCPKeepintvlToMax) {
EXPECT_EQ(get, MAX_TCP_KEEPINTVL);
}
+TEST_P(TCPSocketPairTest, TCPKeepcountDefault) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+
+ int get = -1;
+ socklen_t get_len = sizeof(get);
+ EXPECT_THAT(
+ getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPCNT, &get, &get_len),
+ SyscallSucceedsWithValue(0));
+ EXPECT_EQ(get_len, sizeof(get));
+ EXPECT_EQ(get, 9); // 9 keepalive probes.
+}
+
+TEST_P(TCPSocketPairTest, SetTCPKeepcountZero) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+
+ constexpr int kZero = 0;
+ EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPCNT, &kZero,
+ sizeof(kZero)),
+ SyscallFailsWithErrno(EINVAL));
+}
+
+TEST_P(TCPSocketPairTest, SetTCPKeepcountAboveMax) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+
+ constexpr int kAboveMax = MAX_TCP_KEEPCNT + 1;
+ EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPCNT,
+ &kAboveMax, sizeof(kAboveMax)),
+ SyscallFailsWithErrno(EINVAL));
+}
+
+TEST_P(TCPSocketPairTest, SetTCPKeepcountToMax) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+
+ EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPCNT,
+ &MAX_TCP_KEEPCNT, sizeof(MAX_TCP_KEEPCNT)),
+ SyscallSucceedsWithValue(0));
+
+ int get = -1;
+ socklen_t get_len = sizeof(get);
+ EXPECT_THAT(
+ getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPCNT, &get, &get_len),
+ SyscallSucceedsWithValue(0));
+ EXPECT_EQ(get_len, sizeof(get));
+ EXPECT_EQ(get, MAX_TCP_KEEPCNT);
+}
+
+TEST_P(TCPSocketPairTest, SetTCPKeepcountToOne) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+
+ int keepaliveCount = 1;
+ EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPCNT,
+ &keepaliveCount, sizeof(keepaliveCount)),
+ SyscallSucceedsWithValue(0));
+
+ int get = -1;
+ socklen_t get_len = sizeof(get);
+ EXPECT_THAT(
+ getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPCNT, &get, &get_len),
+ SyscallSucceedsWithValue(0));
+ EXPECT_EQ(get_len, sizeof(get));
+ EXPECT_EQ(get, keepaliveCount);
+}
+
+TEST_P(TCPSocketPairTest, SetTCPKeepcountToNegative) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+
+ int keepaliveCount = -5;
+ EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_KEEPCNT,
+ &keepaliveCount, sizeof(keepaliveCount)),
+ SyscallFailsWithErrno(EINVAL));
+}
+
TEST_P(TCPSocketPairTest, SetOOBInline) {
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
@@ -921,6 +994,30 @@ TEST_P(TCPSocketPairTest, SetTCPWindowClampBelowMinRcvBufConnectedSocket) {
}
}
+TEST_P(TCPSocketPairTest, IpMulticastTtlDefault) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+
+ int get = -1;
+ socklen_t get_len = sizeof(get);
+ EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_TTL,
+ &get, &get_len),
+ SyscallSucceedsWithValue(0));
+ EXPECT_EQ(get_len, sizeof(get));
+ EXPECT_GT(get, 0);
+}
+
+TEST_P(TCPSocketPairTest, IpMulticastLoopDefault) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+
+ int get = -1;
+ socklen_t get_len = sizeof(get);
+ EXPECT_THAT(getsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_LOOP,
+ &get, &get_len),
+ SyscallSucceedsWithValue(0));
+ EXPECT_EQ(get_len, sizeof(get));
+ EXPECT_EQ(get, 1);
+}
+
TEST_P(TCPSocketPairTest, TCPResetDuringClose_NoRandomSave) {
DisableSave ds; // Too many syscalls.
constexpr int kThreadCount = 1000;
diff --git a/test/syscalls/linux/socket_ip_udp_generic.cc b/test/syscalls/linux/socket_ip_udp_generic.cc
index 1c533fdf2..edb86aded 100644
--- a/test/syscalls/linux/socket_ip_udp_generic.cc
+++ b/test/syscalls/linux/socket_ip_udp_generic.cc
@@ -225,9 +225,6 @@ TEST_P(UDPSocketPairTest, ReuseAddrDefault) {
TEST_P(UDPSocketPairTest, SetReuseAddr) {
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- // FIXME(b/129164367): Support SO_REUSEADDR on UDP sockets.
- SKIP_IF(IsRunningOnGvisor());
-
ASSERT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_REUSEADDR,
&kSockOptOn, sizeof(kSockOptOn)),
SyscallSucceeds());
@@ -292,9 +289,6 @@ TEST_P(UDPSocketPairTest, SetReusePort) {
TEST_P(UDPSocketPairTest, SetReuseAddrReusePort) {
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- // FIXME(b/129164367): Support SO_REUSEADDR on UDP sockets.
- SKIP_IF(IsRunningOnGvisor());
-
ASSERT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_REUSEADDR,
&kSockOptOn, sizeof(kSockOptOn)),
SyscallSucceeds());
diff --git a/test/syscalls/linux/socket_ip_unbound.cc b/test/syscalls/linux/socket_ip_unbound.cc
index ca597e267..1c7b0cf90 100644
--- a/test/syscalls/linux/socket_ip_unbound.cc
+++ b/test/syscalls/linux/socket_ip_unbound.cc
@@ -40,7 +40,7 @@ TEST_P(IPUnboundSocketTest, TtlDefault) {
socklen_t get_sz = sizeof(get);
EXPECT_THAT(getsockopt(socket->get(), IPPROTO_IP, IP_TTL, &get, &get_sz),
SyscallSucceedsWithValue(0));
- EXPECT_EQ(get, 64);
+ EXPECT_TRUE(get == 64 || get == 127);
EXPECT_EQ(get_sz, sizeof(get));
}
@@ -157,7 +157,7 @@ TEST_P(IPUnboundSocketTest, TOSDefault) {
int get = -1;
socklen_t get_sz = sizeof(get);
constexpr int kDefaultTOS = 0;
- EXPECT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz),
+ ASSERT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz),
SyscallSucceedsWithValue(0));
EXPECT_EQ(get_sz, sizeof(get));
EXPECT_EQ(get, kDefaultTOS);
@@ -173,7 +173,7 @@ TEST_P(IPUnboundSocketTest, SetTOS) {
int get = -1;
socklen_t get_sz = sizeof(get);
- EXPECT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz),
+ ASSERT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz),
SyscallSucceedsWithValue(0));
EXPECT_EQ(get_sz, sizeof(get));
EXPECT_EQ(get, set);
@@ -188,7 +188,7 @@ TEST_P(IPUnboundSocketTest, ZeroTOS) {
SyscallSucceedsWithValue(0));
int get = -1;
socklen_t get_sz = sizeof(get);
- EXPECT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz),
+ ASSERT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz),
SyscallSucceedsWithValue(0));
EXPECT_EQ(get_sz, sizeof(get));
EXPECT_EQ(get, set);
@@ -210,7 +210,7 @@ TEST_P(IPUnboundSocketTest, InvalidLargeTOS) {
}
int get = -1;
socklen_t get_sz = sizeof(get);
- EXPECT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz),
+ ASSERT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz),
SyscallSucceedsWithValue(0));
EXPECT_EQ(get_sz, sizeof(get));
EXPECT_EQ(get, kDefaultTOS);
@@ -229,7 +229,7 @@ TEST_P(IPUnboundSocketTest, CheckSkipECN) {
}
int get = -1;
socklen_t get_sz = sizeof(get);
- EXPECT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz),
+ ASSERT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz),
SyscallSucceedsWithValue(0));
EXPECT_EQ(get_sz, sizeof(get));
EXPECT_EQ(get, expect);
@@ -249,7 +249,7 @@ TEST_P(IPUnboundSocketTest, ZeroTOSOptionSize) {
}
int get = -1;
socklen_t get_sz = 0;
- EXPECT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz),
+ ASSERT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz),
SyscallSucceedsWithValue(0));
EXPECT_EQ(get_sz, 0);
EXPECT_EQ(get, -1);
@@ -276,7 +276,7 @@ TEST_P(IPUnboundSocketTest, SmallTOSOptionSize) {
}
uint get = -1;
socklen_t get_sz = i;
- EXPECT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz),
+ ASSERT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz),
SyscallSucceedsWithValue(0));
EXPECT_EQ(get_sz, expect_sz);
// Account for partial copies by getsockopt, retrieve the lower
@@ -297,7 +297,7 @@ TEST_P(IPUnboundSocketTest, LargeTOSOptionSize) {
// We expect the system call handler to only copy atmost sizeof(int) bytes
// as asserted by the check below. Hence, we do not expect the copy to
// overflow in getsockopt.
- EXPECT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz),
+ ASSERT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz),
SyscallSucceedsWithValue(0));
EXPECT_EQ(get_sz, sizeof(int));
EXPECT_EQ(get, set);
@@ -325,7 +325,7 @@ TEST_P(IPUnboundSocketTest, NegativeTOS) {
}
int get = -1;
socklen_t get_sz = sizeof(get);
- EXPECT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz),
+ ASSERT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz),
SyscallSucceedsWithValue(0));
EXPECT_EQ(get_sz, sizeof(get));
EXPECT_EQ(get, expect);
@@ -338,20 +338,20 @@ TEST_P(IPUnboundSocketTest, InvalidNegativeTOS) {
TOSOption t = GetTOSOption(GetParam().domain);
int expect;
if (GetParam().domain == AF_INET) {
- EXPECT_THAT(setsockopt(socket->get(), t.level, t.option, &set, set_sz),
+ ASSERT_THAT(setsockopt(socket->get(), t.level, t.option, &set, set_sz),
SyscallSucceedsWithValue(0));
expect = static_cast<uint8_t>(set);
if (GetParam().protocol == IPPROTO_TCP) {
expect &= ~INET_ECN_MASK;
}
} else {
- EXPECT_THAT(setsockopt(socket->get(), t.level, t.option, &set, set_sz),
+ ASSERT_THAT(setsockopt(socket->get(), t.level, t.option, &set, set_sz),
SyscallFailsWithErrno(EINVAL));
expect = 0;
}
int get = 0;
socklen_t get_sz = sizeof(get);
- EXPECT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz),
+ ASSERT_THAT(getsockopt(socket->get(), t.level, t.option, &get, &get_sz),
SyscallSucceedsWithValue(0));
EXPECT_EQ(get_sz, sizeof(get));
EXPECT_EQ(get, expect);
@@ -377,8 +377,11 @@ TEST_P(IPUnboundSocketTest, NullTOS) {
//
// Linux's implementation would need fixing as passing a nullptr as optval
// and non-zero optlen may not be valid.
- EXPECT_THAT(setsockopt(socket->get(), t.level, t.option, nullptr, set_sz),
- SyscallSucceedsWithValue(0));
+ // TODO(b/158666797): Combine the gVisor and linux cases for IPv6.
+ // Some kernel versions return EFAULT, so we handle both.
+ EXPECT_THAT(
+ setsockopt(socket->get(), t.level, t.option, nullptr, set_sz),
+ AnyOf(SyscallFailsWithErrno(EFAULT), SyscallSucceedsWithValue(0)));
}
}
socklen_t get_sz = sizeof(int);
@@ -419,6 +422,34 @@ TEST_P(IPUnboundSocketTest, InsufficientBufferTOS) {
EXPECT_THAT(sendmsg(socket->get(), &msg, 0), SyscallFailsWithErrno(EINVAL));
}
+TEST_P(IPUnboundSocketTest, ReuseAddrDefault) {
+ auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
+
+ int get = -1;
+ socklen_t get_sz = sizeof(get);
+ ASSERT_THAT(
+ getsockopt(socket->get(), SOL_SOCKET, SO_REUSEADDR, &get, &get_sz),
+ SyscallSucceedsWithValue(0));
+ EXPECT_EQ(get, kSockOptOff);
+ EXPECT_EQ(get_sz, sizeof(get));
+}
+
+TEST_P(IPUnboundSocketTest, SetReuseAddr) {
+ auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
+
+ ASSERT_THAT(setsockopt(socket->get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
+ sizeof(kSockOptOn)),
+ SyscallSucceedsWithValue(0));
+
+ int get = -1;
+ socklen_t get_sz = sizeof(get);
+ ASSERT_THAT(
+ getsockopt(socket->get(), SOL_SOCKET, SO_REUSEADDR, &get, &get_sz),
+ SyscallSucceedsWithValue(0));
+ EXPECT_EQ(get, kSockOptOn);
+ EXPECT_EQ(get_sz, sizeof(get));
+}
+
INSTANTIATE_TEST_SUITE_P(
IPUnboundSockets, IPUnboundSocketTest,
::testing::ValuesIn(VecCat<SocketKind>(VecCat<SocketKind>(
diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound.cc b/test/syscalls/linux/socket_ipv4_udp_unbound.cc
index bc4b07a62..de0f5f01b 100644
--- a/test/syscalls/linux/socket_ipv4_udp_unbound.cc
+++ b/test/syscalls/linux/socket_ipv4_udp_unbound.cc
@@ -18,6 +18,7 @@
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
+#include <sys/types.h>
#include <sys/un.h>
#include <cstdio>
@@ -1672,10 +1673,10 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToBcastThenSend) {
}
// Check that SO_REUSEADDR always delivers to the most recently bound socket.
-TEST_P(IPv4UDPUnboundSocketTest, ReuseAddrDistribution) {
- // FIXME(b/129164367): Support SO_REUSEADDR on UDP sockets.
- SKIP_IF(IsRunningOnGvisor());
-
+//
+// FIXME(gvisor.dev/issue/873): Endpoint order is not restored correctly. Enable
+// random and co-op save (below) once that is fixed.
+TEST_P(IPv4UDPUnboundSocketTest, ReuseAddrDistribution_NoRandomSave) {
std::vector<std::unique_ptr<FileDescriptor>> sockets;
sockets.emplace_back(ASSERT_NO_ERRNO_AND_VALUE(NewSocket()));
@@ -1696,6 +1697,9 @@ TEST_P(IPv4UDPUnboundSocketTest, ReuseAddrDistribution) {
constexpr int kMessageSize = 200;
+ // FIXME(gvisor.dev/issue/873): Endpoint order is not restored correctly.
+ const DisableSave ds;
+
for (int i = 0; i < 10; i++) {
// Add a new receiver.
sockets.emplace_back(ASSERT_NO_ERRNO_AND_VALUE(NewSocket()));
@@ -1836,9 +1840,6 @@ TEST_P(IPv4UDPUnboundSocketTest, BindReuseAddrReusePortConvertibleToReusePort) {
}
TEST_P(IPv4UDPUnboundSocketTest, BindReuseAddrReusePortConvertibleToReuseAddr) {
- // FIXME(b/129164367): Support SO_REUSEADDR on UDP sockets.
- SKIP_IF(IsRunningOnGvisor());
-
auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
auto socket3 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
@@ -1880,9 +1881,6 @@ TEST_P(IPv4UDPUnboundSocketTest, BindReuseAddrReusePortConvertibleToReuseAddr) {
}
TEST_P(IPv4UDPUnboundSocketTest, BindReuseAddrReusePortConversionReversable1) {
- // FIXME(b/129164367): Support SO_REUSEADDR on UDP sockets.
- SKIP_IF(IsRunningOnGvisor());
-
auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
auto socket3 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
@@ -1927,9 +1925,6 @@ TEST_P(IPv4UDPUnboundSocketTest, BindReuseAddrReusePortConversionReversable1) {
}
TEST_P(IPv4UDPUnboundSocketTest, BindReuseAddrReusePortConversionReversable2) {
- // FIXME(b/129164367): Support SO_REUSEADDR on UDP sockets.
- SKIP_IF(IsRunningOnGvisor());
-
auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
auto socket3 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
@@ -2020,9 +2015,6 @@ TEST_P(IPv4UDPUnboundSocketTest, BindDoubleReuseAddrReusePortThenReusePort) {
}
TEST_P(IPv4UDPUnboundSocketTest, BindDoubleReuseAddrReusePortThenReuseAddr) {
- // FIXME(b/129164367): Support SO_REUSEADDR on UDP sockets.
- SKIP_IF(IsRunningOnGvisor());
-
auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
auto socket3 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
@@ -2129,6 +2121,39 @@ TEST_P(IPv4UDPUnboundSocketTest, ReuseAddrReusePortDistribution) {
SyscallSucceedsWithValue(kMessageSize));
}
+// Check that connect returns EADDRNOTAVAIL when out of local ephemeral ports.
+// We disable S/R because this test creates a large number of sockets.
+TEST_P(IPv4UDPUnboundSocketTest, UDPConnectPortExhaustion_NoRandomSave) {
+ auto receiver1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
+ constexpr int kClients = 65536;
+ // Bind the first socket to the loopback and take note of the selected port.
+ auto addr = V4Loopback();
+ ASSERT_THAT(bind(receiver1->get(), reinterpret_cast<sockaddr*>(&addr.addr),
+ addr.addr_len),
+ SyscallSucceeds());
+ socklen_t addr_len = addr.addr_len;
+ ASSERT_THAT(getsockname(receiver1->get(),
+ reinterpret_cast<sockaddr*>(&addr.addr), &addr_len),
+ SyscallSucceeds());
+ EXPECT_EQ(addr_len, addr.addr_len);
+
+ // Disable cooperative S/R as we are making too many syscalls.
+ DisableSave ds;
+ std::vector<std::unique_ptr<FileDescriptor>> sockets;
+ for (int i = 0; i < kClients; i++) {
+ auto s = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
+
+ int ret = connect(s->get(), reinterpret_cast<sockaddr*>(&addr.addr),
+ addr.addr_len);
+ if (ret == 0) {
+ sockets.push_back(std::move(s));
+ continue;
+ }
+ ASSERT_THAT(ret, SyscallFailsWithErrno(EAGAIN));
+ break;
+ }
+}
+
// Test that socket will receive packet info control message.
TEST_P(IPv4UDPUnboundSocketTest, SetAndReceiveIPPKTINFO) {
// TODO(gvisor.dev/issue/1202): ioctl() is not supported by hostinet.
@@ -2212,5 +2237,220 @@ TEST_P(IPv4UDPUnboundSocketTest, SetAndReceiveIPPKTINFO) {
EXPECT_EQ(received_pktinfo.ipi_spec_dst.s_addr, htonl(INADDR_LOOPBACK));
EXPECT_EQ(received_pktinfo.ipi_addr.s_addr, htonl(INADDR_LOOPBACK));
}
+
+// Check that setting SO_RCVBUF below min is clamped to the minimum
+// receive buffer size.
+TEST_P(IPv4UDPUnboundSocketTest, SetSocketRecvBufBelowMin) {
+ auto s = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
+
+ // Discover minimum buffer size by setting it to zero.
+ constexpr int kRcvBufSz = 0;
+ ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &kRcvBufSz,
+ sizeof(kRcvBufSz)),
+ SyscallSucceeds());
+
+ int min = 0;
+ socklen_t min_len = sizeof(min);
+ ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &min, &min_len),
+ SyscallSucceeds());
+
+ // Linux doubles the value so let's use a value that when doubled will still
+ // be smaller than min.
+ int below_min = min / 2 - 1;
+ ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &below_min,
+ sizeof(below_min)),
+ SyscallSucceeds());
+
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &val, &val_len),
+ SyscallSucceeds());
+
+ ASSERT_EQ(min, val);
+}
+
+// Check that setting SO_RCVBUF above max is clamped to the maximum
+// receive buffer size.
+TEST_P(IPv4UDPUnboundSocketTest, SetSocketRecvBufAboveMax) {
+ auto s = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
+
+ // Discover maxmimum buffer size by setting to a really large value.
+ constexpr int kRcvBufSz = 0xffffffff;
+ ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &kRcvBufSz,
+ sizeof(kRcvBufSz)),
+ SyscallSucceeds());
+
+ int max = 0;
+ socklen_t max_len = sizeof(max);
+ ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &max, &max_len),
+ SyscallSucceeds());
+
+ int above_max = max + 1;
+ ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &above_max,
+ sizeof(above_max)),
+ SyscallSucceeds());
+
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &val, &val_len),
+ SyscallSucceeds());
+ ASSERT_EQ(max, val);
+}
+
+// Check that setting SO_RCVBUF min <= rcvBufSz <= max is honored.
+TEST_P(IPv4UDPUnboundSocketTest, SetSocketRecvBuf) {
+ auto s = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
+
+ int max = 0;
+ int min = 0;
+ {
+ // Discover maxmimum buffer size by setting to a really large value.
+ constexpr int kRcvBufSz = 0xffffffff;
+ ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &kRcvBufSz,
+ sizeof(kRcvBufSz)),
+ SyscallSucceeds());
+
+ max = 0;
+ socklen_t max_len = sizeof(max);
+ ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &max, &max_len),
+ SyscallSucceeds());
+ }
+
+ {
+ // Discover minimum buffer size by setting it to zero.
+ constexpr int kRcvBufSz = 0;
+ ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &kRcvBufSz,
+ sizeof(kRcvBufSz)),
+ SyscallSucceeds());
+
+ socklen_t min_len = sizeof(min);
+ ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &min, &min_len),
+ SyscallSucceeds());
+ }
+
+ int quarter_sz = min + (max - min) / 4;
+ ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &quarter_sz,
+ sizeof(quarter_sz)),
+ SyscallSucceeds());
+
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_RCVBUF, &val, &val_len),
+ SyscallSucceeds());
+
+ // Linux doubles the value set by SO_SNDBUF/SO_RCVBUF.
+ if (!IsRunningOnGvisor()) {
+ quarter_sz *= 2;
+ }
+ ASSERT_EQ(quarter_sz, val);
+}
+
+// Check that setting SO_SNDBUF below min is clamped to the minimum
+// send buffer size.
+TEST_P(IPv4UDPUnboundSocketTest, SetSocketSendBufBelowMin) {
+ auto s = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
+
+ // Discover minimum buffer size by setting it to zero.
+ constexpr int kSndBufSz = 0;
+ ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &kSndBufSz,
+ sizeof(kSndBufSz)),
+ SyscallSucceeds());
+
+ int min = 0;
+ socklen_t min_len = sizeof(min);
+ ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &min, &min_len),
+ SyscallSucceeds());
+
+ // Linux doubles the value so let's use a value that when doubled will still
+ // be smaller than min.
+ int below_min = min / 2 - 1;
+ ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &below_min,
+ sizeof(below_min)),
+ SyscallSucceeds());
+
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &val, &val_len),
+ SyscallSucceeds());
+
+ ASSERT_EQ(min, val);
+}
+
+// Check that setting SO_SNDBUF above max is clamped to the maximum
+// send buffer size.
+TEST_P(IPv4UDPUnboundSocketTest, SetSocketSendBufAboveMax) {
+ auto s = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
+
+ // Discover maxmimum buffer size by setting to a really large value.
+ constexpr int kSndBufSz = 0xffffffff;
+ ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &kSndBufSz,
+ sizeof(kSndBufSz)),
+ SyscallSucceeds());
+
+ int max = 0;
+ socklen_t max_len = sizeof(max);
+ ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &max, &max_len),
+ SyscallSucceeds());
+
+ int above_max = max + 1;
+ ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &above_max,
+ sizeof(above_max)),
+ SyscallSucceeds());
+
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &val, &val_len),
+ SyscallSucceeds());
+ ASSERT_EQ(max, val);
+}
+
+// Check that setting SO_SNDBUF min <= kSndBufSz <= max is honored.
+TEST_P(IPv4UDPUnboundSocketTest, SetSocketSendBuf) {
+ auto s = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
+
+ int max = 0;
+ int min = 0;
+ {
+ // Discover maxmimum buffer size by setting to a really large value.
+ constexpr int kSndBufSz = 0xffffffff;
+ ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &kSndBufSz,
+ sizeof(kSndBufSz)),
+ SyscallSucceeds());
+
+ max = 0;
+ socklen_t max_len = sizeof(max);
+ ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &max, &max_len),
+ SyscallSucceeds());
+ }
+
+ {
+ // Discover minimum buffer size by setting it to zero.
+ constexpr int kSndBufSz = 0;
+ ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &kSndBufSz,
+ sizeof(kSndBufSz)),
+ SyscallSucceeds());
+
+ socklen_t min_len = sizeof(min);
+ ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &min, &min_len),
+ SyscallSucceeds());
+ }
+
+ int quarter_sz = min + (max - min) / 4;
+ ASSERT_THAT(setsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &quarter_sz,
+ sizeof(quarter_sz)),
+ SyscallSucceeds());
+
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &val, &val_len),
+ SyscallSucceeds());
+
+ // Linux doubles the value set by SO_SNDBUF/SO_RCVBUF.
+ if (!IsRunningOnGvisor()) {
+ quarter_sz *= 2;
+ }
+
+ ASSERT_EQ(quarter_sz, val);
+}
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/socket_netdevice.cc b/test/syscalls/linux/socket_netdevice.cc
index 15d4b85a7..5f8d7f981 100644
--- a/test/syscalls/linux/socket_netdevice.cc
+++ b/test/syscalls/linux/socket_netdevice.cc
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include <linux/ethtool.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/sockios.h>
@@ -49,6 +50,7 @@ TEST(NetdeviceTest, Loopback) {
// Check that the loopback is zero hardware address.
ASSERT_THAT(ioctl(sock.get(), SIOCGIFHWADDR, &ifr), SyscallSucceeds());
+ EXPECT_EQ(ifr.ifr_hwaddr.sa_family, ARPHRD_LOOPBACK);
EXPECT_EQ(ifr.ifr_hwaddr.sa_data[0], 0);
EXPECT_EQ(ifr.ifr_hwaddr.sa_data[1], 0);
EXPECT_EQ(ifr.ifr_hwaddr.sa_data[2], 0);
@@ -178,6 +180,27 @@ TEST(NetdeviceTest, InterfaceMTU) {
EXPECT_GT(ifr.ifr_mtu, 0);
}
+TEST(NetdeviceTest, EthtoolGetTSInfo) {
+ FileDescriptor sock =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
+
+ struct ethtool_ts_info tsi = {};
+ tsi.cmd = ETHTOOL_GET_TS_INFO; // Get NIC's Timestamping capabilities.
+
+ // Prepare the request.
+ struct ifreq ifr = {};
+ snprintf(ifr.ifr_name, IFNAMSIZ, "lo");
+ ifr.ifr_data = (void*)&tsi;
+
+ // Check that SIOCGIFMTU returns a nonzero MTU.
+ if (IsRunningOnGvisor()) {
+ ASSERT_THAT(ioctl(sock.get(), SIOCETHTOOL, &ifr),
+ SyscallFailsWithErrno(EOPNOTSUPP));
+ return;
+ }
+ ASSERT_THAT(ioctl(sock.get(), SIOCETHTOOL, &ifr), SyscallSucceeds());
+}
+
} // namespace
} // namespace testing
diff --git a/test/syscalls/linux/socket_netlink_route.cc b/test/syscalls/linux/socket_netlink_route.cc
index fbe61c5a0..b3fcf8e7c 100644
--- a/test/syscalls/linux/socket_netlink_route.cc
+++ b/test/syscalls/linux/socket_netlink_route.cc
@@ -577,7 +577,10 @@ TEST(NetlinkRouteTest, GetRouteDump) {
std::cout << std::endl;
- if (msg->rtm_table == RT_TABLE_MAIN) {
+ // If the test is running in a new network namespace, it will have only
+ // the local route table.
+ if (msg->rtm_table == RT_TABLE_MAIN ||
+ (!IsRunningOnGvisor() && msg->rtm_table == RT_TABLE_LOCAL)) {
routeFound = true;
dstFound = rtDstFound && dstFound;
}
@@ -595,7 +598,7 @@ TEST(NetlinkRouteTest, GetRouteRequest) {
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get()));
- struct __attribute__((__packed__)) request {
+ struct request {
struct nlmsghdr hdr;
struct rtmsg rtm;
struct nlattr nla;
diff --git a/test/syscalls/linux/socket_unix.cc b/test/syscalls/linux/socket_unix.cc
index 8bf663e8b..591cab3fd 100644
--- a/test/syscalls/linux/socket_unix.cc
+++ b/test/syscalls/linux/socket_unix.cc
@@ -256,10 +256,9 @@ TEST_P(UnixSocketPairTest, ShutdownWrite) {
}
TEST_P(UnixSocketPairTest, SocketReopenFromProcfs) {
- // TODO(b/122310852): We should be returning ENXIO and NOT EIO.
- // TODO(github.dev/issue/1624): This should be resolved in VFS2. Verify
- // that this is the case and delete the SKIP_IF once we delete VFS1.
- SKIP_IF(IsRunningOnGvisor());
+ // TODO(gvisor.dev/issue/1624): In VFS1, we return EIO instead of ENXIO (see
+ // b/122310852). Remove this skip once VFS1 is deleted.
+ SKIP_IF(IsRunningWithVFS1());
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
// Opening a socket pair via /proc/self/fd/X is a ENXIO.
diff --git a/test/syscalls/linux/socket_unix_seqpacket.cc b/test/syscalls/linux/socket_unix_seqpacket.cc
index 84d3a569e..6d03df4d9 100644
--- a/test/syscalls/linux/socket_unix_seqpacket.cc
+++ b/test/syscalls/linux/socket_unix_seqpacket.cc
@@ -43,6 +43,24 @@ TEST_P(SeqpacketUnixSocketPairTest, ReadOneSideClosed) {
SyscallSucceedsWithValue(0));
}
+TEST_P(SeqpacketUnixSocketPairTest, Sendto) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+
+ struct sockaddr_un addr = {};
+ addr.sun_family = AF_UNIX;
+ constexpr char kPath[] = "\0nonexistent";
+ memcpy(addr.sun_path, kPath, sizeof(kPath));
+
+ constexpr char kStr[] = "abc";
+ ASSERT_THAT(sendto(sockets->second_fd(), kStr, 3, 0, (struct sockaddr*)&addr,
+ sizeof(addr)),
+ SyscallSucceedsWithValue(3));
+
+ char data[10] = {};
+ ASSERT_THAT(read(sockets->first_fd(), data, sizeof(data)),
+ SyscallSucceedsWithValue(3));
+}
+
} // namespace
} // namespace testing
diff --git a/test/syscalls/linux/socket_unix_stream.cc b/test/syscalls/linux/socket_unix_stream.cc
index 563467365..99e77b89e 100644
--- a/test/syscalls/linux/socket_unix_stream.cc
+++ b/test/syscalls/linux/socket_unix_stream.cc
@@ -89,6 +89,20 @@ TEST_P(StreamUnixSocketPairTest, ReadOneSideClosedWithUnreadData) {
SyscallFailsWithErrno(ECONNRESET));
}
+TEST_P(StreamUnixSocketPairTest, Sendto) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+
+ struct sockaddr_un addr = {};
+ addr.sun_family = AF_UNIX;
+ constexpr char kPath[] = "\0nonexistent";
+ memcpy(addr.sun_path, kPath, sizeof(kPath));
+
+ constexpr char kStr[] = "abc";
+ ASSERT_THAT(sendto(sockets->second_fd(), kStr, 3, 0, (struct sockaddr*)&addr,
+ sizeof(addr)),
+ SyscallFailsWithErrno(EISCONN));
+}
+
INSTANTIATE_TEST_SUITE_P(
AllUnixDomainSockets, StreamUnixSocketPairTest,
::testing::ValuesIn(IncludeReversals(VecCat<SocketPairKind>(
diff --git a/test/syscalls/linux/splice.cc b/test/syscalls/linux/splice.cc
index f103e2e56..08fc4b1b7 100644
--- a/test/syscalls/linux/splice.cc
+++ b/test/syscalls/linux/splice.cc
@@ -430,6 +430,55 @@ TEST(SpliceTest, TwoPipes) {
EXPECT_EQ(memcmp(rbuf.data(), buf.data(), kPageSize), 0);
}
+TEST(SpliceTest, TwoPipesCircular) {
+ // This test deadlocks the sentry on VFS1 because VFS1 splice ordering is
+ // based on fs.File.UniqueID, which does not prevent circular ordering between
+ // e.g. inode-level locks taken by fs.FileOperations.
+ SKIP_IF(IsRunningWithVFS1());
+
+ // Create two pipes.
+ int fds[2];
+ ASSERT_THAT(pipe(fds), SyscallSucceeds());
+ const FileDescriptor first_rfd(fds[0]);
+ const FileDescriptor first_wfd(fds[1]);
+ ASSERT_THAT(pipe(fds), SyscallSucceeds());
+ const FileDescriptor second_rfd(fds[0]);
+ const FileDescriptor second_wfd(fds[1]);
+
+ // On Linux, each pipe is normally limited to
+ // include/linux/pipe_fs_i.h:PIPE_DEF_BUFFERS buffers worth of data.
+ constexpr size_t PIPE_DEF_BUFFERS = 16;
+
+ // Write some data to each pipe. Below we splice 1 byte at a time between
+ // pipes, which very quickly causes each byte to be stored in a separate
+ // buffer, so we must ensure that the total amount of data in the system is <=
+ // PIPE_DEF_BUFFERS bytes.
+ std::vector<char> buf(PIPE_DEF_BUFFERS / 2);
+ RandomizeBuffer(buf.data(), buf.size());
+ ASSERT_THAT(write(first_wfd.get(), buf.data(), buf.size()),
+ SyscallSucceedsWithValue(buf.size()));
+ ASSERT_THAT(write(second_wfd.get(), buf.data(), buf.size()),
+ SyscallSucceedsWithValue(buf.size()));
+
+ // Have another thread splice from the second pipe to the first, while we
+ // splice from the first to the second. The test passes if this does not
+ // deadlock.
+ const int kIterations = 1000;
+ DisableSave ds;
+ ScopedThread t([&]() {
+ for (int i = 0; i < kIterations; i++) {
+ ASSERT_THAT(
+ splice(second_rfd.get(), nullptr, first_wfd.get(), nullptr, 1, 0),
+ SyscallSucceedsWithValue(1));
+ }
+ });
+ for (int i = 0; i < kIterations; i++) {
+ ASSERT_THAT(
+ splice(first_rfd.get(), nullptr, second_wfd.get(), nullptr, 1, 0),
+ SyscallSucceedsWithValue(1));
+ }
+}
+
TEST(SpliceTest, Blocking) {
// Create two new pipes.
int first[2], second[2];
diff --git a/test/syscalls/linux/sticky.cc b/test/syscalls/linux/sticky.cc
index 92eec0449..4afed6d08 100644
--- a/test/syscalls/linux/sticky.cc
+++ b/test/syscalls/linux/sticky.cc
@@ -40,11 +40,17 @@ namespace {
TEST(StickyTest, StickyBitPermDenied) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- EXPECT_THAT(chmod(dir.path().c_str(), 0777 | S_ISVTX), SyscallSucceeds());
- const FileDescriptor dirfd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY));
- ASSERT_THAT(mkdirat(dirfd.get(), "NewDir", 0755), SyscallSucceeds());
+ const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ EXPECT_THAT(chmod(parent.path().c_str(), 0777 | S_ISVTX), SyscallSucceeds());
+
+ // After changing credentials below, we need to use an open fd to make
+ // modifications in the parent dir, because there is no guarantee that we will
+ // still have the ability to open it.
+ const FileDescriptor parent_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(parent.path(), O_DIRECTORY));
+ ASSERT_THAT(openat(parent_fd.get(), "file", O_CREAT), SyscallSucceeds());
+ ASSERT_THAT(mkdirat(parent_fd.get(), "dir", 0777), SyscallSucceeds());
+ ASSERT_THAT(symlinkat("xyz", parent_fd.get(), "link"), SyscallSucceeds());
// Drop privileges and change IDs only in child thread, or else this parent
// thread won't be able to open some log files after the test ends.
@@ -62,7 +68,13 @@ TEST(StickyTest, StickyBitPermDenied) {
syscall(SYS_setresuid, -1, absl::GetFlag(FLAGS_scratch_uid), -1),
SyscallSucceeds());
- EXPECT_THAT(unlinkat(dirfd.get(), "NewDir", AT_REMOVEDIR),
+ EXPECT_THAT(renameat(parent_fd.get(), "file", parent_fd.get(), "file2"),
+ SyscallFailsWithErrno(EPERM));
+ EXPECT_THAT(unlinkat(parent_fd.get(), "file", 0),
+ SyscallFailsWithErrno(EPERM));
+ EXPECT_THAT(unlinkat(parent_fd.get(), "dir", AT_REMOVEDIR),
+ SyscallFailsWithErrno(EPERM));
+ EXPECT_THAT(unlinkat(parent_fd.get(), "link", 0),
SyscallFailsWithErrno(EPERM));
});
}
@@ -70,10 +82,17 @@ TEST(StickyTest, StickyBitPermDenied) {
TEST(StickyTest, StickyBitSameUID) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- EXPECT_THAT(chmod(dir.path().c_str(), 0777 | S_ISVTX), SyscallSucceeds());
- std::string path = JoinPath(dir.path(), "NewDir");
- ASSERT_THAT(mkdir(path.c_str(), 0755), SyscallSucceeds());
+ const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ EXPECT_THAT(chmod(parent.path().c_str(), 0777 | S_ISVTX), SyscallSucceeds());
+
+ // After changing credentials below, we need to use an open fd to make
+ // modifications in the parent dir, because there is no guarantee that we will
+ // still have the ability to open it.
+ const FileDescriptor parent_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(parent.path(), O_DIRECTORY));
+ ASSERT_THAT(openat(parent_fd.get(), "file", O_CREAT), SyscallSucceeds());
+ ASSERT_THAT(mkdirat(parent_fd.get(), "dir", 0777), SyscallSucceeds());
+ ASSERT_THAT(symlinkat("xyz", parent_fd.get(), "link"), SyscallSucceeds());
// Drop privileges and change IDs only in child thread, or else this parent
// thread won't be able to open some log files after the test ends.
@@ -89,18 +108,29 @@ TEST(StickyTest, StickyBitSameUID) {
SyscallSucceeds());
// We still have the same EUID.
- EXPECT_THAT(rmdir(path.c_str()), SyscallSucceeds());
+ EXPECT_THAT(renameat(parent_fd.get(), "file", parent_fd.get(), "file2"),
+ SyscallSucceeds());
+ EXPECT_THAT(unlinkat(parent_fd.get(), "file2", 0), SyscallSucceeds());
+ EXPECT_THAT(unlinkat(parent_fd.get(), "dir", AT_REMOVEDIR),
+ SyscallSucceeds());
+ EXPECT_THAT(unlinkat(parent_fd.get(), "link", 0), SyscallSucceeds());
});
}
TEST(StickyTest, StickyBitCapFOWNER) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));
- auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- EXPECT_THAT(chmod(dir.path().c_str(), 0777 | S_ISVTX), SyscallSucceeds());
- const FileDescriptor dirfd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY));
- ASSERT_THAT(mkdirat(dirfd.get(), "NewDir", 0755), SyscallSucceeds());
+ const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ EXPECT_THAT(chmod(parent.path().c_str(), 0777 | S_ISVTX), SyscallSucceeds());
+
+ // After changing credentials below, we need to use an open fd to make
+ // modifications in the parent dir, because there is no guarantee that we will
+ // still have the ability to open it.
+ const FileDescriptor parent_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(parent.path(), O_DIRECTORY));
+ ASSERT_THAT(openat(parent_fd.get(), "file", O_CREAT), SyscallSucceeds());
+ ASSERT_THAT(mkdirat(parent_fd.get(), "dir", 0777), SyscallSucceeds());
+ ASSERT_THAT(symlinkat("xyz", parent_fd.get(), "link"), SyscallSucceeds());
// Drop privileges and change IDs only in child thread, or else this parent
// thread won't be able to open some log files after the test ends.
@@ -117,8 +147,12 @@ TEST(StickyTest, StickyBitCapFOWNER) {
SyscallSucceeds());
EXPECT_NO_ERRNO(SetCapability(CAP_FOWNER, true));
- EXPECT_THAT(unlinkat(dirfd.get(), "NewDir", AT_REMOVEDIR),
+ EXPECT_THAT(renameat(parent_fd.get(), "file", parent_fd.get(), "file2"),
+ SyscallSucceeds());
+ EXPECT_THAT(unlinkat(parent_fd.get(), "file2", 0), SyscallSucceeds());
+ EXPECT_THAT(unlinkat(parent_fd.get(), "dir", AT_REMOVEDIR),
SyscallSucceeds());
+ EXPECT_THAT(unlinkat(parent_fd.get(), "link", 0), SyscallSucceeds());
});
}
} // namespace
diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc
index a4d2953e1..0cea7d11f 100644
--- a/test/syscalls/linux/tcp_socket.cc
+++ b/test/syscalls/linux/tcp_socket.cc
@@ -13,6 +13,9 @@
// limitations under the License.
#include <fcntl.h>
+#ifndef __fuchsia__
+#include <linux/filter.h>
+#endif // __fuchsia__
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <poll.h>
@@ -1559,6 +1562,63 @@ TEST_P(SimpleTcpSocketTest, SetTCPWindowClampAboveHalfMinRcvBuf) {
}
}
+#ifndef __fuchsia__
+
+// TODO(gvisor.dev/2746): Support SO_ATTACH_FILTER/SO_DETACH_FILTER.
+// gVisor currently silently ignores attaching a filter.
+TEST_P(SimpleTcpSocketTest, SetSocketAttachDetachFilter) {
+ FileDescriptor s =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
+ // Program generated using sudo tcpdump -i lo tcp and port 1234 -dd
+ struct sock_filter code[] = {
+ {0x28, 0, 0, 0x0000000c}, {0x15, 0, 6, 0x000086dd},
+ {0x30, 0, 0, 0x00000014}, {0x15, 0, 15, 0x00000006},
+ {0x28, 0, 0, 0x00000036}, {0x15, 12, 0, 0x000004d2},
+ {0x28, 0, 0, 0x00000038}, {0x15, 10, 11, 0x000004d2},
+ {0x15, 0, 10, 0x00000800}, {0x30, 0, 0, 0x00000017},
+ {0x15, 0, 8, 0x00000006}, {0x28, 0, 0, 0x00000014},
+ {0x45, 6, 0, 0x00001fff}, {0xb1, 0, 0, 0x0000000e},
+ {0x48, 0, 0, 0x0000000e}, {0x15, 2, 0, 0x000004d2},
+ {0x48, 0, 0, 0x00000010}, {0x15, 0, 1, 0x000004d2},
+ {0x6, 0, 0, 0x00040000}, {0x6, 0, 0, 0x00000000},
+ };
+ struct sock_fprog bpf = {
+ .len = ABSL_ARRAYSIZE(code),
+ .filter = code,
+ };
+ ASSERT_THAT(
+ setsockopt(s.get(), SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)),
+ SyscallSucceeds());
+
+ constexpr int val = 0;
+ ASSERT_THAT(
+ setsockopt(s.get(), SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val)),
+ SyscallSucceeds());
+}
+
+TEST_P(SimpleTcpSocketTest, SetSocketDetachFilterNoInstalledFilter) {
+ // TODO(gvisor.dev/2746): Support SO_ATTACH_FILTER/SO_DETACH_FILTER.
+ SKIP_IF(IsRunningOnGvisor());
+ FileDescriptor s =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
+ constexpr int val = 0;
+ ASSERT_THAT(
+ setsockopt(s.get(), SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val)),
+ SyscallFailsWithErrno(ENOENT));
+}
+
+TEST_P(SimpleTcpSocketTest, GetSocketDetachFilter) {
+ FileDescriptor s =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
+
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(getsockopt(s.get(), SOL_SOCKET, SO_DETACH_FILTER, &val, &val_len),
+ SyscallFailsWithErrno(ENOPROTOOPT));
+}
+
+#endif // __fuchsia__
+
INSTANTIATE_TEST_SUITE_P(AllInetTests, SimpleTcpSocketTest,
::testing::Values(AF_INET, AF_INET6));
diff --git a/test/syscalls/linux/timerfd.cc b/test/syscalls/linux/timerfd.cc
index 86ed87b7c..c4f8fdd7a 100644
--- a/test/syscalls/linux/timerfd.cc
+++ b/test/syscalls/linux/timerfd.cc
@@ -204,16 +204,33 @@ TEST_P(TimerfdTest, SetAbsoluteTime) {
EXPECT_EQ(1, val);
}
-TEST_P(TimerfdTest, IllegalReadWrite) {
+TEST_P(TimerfdTest, IllegalSeek) {
+ auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0));
+ if (!IsRunningWithVFS1()) {
+ EXPECT_THAT(lseek(tfd.get(), 0, SEEK_SET), SyscallFailsWithErrno(ESPIPE));
+ }
+}
+
+TEST_P(TimerfdTest, IllegalPread) {
+ auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0));
+ int val;
+ EXPECT_THAT(pread(tfd.get(), &val, sizeof(val), 0),
+ SyscallFailsWithErrno(ESPIPE));
+}
+
+TEST_P(TimerfdTest, IllegalPwrite) {
+ auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0));
+ EXPECT_THAT(pwrite(tfd.get(), "x", 1, 0), SyscallFailsWithErrno(ESPIPE));
+ if (!IsRunningWithVFS1()) {
+ }
+}
+
+TEST_P(TimerfdTest, IllegalWrite) {
auto const tfd =
ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), TFD_NONBLOCK));
uint64_t val = 0;
- EXPECT_THAT(PreadFd(tfd.get(), &val, sizeof(val), 0),
- SyscallFailsWithErrno(ESPIPE));
- EXPECT_THAT(WriteFd(tfd.get(), &val, sizeof(val)),
+ EXPECT_THAT(write(tfd.get(), &val, sizeof(val)),
SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(PwriteFd(tfd.get(), &val, sizeof(val), 0),
- SyscallFailsWithErrno(ESPIPE));
}
std::string PrintClockId(::testing::TestParamInfo<int> info) {
diff --git a/test/syscalls/linux/tuntap.cc b/test/syscalls/linux/tuntap.cc
index 6195b11e1..97d554e72 100644
--- a/test/syscalls/linux/tuntap.cc
+++ b/test/syscalls/linux/tuntap.cc
@@ -398,5 +398,25 @@ TEST_F(TuntapTest, SendUdpTriggersArpResolution) {
}
}
+// Write hang bug found by syskaller: b/155928773
+// https://syzkaller.appspot.com/bug?id=065b893bd8d1d04a4e0a1d53c578537cde1efe99
+TEST_F(TuntapTest, WriteHangBug155928773) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)));
+
+ FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(OpenAndAttachTap(kTapName, "10.0.0.1"));
+
+ int sock = socket(AF_INET, SOCK_DGRAM, 0);
+ ASSERT_THAT(sock, SyscallSucceeds());
+
+ struct sockaddr_in remote = {};
+ remote.sin_family = AF_INET;
+ remote.sin_port = htons(42);
+ inet_pton(AF_INET, "10.0.0.1", &remote.sin_addr);
+ // Return values do not matter in this test.
+ connect(sock, reinterpret_cast<struct sockaddr*>(&remote), sizeof(remote));
+ write(sock, "hello", 5);
+}
+
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/udp_socket_errqueue_test_case.cc b/test/syscalls/linux/udp_socket_errqueue_test_case.cc
index fcdba7279..54a0594f7 100644
--- a/test/syscalls/linux/udp_socket_errqueue_test_case.cc
+++ b/test/syscalls/linux/udp_socket_errqueue_test_case.cc
@@ -47,7 +47,7 @@ TEST_P(UdpSocketTest, ErrorQueue) {
msg.msg_controllen = sizeof(cmsgbuf);
// recv*(MSG_ERRQUEUE) never blocks, even without MSG_DONTWAIT.
- EXPECT_THAT(RetryEINTR(recvmsg)(s_, &msg, MSG_ERRQUEUE),
+ EXPECT_THAT(RetryEINTR(recvmsg)(bind_.get(), &msg, MSG_ERRQUEUE),
SyscallFailsWithErrno(EAGAIN));
}
diff --git a/test/syscalls/linux/udp_socket_test_cases.cc b/test/syscalls/linux/udp_socket_test_cases.cc
index 42521efef..60c48ed6e 100644
--- a/test/syscalls/linux/udp_socket_test_cases.cc
+++ b/test/syscalls/linux/udp_socket_test_cases.cc
@@ -16,12 +16,16 @@
#include <arpa/inet.h>
#include <fcntl.h>
+#ifndef __fuchsia__
+#include <linux/filter.h>
+#endif // __fuchsia__
#include <netinet/in.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
+#include "absl/strings/str_format.h"
#ifndef SIOCGSTAMP
#include <linux/sockios.h>
#endif
@@ -30,8 +34,11 @@
#include "absl/base/macros.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
+#include "test/syscalls/linux/ip_socket_test_util.h"
#include "test/syscalls/linux/socket_test_util.h"
#include "test/syscalls/linux/unix_domain_socket_test_util.h"
+#include "test/util/file_descriptor.h"
+#include "test/util/posix_error.h"
#include "test/util/test_util.h"
#include "test/util/thread_util.h"
@@ -54,517 +61,470 @@ uint16_t* Port(struct sockaddr_storage* addr) {
return nullptr;
}
+// Sets addr port to "port".
+void SetPort(struct sockaddr_storage* addr, uint16_t port) {
+ switch (addr->ss_family) {
+ case AF_INET: {
+ auto sin = reinterpret_cast<struct sockaddr_in*>(addr);
+ sin->sin_port = port;
+ break;
+ }
+ case AF_INET6: {
+ auto sin6 = reinterpret_cast<struct sockaddr_in6*>(addr);
+ sin6->sin6_port = port;
+ break;
+ }
+ }
+}
+
void UdpSocketTest::SetUp() {
- int type;
+ addrlen_ = GetAddrLength();
+
+ bind_ =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(GetFamily(), SOCK_DGRAM, IPPROTO_UDP));
+ memset(&bind_addr_storage_, 0, sizeof(bind_addr_storage_));
+ bind_addr_ = reinterpret_cast<struct sockaddr*>(&bind_addr_storage_);
+
+ sock_ =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(GetFamily(), SOCK_DGRAM, IPPROTO_UDP));
+}
+
+int UdpSocketTest::GetFamily() {
if (GetParam() == AddressFamily::kIpv4) {
- type = AF_INET;
- auto sin = reinterpret_cast<struct sockaddr_in*>(&anyaddr_storage_);
- addrlen_ = sizeof(*sin);
- sin->sin_addr.s_addr = htonl(INADDR_ANY);
- } else {
- type = AF_INET6;
- auto sin6 = reinterpret_cast<struct sockaddr_in6*>(&anyaddr_storage_);
- addrlen_ = sizeof(*sin6);
- if (GetParam() == AddressFamily::kIpv6) {
- sin6->sin6_addr = IN6ADDR_ANY_INIT;
- } else {
- TestAddress const& v4_mapped_any = V4MappedAny();
- sin6->sin6_addr =
- reinterpret_cast<const struct sockaddr_in6*>(&v4_mapped_any.addr)
- ->sin6_addr;
- }
+ return AF_INET;
}
- ASSERT_THAT(s_ = socket(type, SOCK_DGRAM, IPPROTO_UDP), SyscallSucceeds());
+ return AF_INET6;
+}
- ASSERT_THAT(t_ = socket(type, SOCK_DGRAM, IPPROTO_UDP), SyscallSucceeds());
+PosixError UdpSocketTest::BindLoopback() {
+ bind_addr_storage_ = InetLoopbackAddr();
+ struct sockaddr* bind_addr_ =
+ reinterpret_cast<struct sockaddr*>(&bind_addr_storage_);
+ return BindSocket(bind_.get(), bind_addr_);
+}
- memset(&anyaddr_storage_, 0, sizeof(anyaddr_storage_));
- anyaddr_ = reinterpret_cast<struct sockaddr*>(&anyaddr_storage_);
- anyaddr_->sa_family = type;
+PosixError UdpSocketTest::BindAny() {
+ bind_addr_storage_ = InetAnyAddr();
+ struct sockaddr* bind_addr_ =
+ reinterpret_cast<struct sockaddr*>(&bind_addr_storage_);
+ return BindSocket(bind_.get(), bind_addr_);
+}
- if (gvisor::testing::IsRunningOnGvisor()) {
- for (size_t i = 0; i < ABSL_ARRAYSIZE(ports_); ++i) {
- ports_[i] = TestPort + i;
- }
- } else {
- // When not under gvisor, use utility function to pick port. Assert that
- // all ports are different.
- std::string error;
- for (size_t i = 0; i < ABSL_ARRAYSIZE(ports_); ++i) {
- // Find an unused port, we specify port 0 to allow the kernel to provide
- // the port.
- bool unique = true;
- do {
- ports_[i] = ASSERT_NO_ERRNO_AND_VALUE(PortAvailable(
- 0, AddressFamily::kDualStack, SocketType::kUdp, false));
- ASSERT_GT(ports_[i], 0);
- for (size_t j = 0; j < i; ++j) {
- if (ports_[j] == ports_[i]) {
- unique = false;
- break;
- }
- }
- } while (!unique);
- }
+PosixError UdpSocketTest::BindSocket(int socket, struct sockaddr* addr) {
+ socklen_t len = sizeof(bind_addr_storage_);
+
+ // Bind, then check that we get the right address.
+ RETURN_ERROR_IF_SYSCALL_FAIL(bind(socket, addr, addrlen_));
+
+ RETURN_ERROR_IF_SYSCALL_FAIL(getsockname(socket, addr, &len));
+
+ if (addrlen_ != len) {
+ return PosixError(
+ EINVAL,
+ absl::StrFormat("getsockname len: %u expected: %u", len, addrlen_));
}
+ return PosixError(0);
+}
- // Initialize the sockaddrs.
- for (size_t i = 0; i < ABSL_ARRAYSIZE(addr_); ++i) {
- memset(&addr_storage_[i], 0, sizeof(addr_storage_[i]));
-
- addr_[i] = reinterpret_cast<struct sockaddr*>(&addr_storage_[i]);
- addr_[i]->sa_family = type;
-
- switch (type) {
- case AF_INET: {
- auto sin = reinterpret_cast<struct sockaddr_in*>(addr_[i]);
- sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- sin->sin_port = htons(ports_[i]);
- break;
- }
- case AF_INET6: {
- auto sin6 = reinterpret_cast<struct sockaddr_in6*>(addr_[i]);
- sin6->sin6_addr = in6addr_loopback;
- sin6->sin6_port = htons(ports_[i]);
- break;
- }
- }
+socklen_t UdpSocketTest::GetAddrLength() {
+ struct sockaddr_storage addr;
+ if (GetFamily() == AF_INET) {
+ auto sin = reinterpret_cast<struct sockaddr_in*>(&addr);
+ return sizeof(*sin);
}
+
+ auto sin6 = reinterpret_cast<struct sockaddr_in6*>(&addr);
+ return sizeof(*sin6);
}
-TEST_P(UdpSocketTest, Creation) {
- int type = AF_INET6;
- if (GetParam() == AddressFamily::kIpv4) {
- type = AF_INET;
+sockaddr_storage UdpSocketTest::InetAnyAddr() {
+ struct sockaddr_storage addr;
+ memset(&addr, 0, sizeof(addr));
+ reinterpret_cast<struct sockaddr*>(&addr)->sa_family = GetFamily();
+
+ if (GetFamily() == AF_INET) {
+ auto sin = reinterpret_cast<struct sockaddr_in*>(&addr);
+ sin->sin_addr.s_addr = htonl(INADDR_ANY);
+ sin->sin_port = htons(0);
+ return addr;
}
- int s_;
+ auto sin6 = reinterpret_cast<struct sockaddr_in6*>(&addr);
+ sin6->sin6_addr = IN6ADDR_ANY_INIT;
+ sin6->sin6_port = htons(0);
+ return addr;
+}
+
+sockaddr_storage UdpSocketTest::InetLoopbackAddr() {
+ struct sockaddr_storage addr;
+ memset(&addr, 0, sizeof(addr));
+ reinterpret_cast<struct sockaddr*>(&addr)->sa_family = GetFamily();
+
+ if (GetFamily() == AF_INET) {
+ auto sin = reinterpret_cast<struct sockaddr_in*>(&addr);
+ sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sin->sin_port = htons(0);
+ return addr;
+ }
+ auto sin6 = reinterpret_cast<struct sockaddr_in6*>(&addr);
+ sin6->sin6_addr = in6addr_loopback;
+ sin6->sin6_port = htons(0);
+ return addr;
+}
+
+void UdpSocketTest::Disconnect(int sockfd) {
+ sockaddr_storage addr_storage = InetAnyAddr();
+ sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage);
+ socklen_t addrlen = sizeof(addr_storage);
+
+ addr->sa_family = AF_UNSPEC;
+ ASSERT_THAT(connect(sockfd, addr, addrlen), SyscallSucceeds());
+
+ // Check that after disconnect the socket is bound to the ANY address.
+ EXPECT_THAT(getsockname(sockfd, addr, &addrlen), SyscallSucceeds());
+ if (GetParam() == AddressFamily::kIpv4) {
+ auto addr_out = reinterpret_cast<struct sockaddr_in*>(addr);
+ EXPECT_EQ(addrlen, sizeof(*addr_out));
+ EXPECT_EQ(addr_out->sin_addr.s_addr, htonl(INADDR_ANY));
+ } else {
+ auto addr_out = reinterpret_cast<struct sockaddr_in6*>(addr);
+ EXPECT_EQ(addrlen, sizeof(*addr_out));
+ struct in6_addr loopback = IN6ADDR_ANY_INIT;
+
+ EXPECT_EQ(memcmp(&addr_out->sin6_addr, &loopback, sizeof(in6_addr)), 0);
+ }
+}
- ASSERT_THAT(s_ = socket(type, SOCK_DGRAM, IPPROTO_UDP), SyscallSucceeds());
- EXPECT_THAT(close(s_), SyscallSucceeds());
+TEST_P(UdpSocketTest, Creation) {
+ FileDescriptor sock =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(GetFamily(), SOCK_DGRAM, IPPROTO_UDP));
+ EXPECT_THAT(close(sock.release()), SyscallSucceeds());
- ASSERT_THAT(s_ = socket(type, SOCK_DGRAM, 0), SyscallSucceeds());
- EXPECT_THAT(close(s_), SyscallSucceeds());
+ sock = ASSERT_NO_ERRNO_AND_VALUE(Socket(GetFamily(), SOCK_DGRAM, 0));
+ EXPECT_THAT(close(sock.release()), SyscallSucceeds());
- ASSERT_THAT(s_ = socket(type, SOCK_STREAM, IPPROTO_UDP), SyscallFails());
+ ASSERT_THAT(socket(GetFamily(), SOCK_STREAM, IPPROTO_UDP), SyscallFails());
}
TEST_P(UdpSocketTest, Getsockname) {
// Check that we're not bound.
struct sockaddr_storage addr;
socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
+ EXPECT_THAT(
+ getsockname(bind_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen),
+ SyscallSucceeds());
EXPECT_EQ(addrlen, addrlen_);
- EXPECT_EQ(memcmp(&addr, anyaddr_, addrlen_), 0);
+ struct sockaddr_storage any = InetAnyAddr();
+ EXPECT_EQ(memcmp(&addr, reinterpret_cast<struct sockaddr*>(&any), addrlen_),
+ 0);
- // Bind, then check that we get the right address.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
+
+ EXPECT_THAT(
+ getsockname(bind_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen),
+ SyscallSucceeds());
- addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
EXPECT_EQ(addrlen, addrlen_);
- EXPECT_EQ(memcmp(&addr, addr_[0], addrlen_), 0);
+ EXPECT_EQ(memcmp(&addr, bind_addr_, addrlen_), 0);
}
TEST_P(UdpSocketTest, Getpeername) {
+ ASSERT_NO_ERRNO(BindLoopback());
+
// Check that we're not connected.
struct sockaddr_storage addr;
socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getpeername(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallFailsWithErrno(ENOTCONN));
+ EXPECT_THAT(
+ getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen),
+ SyscallFailsWithErrno(ENOTCONN));
// Connect, then check that we get the right address.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
addrlen = sizeof(addr);
- EXPECT_THAT(getpeername(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
+ EXPECT_THAT(
+ getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen),
+ SyscallSucceeds());
EXPECT_EQ(addrlen, addrlen_);
- EXPECT_EQ(memcmp(&addr, addr_[0], addrlen_), 0);
+ EXPECT_EQ(memcmp(&addr, bind_addr_, addrlen_), 0);
}
TEST_P(UdpSocketTest, SendNotConnected) {
+ ASSERT_NO_ERRNO(BindLoopback());
+
// Do send & write, they must fail.
char buf[512];
- EXPECT_THAT(send(s_, buf, sizeof(buf), 0),
+ EXPECT_THAT(send(sock_.get(), buf, sizeof(buf), 0),
SyscallFailsWithErrno(EDESTADDRREQ));
- EXPECT_THAT(write(s_, buf, sizeof(buf)), SyscallFailsWithErrno(EDESTADDRREQ));
+ EXPECT_THAT(write(sock_.get(), buf, sizeof(buf)),
+ SyscallFailsWithErrno(EDESTADDRREQ));
// Use sendto.
- ASSERT_THAT(sendto(s_, buf, sizeof(buf), 0, addr_[0], addrlen_),
+ ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_),
SyscallSucceedsWithValue(sizeof(buf)));
// Check that we're bound now.
struct sockaddr_storage addr;
socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
+ EXPECT_THAT(
+ getsockname(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen),
+ SyscallSucceeds());
EXPECT_EQ(addrlen, addrlen_);
EXPECT_NE(*Port(&addr), 0);
}
TEST_P(UdpSocketTest, ConnectBinds) {
+ ASSERT_NO_ERRNO(BindLoopback());
+
// Connect the socket.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
// Check that we're bound now.
struct sockaddr_storage addr;
socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
+ EXPECT_THAT(
+ getsockname(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen),
+ SyscallSucceeds());
EXPECT_EQ(addrlen, addrlen_);
EXPECT_NE(*Port(&addr), 0);
}
TEST_P(UdpSocketTest, ReceiveNotBound) {
char buf[512];
- EXPECT_THAT(recv(s_, buf, sizeof(buf), MSG_DONTWAIT),
+ EXPECT_THAT(recv(sock_.get(), buf, sizeof(buf), MSG_DONTWAIT),
SyscallFailsWithErrno(EWOULDBLOCK));
}
TEST_P(UdpSocketTest, Bind) {
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
// Try to bind again.
- EXPECT_THAT(bind(s_, addr_[1], addrlen_), SyscallFailsWithErrno(EINVAL));
+ EXPECT_THAT(bind(bind_.get(), bind_addr_, addrlen_),
+ SyscallFailsWithErrno(EINVAL));
// Check that we're still bound to the original address.
struct sockaddr_storage addr;
socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
+ EXPECT_THAT(
+ getsockname(bind_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen),
+ SyscallSucceeds());
EXPECT_EQ(addrlen, addrlen_);
- EXPECT_EQ(memcmp(&addr, addr_[0], addrlen_), 0);
+ EXPECT_EQ(memcmp(&addr, bind_addr_, addrlen_), 0);
}
TEST_P(UdpSocketTest, BindInUse) {
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
// Try to bind again.
- EXPECT_THAT(bind(t_, addr_[0], addrlen_), SyscallFailsWithErrno(EADDRINUSE));
+ EXPECT_THAT(bind(sock_.get(), bind_addr_, addrlen_),
+ SyscallFailsWithErrno(EADDRINUSE));
}
TEST_P(UdpSocketTest, ReceiveAfterConnect) {
- // Connect s_ to loopback:TestPort, and bind t_ to loopback:TestPort.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(bind(t_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
- // Get the address s_ was bound to during connect.
- struct sockaddr_storage addr;
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
- EXPECT_EQ(addrlen, addrlen_);
-
- // Send from t_ to s_.
+ // Send from sock_ to bind_
char buf[512];
RandomizeBuffer(buf, sizeof(buf));
- ASSERT_THAT(sendto(t_, buf, sizeof(buf), 0,
- reinterpret_cast<sockaddr*>(&addr), addrlen),
+ ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_),
SyscallSucceedsWithValue(sizeof(buf)));
// Receive the data.
char received[sizeof(buf)];
- EXPECT_THAT(recv(s_, received, sizeof(received), 0),
+ EXPECT_THAT(recv(bind_.get(), received, sizeof(received), 0),
SyscallSucceedsWithValue(sizeof(received)));
EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
}
TEST_P(UdpSocketTest, ReceiveAfterDisconnect) {
- // Connect s_ to loopback:TestPort, and bind t_ to loopback:TestPort.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(bind(t_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(t_, addr_[1], addrlen_), SyscallSucceeds());
-
- // Get the address s_ was bound to during connect.
- struct sockaddr_storage addr;
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
- EXPECT_EQ(addrlen, addrlen_);
+ ASSERT_NO_ERRNO(BindLoopback());
for (int i = 0; i < 2; i++) {
- // Send from t_ to s_.
+ // Connet sock_ to bound address.
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
+
+ struct sockaddr_storage addr;
+ socklen_t addrlen = sizeof(addr);
+ EXPECT_THAT(
+ getsockname(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen),
+ SyscallSucceeds());
+ EXPECT_EQ(addrlen, addrlen_);
+
+ // Send from sock to bind_.
char buf[512];
RandomizeBuffer(buf, sizeof(buf));
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
- ASSERT_THAT(sendto(t_, buf, sizeof(buf), 0,
+
+ ASSERT_THAT(sendto(bind_.get(), buf, sizeof(buf), 0,
reinterpret_cast<sockaddr*>(&addr), addrlen),
SyscallSucceedsWithValue(sizeof(buf)));
// Receive the data.
char received[sizeof(buf)];
- EXPECT_THAT(recv(s_, received, sizeof(received), 0),
+ EXPECT_THAT(recv(sock_.get(), received, sizeof(received), 0),
SyscallSucceedsWithValue(sizeof(received)));
EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
- // Disconnect s_.
- struct sockaddr addr = {};
- addr.sa_family = AF_UNSPEC;
- ASSERT_THAT(connect(s_, &addr, sizeof(addr.sa_family)), SyscallSucceeds());
- // Connect s_ loopback:TestPort.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
+ // Disconnect sock_.
+ struct sockaddr unspec = {};
+ unspec.sa_family = AF_UNSPEC;
+ ASSERT_THAT(connect(sock_.get(), &unspec, sizeof(unspec.sa_family)),
+ SyscallSucceeds());
}
}
TEST_P(UdpSocketTest, Connect) {
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
// Check that we're connected to the right peer.
struct sockaddr_storage peer;
socklen_t peerlen = sizeof(peer);
- EXPECT_THAT(getpeername(s_, reinterpret_cast<sockaddr*>(&peer), &peerlen),
- SyscallSucceeds());
+ EXPECT_THAT(
+ getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&peer), &peerlen),
+ SyscallSucceeds());
EXPECT_EQ(peerlen, addrlen_);
- EXPECT_EQ(memcmp(&peer, addr_[0], addrlen_), 0);
+ EXPECT_EQ(memcmp(&peer, bind_addr_, addrlen_), 0);
// Try to bind after connect.
- EXPECT_THAT(bind(s_, addr_[1], addrlen_), SyscallFailsWithErrno(EINVAL));
+ struct sockaddr_storage any = InetAnyAddr();
+ EXPECT_THAT(
+ bind(sock_.get(), reinterpret_cast<struct sockaddr*>(&any), addrlen_),
+ SyscallFailsWithErrno(EINVAL));
+
+ struct sockaddr_storage bind2_storage = InetLoopbackAddr();
+ struct sockaddr* bind2_addr =
+ reinterpret_cast<struct sockaddr*>(&bind2_storage);
+ FileDescriptor bind2 =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(GetFamily(), SOCK_DGRAM, IPPROTO_UDP));
+ ASSERT_NO_ERRNO(BindSocket(bind2.get(), bind2_addr));
// Try to connect again.
- EXPECT_THAT(connect(s_, addr_[2], addrlen_), SyscallSucceeds());
+ EXPECT_THAT(connect(sock_.get(), bind2_addr, addrlen_), SyscallSucceeds());
// Check that peer name changed.
peerlen = sizeof(peer);
- EXPECT_THAT(getpeername(s_, reinterpret_cast<sockaddr*>(&peer), &peerlen),
- SyscallSucceeds());
+ EXPECT_THAT(
+ getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&peer), &peerlen),
+ SyscallSucceeds());
EXPECT_EQ(peerlen, addrlen_);
- EXPECT_EQ(memcmp(&peer, addr_[2], addrlen_), 0);
+ EXPECT_EQ(memcmp(&peer, bind2_addr, addrlen_), 0);
}
-void ConnectAny(AddressFamily family, int sockfd, uint16_t port) {
- struct sockaddr_storage addr = {};
+TEST_P(UdpSocketTest, ConnectAnyZero) {
+ // TODO(138658473): Enable when we can connect to port 0 with gVisor.
+ SKIP_IF(IsRunningOnGvisor());
- // Precondition check.
- {
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(
- getsockname(sockfd, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
-
- if (family == AddressFamily::kIpv4) {
- auto addr_out = reinterpret_cast<struct sockaddr_in*>(&addr);
- EXPECT_EQ(addrlen, sizeof(*addr_out));
- EXPECT_EQ(addr_out->sin_addr.s_addr, htonl(INADDR_ANY));
- } else {
- auto addr_out = reinterpret_cast<struct sockaddr_in6*>(&addr);
- EXPECT_EQ(addrlen, sizeof(*addr_out));
- struct in6_addr any = IN6ADDR_ANY_INIT;
- EXPECT_EQ(memcmp(&addr_out->sin6_addr, &any, sizeof(in6_addr)), 0);
- }
-
- {
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(
- getpeername(sockfd, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallFailsWithErrno(ENOTCONN));
- }
-
- struct sockaddr_storage baddr = {};
- if (family == AddressFamily::kIpv4) {
- auto addr_in = reinterpret_cast<struct sockaddr_in*>(&baddr);
- addrlen = sizeof(*addr_in);
- addr_in->sin_family = AF_INET;
- addr_in->sin_addr.s_addr = htonl(INADDR_ANY);
- addr_in->sin_port = port;
- } else {
- auto addr_in = reinterpret_cast<struct sockaddr_in6*>(&baddr);
- addrlen = sizeof(*addr_in);
- addr_in->sin6_family = AF_INET6;
- addr_in->sin6_port = port;
- if (family == AddressFamily::kIpv6) {
- addr_in->sin6_addr = IN6ADDR_ANY_INIT;
- } else {
- TestAddress const& v4_mapped_any = V4MappedAny();
- addr_in->sin6_addr =
- reinterpret_cast<const struct sockaddr_in6*>(&v4_mapped_any.addr)
- ->sin6_addr;
- }
- }
-
- // TODO(b/138658473): gVisor doesn't allow connecting to the zero port.
- if (port == 0) {
- SKIP_IF(IsRunningOnGvisor());
- }
-
- ASSERT_THAT(connect(sockfd, reinterpret_cast<sockaddr*>(&baddr), addrlen),
- SyscallSucceeds());
- }
-
- // Postcondition check.
- {
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(
- getsockname(sockfd, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
-
- if (family == AddressFamily::kIpv4) {
- auto addr_out = reinterpret_cast<struct sockaddr_in*>(&addr);
- EXPECT_EQ(addrlen, sizeof(*addr_out));
- EXPECT_EQ(addr_out->sin_addr.s_addr, htonl(INADDR_LOOPBACK));
- } else {
- auto addr_out = reinterpret_cast<struct sockaddr_in6*>(&addr);
- EXPECT_EQ(addrlen, sizeof(*addr_out));
- struct in6_addr loopback;
- if (family == AddressFamily::kIpv6) {
- loopback = IN6ADDR_LOOPBACK_INIT;
- } else {
- TestAddress const& v4_mapped_loopback = V4MappedLoopback();
- loopback = reinterpret_cast<const struct sockaddr_in6*>(
- &v4_mapped_loopback.addr)
- ->sin6_addr;
- }
-
- EXPECT_EQ(memcmp(&addr_out->sin6_addr, &loopback, sizeof(in6_addr)), 0);
- }
+ struct sockaddr_storage any = InetAnyAddr();
+ EXPECT_THAT(
+ connect(sock_.get(), reinterpret_cast<struct sockaddr*>(&any), addrlen_),
+ SyscallSucceeds());
- addrlen = sizeof(addr);
- if (port == 0) {
- EXPECT_THAT(
- getpeername(sockfd, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallFailsWithErrno(ENOTCONN));
- } else {
- EXPECT_THAT(
- getpeername(sockfd, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
- }
- }
+ struct sockaddr_storage addr;
+ socklen_t addrlen = sizeof(addr);
+ EXPECT_THAT(
+ getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen),
+ SyscallFailsWithErrno(ENOTCONN));
}
-TEST_P(UdpSocketTest, ConnectAny) { ConnectAny(GetParam(), s_, 0); }
-
TEST_P(UdpSocketTest, ConnectAnyWithPort) {
- auto port = *Port(reinterpret_cast<struct sockaddr_storage*>(addr_[1]));
- ConnectAny(GetParam(), s_, port);
+ ASSERT_NO_ERRNO(BindAny());
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
+
+ struct sockaddr_storage addr;
+ socklen_t addrlen = sizeof(addr);
+ EXPECT_THAT(
+ getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen),
+ SyscallSucceeds());
}
-void DisconnectAfterConnectAny(AddressFamily family, int sockfd, int port) {
- struct sockaddr_storage addr = {};
+TEST_P(UdpSocketTest, DisconnectAfterConnectAny) {
+ // TODO(138658473): Enable when we can connect to port 0 with gVisor.
+ SKIP_IF(IsRunningOnGvisor());
+ struct sockaddr_storage any = InetAnyAddr();
+ EXPECT_THAT(
+ connect(sock_.get(), reinterpret_cast<struct sockaddr*>(&any), addrlen_),
+ SyscallSucceeds());
+ struct sockaddr_storage addr;
socklen_t addrlen = sizeof(addr);
- struct sockaddr_storage baddr = {};
- if (family == AddressFamily::kIpv4) {
- auto addr_in = reinterpret_cast<struct sockaddr_in*>(&baddr);
- addrlen = sizeof(*addr_in);
- addr_in->sin_family = AF_INET;
- addr_in->sin_addr.s_addr = htonl(INADDR_ANY);
- addr_in->sin_port = port;
- } else {
- auto addr_in = reinterpret_cast<struct sockaddr_in6*>(&baddr);
- addrlen = sizeof(*addr_in);
- addr_in->sin6_family = AF_INET6;
- addr_in->sin6_port = port;
- if (family == AddressFamily::kIpv6) {
- addr_in->sin6_addr = IN6ADDR_ANY_INIT;
- } else {
- TestAddress const& v4_mapped_any = V4MappedAny();
- addr_in->sin6_addr =
- reinterpret_cast<const struct sockaddr_in6*>(&v4_mapped_any.addr)
- ->sin6_addr;
- }
- }
+ EXPECT_THAT(
+ getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen),
+ SyscallFailsWithErrno(ENOTCONN));
- // TODO(b/138658473): gVisor doesn't allow connecting to the zero port.
- if (port == 0) {
- SKIP_IF(IsRunningOnGvisor());
- }
+ Disconnect(sock_.get());
+}
- ASSERT_THAT(connect(sockfd, reinterpret_cast<sockaddr*>(&baddr), addrlen),
- SyscallSucceeds());
- // Now the socket is bound to the loopback address.
+TEST_P(UdpSocketTest, DisconnectAfterConnectAnyWithPort) {
+ ASSERT_NO_ERRNO(BindAny());
+ EXPECT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
- // Disconnect
- addrlen = sizeof(addr);
- addr.ss_family = AF_UNSPEC;
- ASSERT_THAT(connect(sockfd, reinterpret_cast<sockaddr*>(&addr), addrlen),
- SyscallSucceeds());
+ struct sockaddr_storage addr;
+ socklen_t addrlen = sizeof(addr);
+ EXPECT_THAT(
+ getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen),
+ SyscallSucceeds());
- // Check that after disconnect the socket is bound to the ANY address.
- EXPECT_THAT(getsockname(sockfd, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
- if (family == AddressFamily::kIpv4) {
- auto addr_out = reinterpret_cast<struct sockaddr_in*>(&addr);
- EXPECT_EQ(addrlen, sizeof(*addr_out));
- EXPECT_EQ(addr_out->sin_addr.s_addr, htonl(INADDR_ANY));
- } else {
- auto addr_out = reinterpret_cast<struct sockaddr_in6*>(&addr);
- EXPECT_EQ(addrlen, sizeof(*addr_out));
- struct in6_addr loopback = IN6ADDR_ANY_INIT;
+ EXPECT_EQ(addrlen, addrlen_);
+ EXPECT_EQ(*Port(&bind_addr_storage_), *Port(&addr));
- EXPECT_EQ(memcmp(&addr_out->sin6_addr, &loopback, sizeof(in6_addr)), 0);
- }
+ Disconnect(sock_.get());
}
-TEST_P(UdpSocketTest, DisconnectAfterConnectAny) {
- DisconnectAfterConnectAny(GetParam(), s_, 0);
-}
+TEST_P(UdpSocketTest, DisconnectAfterBind) {
+ ASSERT_NO_ERRNO(BindLoopback());
-TEST_P(UdpSocketTest, DisconnectAfterConnectAnyWithPort) {
- auto port = *Port(reinterpret_cast<struct sockaddr_storage*>(addr_[1]));
- DisconnectAfterConnectAny(GetParam(), s_, port);
-}
+ // Bind to the next port above bind_.
+ struct sockaddr_storage addr_storage = InetLoopbackAddr();
+ struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage);
+ SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1);
+ ASSERT_NO_ERRNO(BindSocket(sock_.get(), addr));
-TEST_P(UdpSocketTest, DisconnectAfterBind) {
- ASSERT_THAT(bind(s_, addr_[1], addrlen_), SyscallSucceeds());
// Connect the socket.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
- struct sockaddr_storage addr = {};
- addr.ss_family = AF_UNSPEC;
- EXPECT_THAT(
- connect(s_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr.ss_family)),
- SyscallSucceeds());
+ struct sockaddr_storage unspec = {};
+ unspec.ss_family = AF_UNSPEC;
+ EXPECT_THAT(connect(sock_.get(), reinterpret_cast<sockaddr*>(&unspec),
+ sizeof(unspec.ss_family)),
+ SyscallSucceeds());
// Check that we're still bound.
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
+ socklen_t addrlen = sizeof(unspec);
+ EXPECT_THAT(
+ getsockname(sock_.get(), reinterpret_cast<sockaddr*>(&unspec), &addrlen),
+ SyscallSucceeds());
EXPECT_EQ(addrlen, addrlen_);
- EXPECT_EQ(memcmp(&addr, addr_[1], addrlen_), 0);
+ EXPECT_EQ(memcmp(addr, &unspec, addrlen_), 0);
addrlen = sizeof(addr);
- EXPECT_THAT(getpeername(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
+ EXPECT_THAT(getpeername(sock_.get(), addr, &addrlen),
SyscallFailsWithErrno(ENOTCONN));
}
TEST_P(UdpSocketTest, BindToAnyConnnectToLocalhost) {
- struct sockaddr_storage baddr = {};
- auto port = *Port(reinterpret_cast<struct sockaddr_storage*>(addr_[1]));
- if (GetParam() == AddressFamily::kIpv4) {
- auto addr_in = reinterpret_cast<struct sockaddr_in*>(&baddr);
- addr_in->sin_family = AF_INET;
- addr_in->sin_port = port;
- addr_in->sin_addr.s_addr = htonl(INADDR_ANY);
- } else {
- auto addr_in = reinterpret_cast<struct sockaddr_in6*>(&baddr);
- addr_in->sin6_family = AF_INET6;
- addr_in->sin6_port = port;
- addr_in->sin6_scope_id = 0;
- addr_in->sin6_addr = IN6ADDR_ANY_INIT;
- }
- ASSERT_THAT(bind(s_, reinterpret_cast<sockaddr*>(&baddr), addrlen_),
- SyscallSucceeds());
- // Connect the socket.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindAny());
- struct sockaddr_storage addr = {};
+ struct sockaddr_storage addr_storage = InetLoopbackAddr();
+ struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage);
+ SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1);
socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
+
+ // Connect the socket.
+ ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds());
+
+ EXPECT_THAT(getsockname(bind_.get(), addr, &addrlen), SyscallSucceeds());
// If the socket is bound to ANY and connected to a loopback address,
// getsockname() has to return the loopback address.
if (GetParam() == AddressFamily::kIpv4) {
- auto addr_out = reinterpret_cast<struct sockaddr_in*>(&addr);
+ auto addr_out = reinterpret_cast<struct sockaddr_in*>(addr);
EXPECT_EQ(addrlen, sizeof(*addr_out));
EXPECT_EQ(addr_out->sin_addr.s_addr, htonl(INADDR_LOOPBACK));
} else {
- auto addr_out = reinterpret_cast<struct sockaddr_in6*>(&addr);
+ auto addr_out = reinterpret_cast<struct sockaddr_in6*>(addr);
struct in6_addr loopback = IN6ADDR_LOOPBACK_INIT;
EXPECT_EQ(addrlen, sizeof(*addr_out));
EXPECT_EQ(memcmp(&addr_out->sin6_addr, &loopback, sizeof(in6_addr)), 0);
@@ -572,91 +532,97 @@ TEST_P(UdpSocketTest, BindToAnyConnnectToLocalhost) {
}
TEST_P(UdpSocketTest, DisconnectAfterBindToAny) {
- struct sockaddr_storage baddr = {};
- socklen_t addrlen;
- auto port = *Port(reinterpret_cast<struct sockaddr_storage*>(addr_[1]));
- if (GetParam() == AddressFamily::kIpv4) {
- auto addr_in = reinterpret_cast<struct sockaddr_in*>(&baddr);
- addr_in->sin_family = AF_INET;
- addr_in->sin_port = port;
- addr_in->sin_addr.s_addr = htonl(INADDR_ANY);
- } else {
- auto addr_in = reinterpret_cast<struct sockaddr_in6*>(&baddr);
- addr_in->sin6_family = AF_INET6;
- addr_in->sin6_port = port;
- addr_in->sin6_scope_id = 0;
- addr_in->sin6_addr = IN6ADDR_ANY_INIT;
- }
- ASSERT_THAT(bind(s_, reinterpret_cast<sockaddr*>(&baddr), addrlen_),
- SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
+
+ struct sockaddr_storage any_storage = InetAnyAddr();
+ struct sockaddr* any = reinterpret_cast<struct sockaddr*>(&any_storage);
+ SetPort(&any_storage, *Port(&bind_addr_storage_) + 1);
+
+ ASSERT_NO_ERRNO(BindSocket(sock_.get(), any));
+
// Connect the socket.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
- struct sockaddr_storage addr = {};
- addr.ss_family = AF_UNSPEC;
- EXPECT_THAT(
- connect(s_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr.ss_family)),
- SyscallSucceeds());
+ Disconnect(sock_.get());
// Check that we're still bound.
- addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
+ struct sockaddr_storage addr;
+ socklen_t addrlen = sizeof(addr);
+ EXPECT_THAT(
+ getsockname(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen),
+ SyscallSucceeds());
EXPECT_EQ(addrlen, addrlen_);
- EXPECT_EQ(memcmp(&addr, &baddr, addrlen), 0);
+ EXPECT_EQ(memcmp(&addr, any, addrlen), 0);
addrlen = sizeof(addr);
- EXPECT_THAT(getpeername(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallFailsWithErrno(ENOTCONN));
+ EXPECT_THAT(
+ getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen),
+ SyscallFailsWithErrno(ENOTCONN));
}
TEST_P(UdpSocketTest, Disconnect) {
+ ASSERT_NO_ERRNO(BindLoopback());
+
+ struct sockaddr_storage any_storage = InetAnyAddr();
+ struct sockaddr* any = reinterpret_cast<struct sockaddr*>(&any_storage);
+ SetPort(&any_storage, *Port(&bind_addr_storage_) + 1);
+ ASSERT_NO_ERRNO(BindSocket(sock_.get(), any));
+
for (int i = 0; i < 2; i++) {
// Try to connect again.
- EXPECT_THAT(connect(s_, addr_[2], addrlen_), SyscallSucceeds());
+ EXPECT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
// Check that we're connected to the right peer.
struct sockaddr_storage peer;
socklen_t peerlen = sizeof(peer);
- EXPECT_THAT(getpeername(s_, reinterpret_cast<sockaddr*>(&peer), &peerlen),
- SyscallSucceeds());
+ EXPECT_THAT(
+ getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&peer), &peerlen),
+ SyscallSucceeds());
EXPECT_EQ(peerlen, addrlen_);
- EXPECT_EQ(memcmp(&peer, addr_[2], addrlen_), 0);
+ EXPECT_EQ(memcmp(&peer, bind_addr_, addrlen_), 0);
// Try to disconnect.
struct sockaddr_storage addr = {};
addr.ss_family = AF_UNSPEC;
- EXPECT_THAT(
- connect(s_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr.ss_family)),
- SyscallSucceeds());
+ EXPECT_THAT(connect(sock_.get(), reinterpret_cast<sockaddr*>(&addr),
+ sizeof(addr.ss_family)),
+ SyscallSucceeds());
peerlen = sizeof(peer);
- EXPECT_THAT(getpeername(s_, reinterpret_cast<sockaddr*>(&peer), &peerlen),
- SyscallFailsWithErrno(ENOTCONN));
+ EXPECT_THAT(
+ getpeername(sock_.get(), reinterpret_cast<sockaddr*>(&peer), &peerlen),
+ SyscallFailsWithErrno(ENOTCONN));
// Check that we're still bound.
socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(getsockname(s_, reinterpret_cast<sockaddr*>(&addr), &addrlen),
- SyscallSucceeds());
+ EXPECT_THAT(
+ getsockname(sock_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen),
+ SyscallSucceeds());
EXPECT_EQ(addrlen, addrlen_);
- EXPECT_EQ(*Port(&addr), 0);
+ EXPECT_EQ(*Port(&addr), *Port(&any_storage));
}
}
TEST_P(UdpSocketTest, ConnectBadAddress) {
struct sockaddr addr = {};
- addr.sa_family = addr_[0]->sa_family;
- ASSERT_THAT(connect(s_, &addr, sizeof(addr.sa_family)),
+ addr.sa_family = GetFamily();
+ ASSERT_THAT(connect(sock_.get(), &addr, sizeof(addr.sa_family)),
SyscallFailsWithErrno(EINVAL));
}
TEST_P(UdpSocketTest, SendToAddressOtherThanConnected) {
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
+
+ struct sockaddr_storage addr_storage = InetAnyAddr();
+ struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage);
+ SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1);
+
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
// Send to a different destination than we're connected to.
char buf[512];
- EXPECT_THAT(sendto(s_, buf, sizeof(buf), 0, addr_[1], addrlen_),
+ EXPECT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, addr, addrlen_),
SyscallSucceedsWithValue(sizeof(buf)));
}
@@ -664,24 +630,27 @@ TEST_P(UdpSocketTest, ZerolengthWriteAllowed) {
// TODO(gvisor.dev/issue/1202): Hostinet does not support zero length writes.
SKIP_IF(IsRunningWithHostinet());
- // Bind s_ to loopback:TestPort, and connect to loopback:TestPort+1.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(s_, addr_[1], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
+ // Connect to loopback:bind_addr_+1.
+ struct sockaddr_storage addr_storage = InetLoopbackAddr();
+ struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage);
+ SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1);
+ ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds());
- // Bind t_ to loopback:TestPort+1.
- ASSERT_THAT(bind(t_, addr_[1], addrlen_), SyscallSucceeds());
+ // Bind sock to loopback:bind_addr_+1.
+ ASSERT_THAT(bind(sock_.get(), addr, addrlen_), SyscallSucceeds());
char buf[3];
- // Send zero length packet from s_ to t_.
- ASSERT_THAT(write(s_, buf, 0), SyscallSucceedsWithValue(0));
+ // Send zero length packet from bind_ to sock_.
+ ASSERT_THAT(write(bind_.get(), buf, 0), SyscallSucceedsWithValue(0));
- struct pollfd pfd = {t_, POLLIN, 0};
- ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000),
+ struct pollfd pfd = {sock_.get(), POLLIN, 0};
+ ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout*/ 1000),
SyscallSucceedsWithValue(1));
// Receive the packet.
char received[3];
- EXPECT_THAT(read(t_, received, sizeof(received)),
+ EXPECT_THAT(read(sock_.get(), received, sizeof(received)),
SyscallSucceedsWithValue(0));
}
@@ -689,216 +658,252 @@ TEST_P(UdpSocketTest, ZerolengthWriteAllowedNonBlockRead) {
// TODO(gvisor.dev/issue/1202): Hostinet does not support zero length writes.
SKIP_IF(IsRunningWithHostinet());
- // Bind s_ to loopback:TestPort, and connect to loopback:TestPort+1.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(s_, addr_[1], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
- // Bind t_ to loopback:TestPort+1.
- ASSERT_THAT(bind(t_, addr_[1], addrlen_), SyscallSucceeds());
+ // Connect to loopback:bind_addr_port+1.
+ struct sockaddr_storage addr_storage = InetLoopbackAddr();
+ struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage);
+ SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1);
+ ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds());
- // Set t_ to non-blocking.
+ // Bind sock to loopback:bind_addr_port+1.
+ ASSERT_THAT(bind(sock_.get(), addr, addrlen_), SyscallSucceeds());
+
+ // Set sock to non-blocking.
int opts = 0;
- ASSERT_THAT(opts = fcntl(t_, F_GETFL), SyscallSucceeds());
- ASSERT_THAT(fcntl(t_, F_SETFL, opts | O_NONBLOCK), SyscallSucceeds());
+ ASSERT_THAT(opts = fcntl(sock_.get(), F_GETFL), SyscallSucceeds());
+ ASSERT_THAT(fcntl(sock_.get(), F_SETFL, opts | O_NONBLOCK),
+ SyscallSucceeds());
char buf[3];
- // Send zero length packet from s_ to t_.
- ASSERT_THAT(write(s_, buf, 0), SyscallSucceedsWithValue(0));
+ // Send zero length packet from bind_ to sock_.
+ ASSERT_THAT(write(bind_.get(), buf, 0), SyscallSucceedsWithValue(0));
- struct pollfd pfd = {t_, POLLIN, 0};
+ struct pollfd pfd = {sock_.get(), POLLIN, 0};
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000),
SyscallSucceedsWithValue(1));
// Receive the packet.
char received[3];
- EXPECT_THAT(read(t_, received, sizeof(received)),
+ EXPECT_THAT(read(sock_.get(), received, sizeof(received)),
SyscallSucceedsWithValue(0));
- EXPECT_THAT(read(t_, received, sizeof(received)),
+ EXPECT_THAT(read(sock_.get(), received, sizeof(received)),
SyscallFailsWithErrno(EAGAIN));
}
TEST_P(UdpSocketTest, SendAndReceiveNotConnected) {
- // Bind s_ to loopback.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
- // Send some data to s_.
+ // Send some data to bind_.
char buf[512];
RandomizeBuffer(buf, sizeof(buf));
- ASSERT_THAT(sendto(t_, buf, sizeof(buf), 0, addr_[0], addrlen_),
+ ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_),
SyscallSucceedsWithValue(sizeof(buf)));
// Receive the data.
char received[sizeof(buf)];
- EXPECT_THAT(recv(s_, received, sizeof(received), 0),
+ EXPECT_THAT(recv(bind_.get(), received, sizeof(received), 0),
SyscallSucceedsWithValue(sizeof(received)));
EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
}
TEST_P(UdpSocketTest, SendAndReceiveConnected) {
- // Bind s_ to loopback:TestPort, and connect to loopback:TestPort+1.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(s_, addr_[1], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
- // Bind t_ to loopback:TestPort+1.
- ASSERT_THAT(bind(t_, addr_[1], addrlen_), SyscallSucceeds());
+ // Connect to loopback:bind_addr_port+1.
+ struct sockaddr_storage addr_storage = InetLoopbackAddr();
+ struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage);
+ SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1);
+ ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds());
- // Send some data from t_ to s_.
+ // Bind sock to loopback:TestPort+1.
+ ASSERT_THAT(bind(sock_.get(), addr, addrlen_), SyscallSucceeds());
+
+ // Send some data from sock to bind_.
char buf[512];
RandomizeBuffer(buf, sizeof(buf));
- ASSERT_THAT(sendto(t_, buf, sizeof(buf), 0, addr_[0], addrlen_),
+ ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_),
SyscallSucceedsWithValue(sizeof(buf)));
// Receive the data.
char received[sizeof(buf)];
- EXPECT_THAT(recv(s_, received, sizeof(received), 0),
+ EXPECT_THAT(recv(bind_.get(), received, sizeof(received), 0),
SyscallSucceedsWithValue(sizeof(received)));
EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
}
TEST_P(UdpSocketTest, ReceiveFromNotConnected) {
- // Bind s_ to loopback:TestPort, and connect to loopback:TestPort+1.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(s_, addr_[1], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
+
+ // Connect to loopback:bind_addr_port+1.
+ struct sockaddr_storage addr_storage = InetLoopbackAddr();
+ struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage);
+ SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1);
+ ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds());
- // Bind t_ to loopback:TestPort+2.
- ASSERT_THAT(bind(t_, addr_[2], addrlen_), SyscallSucceeds());
+ // Bind sock to loopback:bind_addr_port+2.
+ struct sockaddr_storage addr2_storage = InetLoopbackAddr();
+ struct sockaddr* addr2 = reinterpret_cast<struct sockaddr*>(&addr2_storage);
+ SetPort(&addr2_storage, *Port(&bind_addr_storage_) + 2);
+ ASSERT_THAT(bind(sock_.get(), addr2, addrlen_), SyscallSucceeds());
- // Send some data from t_ to s_.
+ // Send some data from sock to bind_.
char buf[512];
- ASSERT_THAT(sendto(t_, buf, sizeof(buf), 0, addr_[0], addrlen_),
+ ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_),
SyscallSucceedsWithValue(sizeof(buf)));
- // Check that the data isn't_ received because it was sent from a different
+ // Check that the data isn't received because it was sent from a different
// address than we're connected.
- EXPECT_THAT(recv(s_, buf, sizeof(buf), MSG_DONTWAIT),
+ EXPECT_THAT(recv(sock_.get(), buf, sizeof(buf), MSG_DONTWAIT),
SyscallFailsWithErrno(EWOULDBLOCK));
}
TEST_P(UdpSocketTest, ReceiveBeforeConnect) {
- // Bind s_ to loopback:TestPort.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
- // Bind t_ to loopback:TestPort+2.
- ASSERT_THAT(bind(t_, addr_[2], addrlen_), SyscallSucceeds());
+ // Bind sock to loopback:bind_addr_port+2.
+ struct sockaddr_storage addr2_storage = InetLoopbackAddr();
+ struct sockaddr* addr2 = reinterpret_cast<struct sockaddr*>(&addr2_storage);
+ SetPort(&addr2_storage, *Port(&bind_addr_storage_) + 2);
+ ASSERT_THAT(bind(sock_.get(), addr2, addrlen_), SyscallSucceeds());
- // Send some data from t_ to s_.
+ // Send some data from sock to bind_.
char buf[512];
RandomizeBuffer(buf, sizeof(buf));
- ASSERT_THAT(sendto(t_, buf, sizeof(buf), 0, addr_[0], addrlen_),
+ ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_),
SyscallSucceedsWithValue(sizeof(buf)));
// Connect to loopback:TestPort+1.
- ASSERT_THAT(connect(s_, addr_[1], addrlen_), SyscallSucceeds());
+ struct sockaddr_storage addr_storage = InetLoopbackAddr();
+ struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage);
+ SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1);
+ ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds());
// Receive the data. It works because it was sent before the connect.
char received[sizeof(buf)];
- EXPECT_THAT(recv(s_, received, sizeof(received), 0),
+ EXPECT_THAT(recv(bind_.get(), received, sizeof(received), 0),
SyscallSucceedsWithValue(sizeof(received)));
EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
// Send again. This time it should not be received.
- ASSERT_THAT(sendto(t_, buf, sizeof(buf), 0, addr_[0], addrlen_),
+ ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_),
SyscallSucceedsWithValue(sizeof(buf)));
- EXPECT_THAT(recv(s_, buf, sizeof(buf), MSG_DONTWAIT),
+ EXPECT_THAT(recv(bind_.get(), buf, sizeof(buf), MSG_DONTWAIT),
SyscallFailsWithErrno(EWOULDBLOCK));
}
TEST_P(UdpSocketTest, ReceiveFrom) {
- // Bind s_ to loopback:TestPort, and connect to loopback:TestPort+1.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(s_, addr_[1], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
+
+ // Connect to loopback:bind_addr_port+1.
+ struct sockaddr_storage addr_storage = InetLoopbackAddr();
+ struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage);
+ SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1);
+ ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds());
- // Bind t_ to loopback:TestPort+1.
- ASSERT_THAT(bind(t_, addr_[1], addrlen_), SyscallSucceeds());
+ // Bind sock to loopback:TestPort+1.
+ ASSERT_THAT(bind(sock_.get(), addr, addrlen_), SyscallSucceeds());
- // Send some data from t_ to s_.
+ // Send some data from sock to bind_.
char buf[512];
RandomizeBuffer(buf, sizeof(buf));
- ASSERT_THAT(sendto(t_, buf, sizeof(buf), 0, addr_[0], addrlen_),
+ ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_),
SyscallSucceedsWithValue(sizeof(buf)));
// Receive the data and sender address.
char received[sizeof(buf)];
- struct sockaddr_storage addr;
- socklen_t addrlen = sizeof(addr);
- EXPECT_THAT(recvfrom(s_, received, sizeof(received), 0,
- reinterpret_cast<sockaddr*>(&addr), &addrlen),
+ struct sockaddr_storage addr2;
+ socklen_t addr2len = sizeof(addr2);
+ EXPECT_THAT(recvfrom(bind_.get(), received, sizeof(received), 0,
+ reinterpret_cast<sockaddr*>(&addr2), &addr2len),
SyscallSucceedsWithValue(sizeof(received)));
EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
- EXPECT_EQ(addrlen, addrlen_);
- EXPECT_EQ(memcmp(&addr, addr_[1], addrlen_), 0);
+ EXPECT_EQ(addr2len, addrlen_);
+ EXPECT_EQ(memcmp(addr, &addr2, addrlen_), 0);
}
TEST_P(UdpSocketTest, Listen) {
- ASSERT_THAT(listen(s_, SOMAXCONN), SyscallFailsWithErrno(EOPNOTSUPP));
+ ASSERT_THAT(listen(sock_.get(), SOMAXCONN),
+ SyscallFailsWithErrno(EOPNOTSUPP));
}
TEST_P(UdpSocketTest, Accept) {
- ASSERT_THAT(accept(s_, nullptr, nullptr), SyscallFailsWithErrno(EOPNOTSUPP));
+ ASSERT_THAT(accept(sock_.get(), nullptr, nullptr),
+ SyscallFailsWithErrno(EOPNOTSUPP));
}
// This test validates that a read shutdown with pending data allows the read
// to proceed with the data before returning EAGAIN.
TEST_P(UdpSocketTest, ReadShutdownNonblockPendingData) {
- char received[512];
+ ASSERT_NO_ERRNO(BindLoopback());
- // Bind t_ to loopback:TestPort+2.
- ASSERT_THAT(bind(t_, addr_[2], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(t_, addr_[1], addrlen_), SyscallSucceeds());
+ // Connect to loopback:bind_addr_port+1.
+ struct sockaddr_storage addr_storage = InetLoopbackAddr();
+ struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage);
+ SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1);
+ ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds());
- // Connect the socket, then try to shutdown again.
- ASSERT_THAT(bind(s_, addr_[1], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(s_, addr_[2], addrlen_), SyscallSucceeds());
+ // Bind to loopback:bind_addr_port+1 and connect to bind_addr_.
+ ASSERT_THAT(bind(sock_.get(), addr, addrlen_), SyscallSucceeds());
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
// Verify that we get EWOULDBLOCK when there is nothing to read.
- EXPECT_THAT(recv(s_, received, sizeof(received), MSG_DONTWAIT),
+ char received[512];
+ EXPECT_THAT(recv(bind_.get(), received, sizeof(received), MSG_DONTWAIT),
SyscallFailsWithErrno(EWOULDBLOCK));
const char* buf = "abc";
- EXPECT_THAT(write(t_, buf, 3), SyscallSucceedsWithValue(3));
+ EXPECT_THAT(write(sock_.get(), buf, 3), SyscallSucceedsWithValue(3));
int opts = 0;
- ASSERT_THAT(opts = fcntl(s_, F_GETFL), SyscallSucceeds());
- ASSERT_THAT(fcntl(s_, F_SETFL, opts | O_NONBLOCK), SyscallSucceeds());
- ASSERT_THAT(opts = fcntl(s_, F_GETFL), SyscallSucceeds());
+ ASSERT_THAT(opts = fcntl(bind_.get(), F_GETFL), SyscallSucceeds());
+ ASSERT_THAT(fcntl(bind_.get(), F_SETFL, opts | O_NONBLOCK),
+ SyscallSucceeds());
+ ASSERT_THAT(opts = fcntl(bind_.get(), F_GETFL), SyscallSucceeds());
ASSERT_NE(opts & O_NONBLOCK, 0);
- EXPECT_THAT(shutdown(s_, SHUT_RD), SyscallSucceeds());
+ EXPECT_THAT(shutdown(bind_.get(), SHUT_RD), SyscallSucceeds());
- struct pollfd pfd = {s_, POLLIN, 0};
+ struct pollfd pfd = {bind_.get(), POLLIN, 0};
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000),
SyscallSucceedsWithValue(1));
// We should get the data even though read has been shutdown.
- EXPECT_THAT(recv(s_, received, 2, 0), SyscallSucceedsWithValue(2));
+ EXPECT_THAT(recv(bind_.get(), received, 2, 0), SyscallSucceedsWithValue(2));
// Because we read less than the entire packet length, since it's a packet
// based socket any subsequent reads should return EWOULDBLOCK.
- EXPECT_THAT(recv(s_, received, 1, 0), SyscallFailsWithErrno(EWOULDBLOCK));
+ EXPECT_THAT(recv(bind_.get(), received, 1, 0),
+ SyscallFailsWithErrno(EWOULDBLOCK));
}
// This test is validating that even after a socket is shutdown if it's
// reconnected it will reset the shutdown state.
TEST_P(UdpSocketTest, ReadShutdownSameSocketResetsShutdownState) {
char received[512];
- EXPECT_THAT(recv(s_, received, sizeof(received), MSG_DONTWAIT),
+ EXPECT_THAT(recv(bind_.get(), received, sizeof(received), MSG_DONTWAIT),
SyscallFailsWithErrno(EWOULDBLOCK));
- EXPECT_THAT(shutdown(s_, SHUT_RD), SyscallFailsWithErrno(ENOTCONN));
+ EXPECT_THAT(shutdown(bind_.get(), SHUT_RD), SyscallFailsWithErrno(ENOTCONN));
- EXPECT_THAT(recv(s_, received, sizeof(received), MSG_DONTWAIT),
+ EXPECT_THAT(recv(bind_.get(), received, sizeof(received), MSG_DONTWAIT),
SyscallFailsWithErrno(EWOULDBLOCK));
// Connect the socket, then try to shutdown again.
- ASSERT_THAT(bind(s_, addr_[1], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(s_, addr_[2], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
+
+ // Connect to loopback:bind_addr_port+1.
+ struct sockaddr_storage addr_storage = InetLoopbackAddr();
+ struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage);
+ SetPort(&addr_storage, *Port(&bind_addr_storage_) + 1);
+ ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds());
- EXPECT_THAT(recv(s_, received, sizeof(received), MSG_DONTWAIT),
+ EXPECT_THAT(recv(bind_.get(), received, sizeof(received), MSG_DONTWAIT),
SyscallFailsWithErrno(EWOULDBLOCK));
}
@@ -907,24 +912,26 @@ TEST_P(UdpSocketTest, ReadShutdown) {
// MSG_DONTWAIT blocks indefinitely.
SKIP_IF(IsRunningWithHostinet());
+ ASSERT_NO_ERRNO(BindLoopback());
+
char received[512];
- EXPECT_THAT(recv(s_, received, sizeof(received), MSG_DONTWAIT),
+ EXPECT_THAT(recv(sock_.get(), received, sizeof(received), MSG_DONTWAIT),
SyscallFailsWithErrno(EWOULDBLOCK));
- EXPECT_THAT(shutdown(s_, SHUT_RD), SyscallFailsWithErrno(ENOTCONN));
+ EXPECT_THAT(shutdown(sock_.get(), SHUT_RD), SyscallFailsWithErrno(ENOTCONN));
- EXPECT_THAT(recv(s_, received, sizeof(received), MSG_DONTWAIT),
+ EXPECT_THAT(recv(sock_.get(), received, sizeof(received), MSG_DONTWAIT),
SyscallFailsWithErrno(EWOULDBLOCK));
// Connect the socket, then try to shutdown again.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
- EXPECT_THAT(recv(s_, received, sizeof(received), MSG_DONTWAIT),
+ EXPECT_THAT(recv(sock_.get(), received, sizeof(received), MSG_DONTWAIT),
SyscallFailsWithErrno(EWOULDBLOCK));
- EXPECT_THAT(shutdown(s_, SHUT_RD), SyscallSucceeds());
+ EXPECT_THAT(shutdown(sock_.get(), SHUT_RD), SyscallSucceeds());
- EXPECT_THAT(recv(s_, received, sizeof(received), 0),
+ EXPECT_THAT(recv(sock_.get(), received, sizeof(received), 0),
SyscallSucceedsWithValue(0));
}
@@ -932,93 +939,94 @@ TEST_P(UdpSocketTest, ReadShutdownDifferentThread) {
// TODO(gvisor.dev/issue/1202): Calling recv() after shutdown without
// MSG_DONTWAIT blocks indefinitely.
SKIP_IF(IsRunningWithHostinet());
+ ASSERT_NO_ERRNO(BindLoopback());
char received[512];
- EXPECT_THAT(recv(s_, received, sizeof(received), MSG_DONTWAIT),
+ EXPECT_THAT(recv(sock_.get(), received, sizeof(received), MSG_DONTWAIT),
SyscallFailsWithErrno(EWOULDBLOCK));
// Connect the socket, then shutdown from another thread.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
- EXPECT_THAT(recv(s_, received, sizeof(received), MSG_DONTWAIT),
+ EXPECT_THAT(recv(sock_.get(), received, sizeof(received), MSG_DONTWAIT),
SyscallFailsWithErrno(EWOULDBLOCK));
ScopedThread t([&] {
absl::SleepFor(absl::Milliseconds(200));
- EXPECT_THAT(shutdown(this->s_, SHUT_RD), SyscallSucceeds());
+ EXPECT_THAT(shutdown(sock_.get(), SHUT_RD), SyscallSucceeds());
});
- EXPECT_THAT(RetryEINTR(recv)(s_, received, sizeof(received), 0),
+ EXPECT_THAT(RetryEINTR(recv)(sock_.get(), received, sizeof(received), 0),
SyscallSucceedsWithValue(0));
t.Join();
- EXPECT_THAT(RetryEINTR(recv)(s_, received, sizeof(received), 0),
+ EXPECT_THAT(RetryEINTR(recv)(sock_.get(), received, sizeof(received), 0),
SyscallSucceedsWithValue(0));
}
TEST_P(UdpSocketTest, WriteShutdown) {
- EXPECT_THAT(shutdown(s_, SHUT_WR), SyscallFailsWithErrno(ENOTCONN));
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
- EXPECT_THAT(shutdown(s_, SHUT_WR), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
+ EXPECT_THAT(shutdown(sock_.get(), SHUT_WR), SyscallFailsWithErrno(ENOTCONN));
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
+ EXPECT_THAT(shutdown(sock_.get(), SHUT_WR), SyscallSucceeds());
}
TEST_P(UdpSocketTest, SynchronousReceive) {
- // Bind s_ to loopback.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
- // Send some data to s_ from another thread.
+ // Send some data to bind_ from another thread.
char buf[512];
RandomizeBuffer(buf, sizeof(buf));
// Receive the data prior to actually starting the other thread.
char received[512];
- EXPECT_THAT(RetryEINTR(recv)(s_, received, sizeof(received), MSG_DONTWAIT),
- SyscallFailsWithErrno(EWOULDBLOCK));
+ EXPECT_THAT(
+ RetryEINTR(recv)(bind_.get(), received, sizeof(received), MSG_DONTWAIT),
+ SyscallFailsWithErrno(EWOULDBLOCK));
// Start the thread.
ScopedThread t([&] {
absl::SleepFor(absl::Milliseconds(200));
- ASSERT_THAT(
- sendto(this->t_, buf, sizeof(buf), 0, this->addr_[0], this->addrlen_),
- SyscallSucceedsWithValue(sizeof(buf)));
+ ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, this->bind_addr_,
+ this->addrlen_),
+ SyscallSucceedsWithValue(sizeof(buf)));
});
- EXPECT_THAT(RetryEINTR(recv)(s_, received, sizeof(received), 0),
+ EXPECT_THAT(RetryEINTR(recv)(bind_.get(), received, sizeof(received), 0),
SyscallSucceedsWithValue(512));
EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
}
TEST_P(UdpSocketTest, BoundaryPreserved_SendRecv) {
- // Bind s_ to loopback:TestPort.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
- // Send 3 packets from t_ to s_.
+ // Send 3 packets from sock to bind_.
constexpr int psize = 100;
char buf[3 * psize];
RandomizeBuffer(buf, sizeof(buf));
for (int i = 0; i < 3; ++i) {
- ASSERT_THAT(sendto(t_, buf + i * psize, psize, 0, addr_[0], addrlen_),
- SyscallSucceedsWithValue(psize));
+ ASSERT_THAT(
+ sendto(sock_.get(), buf + i * psize, psize, 0, bind_addr_, addrlen_),
+ SyscallSucceedsWithValue(psize));
}
// Receive the data as 3 separate packets.
char received[6 * psize];
for (int i = 0; i < 3; ++i) {
- EXPECT_THAT(recv(s_, received + i * psize, 3 * psize, 0),
+ EXPECT_THAT(recv(bind_.get(), received + i * psize, 3 * psize, 0),
SyscallSucceedsWithValue(psize));
}
EXPECT_EQ(memcmp(buf, received, 3 * psize), 0);
}
TEST_P(UdpSocketTest, BoundaryPreserved_WritevReadv) {
- // Bind s_ to loopback:TestPort.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
- // Direct writes from t_ to s_.
- ASSERT_THAT(connect(t_, addr_[0], addrlen_), SyscallSucceeds());
+ // Direct writes from sock to bind_.
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
- // Send 2 packets from t_ to s_, where each packet's data consists of 2
- // discontiguous iovecs.
+ // Send 2 packets from sock to bind_, where each packet's data consists of
+ // 2 discontiguous iovecs.
constexpr size_t kPieceSize = 100;
char buf[4 * kPieceSize];
RandomizeBuffer(buf, sizeof(buf));
@@ -1030,7 +1038,8 @@ TEST_P(UdpSocketTest, BoundaryPreserved_WritevReadv) {
reinterpret_cast<uintptr_t>(buf) + (i + 2 * j) * kPieceSize);
iov[j].iov_len = kPieceSize;
}
- ASSERT_THAT(writev(t_, iov, 2), SyscallSucceedsWithValue(2 * kPieceSize));
+ ASSERT_THAT(writev(sock_.get(), iov, 2),
+ SyscallSucceedsWithValue(2 * kPieceSize));
}
// Receive the data as 2 separate packets.
@@ -1042,17 +1051,17 @@ TEST_P(UdpSocketTest, BoundaryPreserved_WritevReadv) {
reinterpret_cast<uintptr_t>(received) + (i + 2 * j) * kPieceSize);
iov[j].iov_len = kPieceSize;
}
- ASSERT_THAT(readv(s_, iov, 3), SyscallSucceedsWithValue(2 * kPieceSize));
+ ASSERT_THAT(readv(bind_.get(), iov, 3),
+ SyscallSucceedsWithValue(2 * kPieceSize));
}
EXPECT_EQ(memcmp(buf, received, 4 * kPieceSize), 0);
}
TEST_P(UdpSocketTest, BoundaryPreserved_SendMsgRecvMsg) {
- // Bind s_ to loopback:TestPort.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
- // Send 2 packets from t_ to s_, where each packet's data consists of 2
- // discontiguous iovecs.
+ // Send 2 packets from sock to bind_, where each packet's data consists of
+ // 2 discontiguous iovecs.
constexpr size_t kPieceSize = 100;
char buf[4 * kPieceSize];
RandomizeBuffer(buf, sizeof(buf));
@@ -1065,11 +1074,12 @@ TEST_P(UdpSocketTest, BoundaryPreserved_SendMsgRecvMsg) {
iov[j].iov_len = kPieceSize;
}
struct msghdr msg = {};
- msg.msg_name = addr_[0];
+ msg.msg_name = bind_addr_;
msg.msg_namelen = addrlen_;
msg.msg_iov = iov;
msg.msg_iovlen = 2;
- ASSERT_THAT(sendmsg(t_, &msg, 0), SyscallSucceedsWithValue(2 * kPieceSize));
+ ASSERT_THAT(sendmsg(sock_.get(), &msg, 0),
+ SyscallSucceedsWithValue(2 * kPieceSize));
}
// Receive the data as 2 separate packets.
@@ -1084,85 +1094,87 @@ TEST_P(UdpSocketTest, BoundaryPreserved_SendMsgRecvMsg) {
struct msghdr msg = {};
msg.msg_iov = iov;
msg.msg_iovlen = 3;
- ASSERT_THAT(recvmsg(s_, &msg, 0), SyscallSucceedsWithValue(2 * kPieceSize));
+ ASSERT_THAT(recvmsg(bind_.get(), &msg, 0),
+ SyscallSucceedsWithValue(2 * kPieceSize));
}
EXPECT_EQ(memcmp(buf, received, 4 * kPieceSize), 0);
}
TEST_P(UdpSocketTest, FIONREADShutdown) {
+ ASSERT_NO_ERRNO(BindLoopback());
+
int n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
+ EXPECT_THAT(ioctl(sock_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
EXPECT_EQ(n, 0);
// A UDP socket must be connected before it can be shutdown.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
+ EXPECT_THAT(ioctl(sock_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
EXPECT_EQ(n, 0);
- EXPECT_THAT(shutdown(s_, SHUT_RD), SyscallSucceeds());
+ EXPECT_THAT(shutdown(sock_.get(), SHUT_RD), SyscallSucceeds());
n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
+ EXPECT_THAT(ioctl(sock_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
EXPECT_EQ(n, 0);
}
TEST_P(UdpSocketTest, FIONREADWriteShutdown) {
int n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
+ EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
EXPECT_EQ(n, 0);
- // Bind s_ to loopback:TestPort.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
// A UDP socket must be connected before it can be shutdown.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_THAT(connect(bind_.get(), bind_addr_, addrlen_), SyscallSucceeds());
n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
+ EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
EXPECT_EQ(n, 0);
const char str[] = "abc";
- ASSERT_THAT(send(s_, str, sizeof(str), 0),
+ ASSERT_THAT(send(bind_.get(), str, sizeof(str), 0),
SyscallSucceedsWithValue(sizeof(str)));
- struct pollfd pfd = {s_, POLLIN, 0};
+ struct pollfd pfd = {bind_.get(), POLLIN, 0};
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000),
SyscallSucceedsWithValue(1));
n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
+ EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
EXPECT_EQ(n, sizeof(str));
- EXPECT_THAT(shutdown(s_, SHUT_RD), SyscallSucceeds());
+ EXPECT_THAT(shutdown(bind_.get(), SHUT_RD), SyscallSucceeds());
n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
+ EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
EXPECT_EQ(n, sizeof(str));
}
// NOTE: Do not use `FIONREAD` as test name because it will be replaced by the
// corresponding macro and become `0x541B`.
TEST_P(UdpSocketTest, Fionread) {
- // Bind s_ to loopback:TestPort.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
// Check that the bound socket with an empty buffer reports an empty first
// packet.
int n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
+ EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
EXPECT_EQ(n, 0);
- // Send 3 packets from t_ to s_.
+ // Send 3 packets from sock to bind_.
constexpr int psize = 100;
char buf[3 * psize];
RandomizeBuffer(buf, sizeof(buf));
- struct pollfd pfd = {s_, POLLIN, 0};
+ struct pollfd pfd = {bind_.get(), POLLIN, 0};
for (int i = 0; i < 3; ++i) {
- ASSERT_THAT(sendto(t_, buf + i * psize, psize, 0, addr_[0], addrlen_),
- SyscallSucceedsWithValue(psize));
+ ASSERT_THAT(
+ sendto(sock_.get(), buf + i * psize, psize, 0, bind_addr_, addrlen_),
+ SyscallSucceedsWithValue(psize));
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000),
SyscallSucceedsWithValue(1));
@@ -1170,30 +1182,30 @@ TEST_P(UdpSocketTest, Fionread) {
// Check that regardless of how many packets are in the queue, the size
// reported is that of a single packet.
n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
+ EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
EXPECT_EQ(n, psize);
}
}
TEST_P(UdpSocketTest, FIONREADZeroLengthPacket) {
- // Bind s_ to loopback:TestPort.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
// Check that the bound socket with an empty buffer reports an empty first
// packet.
int n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
+ EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
EXPECT_EQ(n, 0);
- // Send 3 packets from t_ to s_.
+ // Send 3 packets from sock to bind_.
constexpr int psize = 100;
char buf[3 * psize];
RandomizeBuffer(buf, sizeof(buf));
- struct pollfd pfd = {s_, POLLIN, 0};
+ struct pollfd pfd = {bind_.get(), POLLIN, 0};
for (int i = 0; i < 3; ++i) {
- ASSERT_THAT(sendto(t_, buf + i * psize, 0, 0, addr_[0], addrlen_),
- SyscallSucceedsWithValue(0));
+ ASSERT_THAT(
+ sendto(sock_.get(), buf + i * psize, 0, 0, bind_addr_, addrlen_),
+ SyscallSucceedsWithValue(0));
// TODO(gvisor.dev/issue/2726): sending a zero-length message to a hostinet
// socket does not cause a poll event to be triggered.
@@ -1205,40 +1217,77 @@ TEST_P(UdpSocketTest, FIONREADZeroLengthPacket) {
// Check that regardless of how many packets are in the queue, the size
// reported is that of a single packet.
n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
+ EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
EXPECT_EQ(n, 0);
}
}
TEST_P(UdpSocketTest, FIONREADZeroLengthWriteShutdown) {
int n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
+ EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
EXPECT_EQ(n, 0);
- // Bind s_ to loopback:TestPort.
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
// A UDP socket must be connected before it can be shutdown.
- ASSERT_THAT(connect(s_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_THAT(connect(bind_.get(), bind_addr_, addrlen_), SyscallSucceeds());
n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
+ EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
EXPECT_EQ(n, 0);
const char str[] = "abc";
- ASSERT_THAT(send(s_, str, 0, 0), SyscallSucceedsWithValue(0));
+ ASSERT_THAT(send(bind_.get(), str, 0, 0), SyscallSucceedsWithValue(0));
n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
+ EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
EXPECT_EQ(n, 0);
- EXPECT_THAT(shutdown(s_, SHUT_RD), SyscallSucceeds());
+ EXPECT_THAT(shutdown(bind_.get(), SHUT_RD), SyscallSucceeds());
n = -1;
- EXPECT_THAT(ioctl(s_, FIONREAD, &n), SyscallSucceedsWithValue(0));
+ EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
EXPECT_EQ(n, 0);
}
+TEST_P(UdpSocketTest, SoNoCheckOffByDefault) {
+ // TODO(gvisor.dev/issue/1202): SO_NO_CHECK socket option not supported by
+ // hostinet.
+ SKIP_IF(IsRunningWithHostinet());
+
+ int v = -1;
+ socklen_t optlen = sizeof(v);
+ ASSERT_THAT(getsockopt(bind_.get(), SOL_SOCKET, SO_NO_CHECK, &v, &optlen),
+ SyscallSucceeds());
+ ASSERT_EQ(v, kSockOptOff);
+ ASSERT_EQ(optlen, sizeof(v));
+}
+
+TEST_P(UdpSocketTest, SoNoCheck) {
+ // TODO(gvisor.dev/issue/1202): SO_NO_CHECK socket option not supported by
+ // hostinet.
+ SKIP_IF(IsRunningWithHostinet());
+
+ int v = kSockOptOn;
+ socklen_t optlen = sizeof(v);
+ ASSERT_THAT(setsockopt(bind_.get(), SOL_SOCKET, SO_NO_CHECK, &v, optlen),
+ SyscallSucceeds());
+ v = -1;
+ ASSERT_THAT(getsockopt(bind_.get(), SOL_SOCKET, SO_NO_CHECK, &v, &optlen),
+ SyscallSucceeds());
+ ASSERT_EQ(v, kSockOptOn);
+ ASSERT_EQ(optlen, sizeof(v));
+
+ v = kSockOptOff;
+ ASSERT_THAT(setsockopt(bind_.get(), SOL_SOCKET, SO_NO_CHECK, &v, optlen),
+ SyscallSucceeds());
+ v = -1;
+ ASSERT_THAT(getsockopt(bind_.get(), SOL_SOCKET, SO_NO_CHECK, &v, &optlen),
+ SyscallSucceeds());
+ ASSERT_EQ(v, kSockOptOff);
+ ASSERT_EQ(optlen, sizeof(v));
+}
+
TEST_P(UdpSocketTest, SoTimestampOffByDefault) {
// TODO(gvisor.dev/issue/1202): SO_TIMESTAMP socket option not supported by
// hostinet.
@@ -1246,7 +1295,7 @@ TEST_P(UdpSocketTest, SoTimestampOffByDefault) {
int v = -1;
socklen_t optlen = sizeof(v);
- ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_TIMESTAMP, &v, &optlen),
+ ASSERT_THAT(getsockopt(bind_.get(), SOL_SOCKET, SO_TIMESTAMP, &v, &optlen),
SyscallSucceeds());
ASSERT_EQ(v, kSockOptOff);
ASSERT_EQ(optlen, sizeof(v));
@@ -1257,18 +1306,19 @@ TEST_P(UdpSocketTest, SoTimestamp) {
// supported by hostinet.
SKIP_IF(IsRunningWithHostinet());
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(t_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
int v = 1;
- ASSERT_THAT(setsockopt(s_, SOL_SOCKET, SO_TIMESTAMP, &v, sizeof(v)),
+ ASSERT_THAT(setsockopt(bind_.get(), SOL_SOCKET, SO_TIMESTAMP, &v, sizeof(v)),
SyscallSucceeds());
char buf[3];
- // Send zero length packet from t_ to s_.
- ASSERT_THAT(RetryEINTR(write)(t_, buf, 0), SyscallSucceedsWithValue(0));
+ // Send zero length packet from sock to bind_.
+ ASSERT_THAT(RetryEINTR(write)(sock_.get(), buf, 0),
+ SyscallSucceedsWithValue(0));
- struct pollfd pfd = {s_, POLLIN, 0};
+ struct pollfd pfd = {bind_.get(), POLLIN, 0};
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000),
SyscallSucceedsWithValue(1));
@@ -1282,7 +1332,8 @@ TEST_P(UdpSocketTest, SoTimestamp) {
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
- ASSERT_THAT(RetryEINTR(recvmsg)(s_, &msg, 0), SyscallSucceedsWithValue(0));
+ ASSERT_THAT(RetryEINTR(recvmsg)(bind_.get(), &msg, 0),
+ SyscallSucceedsWithValue(0));
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
ASSERT_NE(cmsg, nullptr);
@@ -1296,36 +1347,37 @@ TEST_P(UdpSocketTest, SoTimestamp) {
ASSERT_TRUE(tv.tv_sec != 0 || tv.tv_usec != 0);
// There should be nothing to get via ioctl.
- ASSERT_THAT(ioctl(s_, SIOCGSTAMP, &tv), SyscallFailsWithErrno(ENOENT));
+ ASSERT_THAT(ioctl(bind_.get(), SIOCGSTAMP, &tv),
+ SyscallFailsWithErrno(ENOENT));
}
TEST_P(UdpSocketTest, WriteShutdownNotConnected) {
- EXPECT_THAT(shutdown(s_, SHUT_WR), SyscallFailsWithErrno(ENOTCONN));
+ EXPECT_THAT(shutdown(bind_.get(), SHUT_WR), SyscallFailsWithErrno(ENOTCONN));
}
TEST_P(UdpSocketTest, TimestampIoctl) {
// TODO(gvisor.dev/issue/1202): ioctl() is not supported by hostinet.
SKIP_IF(IsRunningWithHostinet());
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(t_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
char buf[3];
- // Send packet from t_ to s_.
- ASSERT_THAT(RetryEINTR(write)(t_, buf, sizeof(buf)),
+ // Send packet from sock to bind_.
+ ASSERT_THAT(RetryEINTR(write)(sock_.get(), buf, sizeof(buf)),
SyscallSucceedsWithValue(sizeof(buf)));
- struct pollfd pfd = {s_, POLLIN, 0};
+ struct pollfd pfd = {bind_.get(), POLLIN, 0};
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000),
SyscallSucceedsWithValue(1));
// There should be no control messages.
char recv_buf[sizeof(buf)];
- ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(s_, recv_buf, sizeof(recv_buf)));
+ ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(bind_.get(), recv_buf, sizeof(recv_buf)));
// A nonzero timeval should be available via ioctl.
struct timeval tv = {};
- ASSERT_THAT(ioctl(s_, SIOCGSTAMP, &tv), SyscallSucceeds());
+ ASSERT_THAT(ioctl(bind_.get(), SIOCGSTAMP, &tv), SyscallSucceeds());
ASSERT_TRUE(tv.tv_sec != 0 || tv.tv_usec != 0);
}
@@ -1333,11 +1385,12 @@ TEST_P(UdpSocketTest, TimestampIoctlNothingRead) {
// TODO(gvisor.dev/issue/1202): ioctl() is not supported by hostinet.
SKIP_IF(IsRunningWithHostinet());
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(t_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
struct timeval tv = {};
- ASSERT_THAT(ioctl(s_, SIOCGSTAMP, &tv), SyscallFailsWithErrno(ENOENT));
+ ASSERT_THAT(ioctl(sock_.get(), SIOCGSTAMP, &tv),
+ SyscallFailsWithErrno(ENOENT));
}
// Test that the timestamp accessed via SIOCGSTAMP is still accessible after
@@ -1347,33 +1400,35 @@ TEST_P(UdpSocketTest, TimestampIoctlPersistence) {
// supported by hostinet.
SKIP_IF(IsRunningWithHostinet());
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(t_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
char buf[3];
- // Send packet from t_ to s_.
- ASSERT_THAT(RetryEINTR(write)(t_, buf, sizeof(buf)),
+ // Send packet from sock to bind_.
+ ASSERT_THAT(RetryEINTR(write)(sock_.get(), buf, sizeof(buf)),
SyscallSucceedsWithValue(sizeof(buf)));
- ASSERT_THAT(RetryEINTR(write)(t_, buf, 0), SyscallSucceedsWithValue(0));
+ ASSERT_THAT(RetryEINTR(write)(sock_.get(), buf, 0),
+ SyscallSucceedsWithValue(0));
- struct pollfd pfd = {s_, POLLIN, 0};
+ struct pollfd pfd = {bind_.get(), POLLIN, 0};
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000),
SyscallSucceedsWithValue(1));
// There should be no control messages.
char recv_buf[sizeof(buf)];
- ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(s_, recv_buf, sizeof(recv_buf)));
+ ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(bind_.get(), recv_buf, sizeof(recv_buf)));
// A nonzero timeval should be available via ioctl.
struct timeval tv = {};
- ASSERT_THAT(ioctl(s_, SIOCGSTAMP, &tv), SyscallSucceeds());
+ ASSERT_THAT(ioctl(bind_.get(), SIOCGSTAMP, &tv), SyscallSucceeds());
ASSERT_TRUE(tv.tv_sec != 0 || tv.tv_usec != 0);
// Enable SO_TIMESTAMP and send a message.
int v = 1;
- EXPECT_THAT(setsockopt(s_, SOL_SOCKET, SO_TIMESTAMP, &v, sizeof(v)),
+ EXPECT_THAT(setsockopt(bind_.get(), SOL_SOCKET, SO_TIMESTAMP, &v, sizeof(v)),
SyscallSucceeds());
- ASSERT_THAT(RetryEINTR(write)(t_, buf, 0), SyscallSucceedsWithValue(0));
+ ASSERT_THAT(RetryEINTR(write)(sock_.get(), buf, 0),
+ SyscallSucceedsWithValue(0));
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000),
SyscallSucceedsWithValue(1));
@@ -1386,13 +1441,14 @@ TEST_P(UdpSocketTest, TimestampIoctlPersistence) {
msg.msg_iovlen = 1;
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
- ASSERT_THAT(RetryEINTR(recvmsg)(s_, &msg, 0), SyscallSucceedsWithValue(0));
+ ASSERT_THAT(RetryEINTR(recvmsg)(bind_.get(), &msg, 0),
+ SyscallSucceedsWithValue(0));
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
ASSERT_NE(cmsg, nullptr);
// The ioctl should return the exact same values as before.
struct timeval tv2 = {};
- ASSERT_THAT(ioctl(s_, SIOCGSTAMP, &tv2), SyscallSucceeds());
+ ASSERT_THAT(ioctl(bind_.get(), SIOCGSTAMP, &tv2), SyscallSucceeds());
ASSERT_EQ(tv.tv_sec, tv2.tv_sec);
ASSERT_EQ(tv.tv_usec, tv2.tv_usec);
}
@@ -1401,8 +1457,8 @@ TEST_P(UdpSocketTest, TimestampIoctlPersistence) {
// outgoing packets, and that a receiving socket with IP_RECVTOS or
// IPV6_RECVTCLASS will create the corresponding control message.
TEST_P(UdpSocketTest, SetAndReceiveTOS) {
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(t_, addr_[0], addrlen_), SyscallSucceeds());
+ ASSERT_NO_ERRNO(BindLoopback());
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
// Allow socket to receive control message.
int recv_level = SOL_IP;
@@ -1411,9 +1467,9 @@ TEST_P(UdpSocketTest, SetAndReceiveTOS) {
recv_level = SOL_IPV6;
recv_type = IPV6_RECVTCLASS;
}
- ASSERT_THAT(
- setsockopt(s_, recv_level, recv_type, &kSockOptOn, sizeof(kSockOptOn)),
- SyscallSucceeds());
+ ASSERT_THAT(setsockopt(bind_.get(), recv_level, recv_type, &kSockOptOn,
+ sizeof(kSockOptOn)),
+ SyscallSucceeds());
// Set socket TOS.
int sent_level = recv_level;
@@ -1422,9 +1478,9 @@ TEST_P(UdpSocketTest, SetAndReceiveTOS) {
sent_type = IPV6_TCLASS;
}
int sent_tos = IPTOS_LOWDELAY; // Choose some TOS value.
- ASSERT_THAT(
- setsockopt(t_, sent_level, sent_type, &sent_tos, sizeof(sent_tos)),
- SyscallSucceeds());
+ ASSERT_THAT(setsockopt(sock_.get(), sent_level, sent_type, &sent_tos,
+ sizeof(sent_tos)),
+ SyscallSucceeds());
// Prepare message to send.
constexpr size_t kDataLength = 1024;
@@ -1436,7 +1492,7 @@ TEST_P(UdpSocketTest, SetAndReceiveTOS) {
sent_msg.msg_iov = &sent_iov;
sent_msg.msg_iovlen = 1;
- ASSERT_THAT(RetryEINTR(sendmsg)(t_, &sent_msg, 0),
+ ASSERT_THAT(RetryEINTR(sendmsg)(sock_.get(), &sent_msg, 0),
SyscallSucceedsWithValue(kDataLength));
// Receive message.
@@ -1454,7 +1510,7 @@ TEST_P(UdpSocketTest, SetAndReceiveTOS) {
std::vector<char> received_cmsgbuf(CMSG_SPACE(cmsg_data_len));
received_msg.msg_control = &received_cmsgbuf[0];
received_msg.msg_controllen = received_cmsgbuf.size();
- ASSERT_THAT(RetryEINTR(recvmsg)(s_, &received_msg, 0),
+ ASSERT_THAT(RetryEINTR(recvmsg)(bind_.get(), &received_msg, 0),
SyscallSucceedsWithValue(kDataLength));
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&received_msg);
@@ -1473,8 +1529,9 @@ TEST_P(UdpSocketTest, SetAndReceiveTOS) {
TEST_P(UdpSocketTest, SendAndReceiveTOS) {
// TODO(b/146661005): Setting TOS via cmsg not supported for netstack.
SKIP_IF(IsRunningOnGvisor() && !IsRunningWithHostinet());
- ASSERT_THAT(bind(s_, addr_[0], addrlen_), SyscallSucceeds());
- ASSERT_THAT(connect(t_, addr_[0], addrlen_), SyscallSucceeds());
+
+ ASSERT_NO_ERRNO(BindLoopback());
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
// Allow socket to receive control message.
int recv_level = SOL_IP;
@@ -1484,9 +1541,9 @@ TEST_P(UdpSocketTest, SendAndReceiveTOS) {
recv_type = IPV6_RECVTCLASS;
}
int recv_opt = kSockOptOn;
- ASSERT_THAT(
- setsockopt(s_, recv_level, recv_type, &recv_opt, sizeof(recv_opt)),
- SyscallSucceeds());
+ ASSERT_THAT(setsockopt(bind_.get(), recv_level, recv_type, &recv_opt,
+ sizeof(recv_opt)),
+ SyscallSucceeds());
// Prepare message to send.
constexpr size_t kDataLength = 1024;
@@ -1517,7 +1574,7 @@ TEST_P(UdpSocketTest, SendAndReceiveTOS) {
sent_cmsg->cmsg_type = sent_type;
*(int8_t*)CMSG_DATA(sent_cmsg) = sent_tos;
- ASSERT_THAT(RetryEINTR(sendmsg)(t_, &sent_msg, 0),
+ ASSERT_THAT(RetryEINTR(sendmsg)(sock_.get(), &sent_msg, 0),
SyscallSucceedsWithValue(kDataLength));
// Receive message.
@@ -1531,7 +1588,7 @@ TEST_P(UdpSocketTest, SendAndReceiveTOS) {
std::vector<char> received_cmsgbuf(CMSG_SPACE(cmsg_data_len));
received_msg.msg_control = &received_cmsgbuf[0];
received_msg.msg_controllen = CMSG_LEN(cmsg_data_len);
- ASSERT_THAT(RetryEINTR(recvmsg)(s_, &received_msg, 0),
+ ASSERT_THAT(RetryEINTR(recvmsg)(bind_.get(), &received_msg, 0),
SyscallSucceedsWithValue(kDataLength));
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&received_msg);
@@ -1543,5 +1600,182 @@ TEST_P(UdpSocketTest, SendAndReceiveTOS) {
memcpy(&received_tos, CMSG_DATA(cmsg), sizeof(received_tos));
EXPECT_EQ(received_tos, sent_tos);
}
+
+TEST_P(UdpSocketTest, RecvBufLimitsEmptyRcvBuf) {
+ // Discover minimum buffer size by setting it to zero.
+ constexpr int kRcvBufSz = 0;
+ ASSERT_THAT(setsockopt(bind_.get(), SOL_SOCKET, SO_RCVBUF, &kRcvBufSz,
+ sizeof(kRcvBufSz)),
+ SyscallSucceeds());
+
+ int min = 0;
+ socklen_t min_len = sizeof(min);
+ ASSERT_THAT(getsockopt(bind_.get(), SOL_SOCKET, SO_RCVBUF, &min, &min_len),
+ SyscallSucceeds());
+
+ // Bind bind_ to loopback.
+ ASSERT_NO_ERRNO(BindLoopback());
+
+ {
+ // Send data of size min and verify that it's received.
+ std::vector<char> buf(min);
+ RandomizeBuffer(buf.data(), buf.size());
+ ASSERT_THAT(
+ sendto(sock_.get(), buf.data(), buf.size(), 0, bind_addr_, addrlen_),
+ SyscallSucceedsWithValue(buf.size()));
+ std::vector<char> received(buf.size());
+ EXPECT_THAT(
+ recv(bind_.get(), received.data(), received.size(), MSG_DONTWAIT),
+ SyscallSucceedsWithValue(received.size()));
+ }
+
+ {
+ // Send data of size min + 1 and verify that its received. Both linux and
+ // Netstack accept a dgram that exceeds rcvBuf limits if the receive buffer
+ // is currently empty.
+ std::vector<char> buf(min + 1);
+ RandomizeBuffer(buf.data(), buf.size());
+ ASSERT_THAT(
+ sendto(sock_.get(), buf.data(), buf.size(), 0, bind_addr_, addrlen_),
+ SyscallSucceedsWithValue(buf.size()));
+
+ std::vector<char> received(buf.size());
+ EXPECT_THAT(
+ recv(bind_.get(), received.data(), received.size(), MSG_DONTWAIT),
+ SyscallSucceedsWithValue(received.size()));
+ }
+}
+
+// Test that receive buffer limits are enforced.
+TEST_P(UdpSocketTest, RecvBufLimits) {
+ // Bind s_ to loopback.
+ ASSERT_NO_ERRNO(BindLoopback());
+
+ int min = 0;
+ {
+ // Discover minimum buffer size by trying to set it to zero.
+ constexpr int kRcvBufSz = 0;
+ ASSERT_THAT(setsockopt(bind_.get(), SOL_SOCKET, SO_RCVBUF, &kRcvBufSz,
+ sizeof(kRcvBufSz)),
+ SyscallSucceeds());
+
+ socklen_t min_len = sizeof(min);
+ ASSERT_THAT(getsockopt(bind_.get(), SOL_SOCKET, SO_RCVBUF, &min, &min_len),
+ SyscallSucceeds());
+ }
+
+ // Now set the limit to min * 4.
+ int new_rcv_buf_sz = min * 4;
+ if (!IsRunningOnGvisor() || IsRunningWithHostinet()) {
+ // Linux doubles the value specified so just set to min * 2.
+ new_rcv_buf_sz = min * 2;
+ }
+
+ ASSERT_THAT(setsockopt(bind_.get(), SOL_SOCKET, SO_RCVBUF, &new_rcv_buf_sz,
+ sizeof(new_rcv_buf_sz)),
+ SyscallSucceeds());
+ int rcv_buf_sz = 0;
+ {
+ socklen_t rcv_buf_len = sizeof(rcv_buf_sz);
+ ASSERT_THAT(getsockopt(bind_.get(), SOL_SOCKET, SO_RCVBUF, &rcv_buf_sz,
+ &rcv_buf_len),
+ SyscallSucceeds());
+ }
+
+ {
+ std::vector<char> buf(min);
+ RandomizeBuffer(buf.data(), buf.size());
+
+ ASSERT_THAT(
+ sendto(sock_.get(), buf.data(), buf.size(), 0, bind_addr_, addrlen_),
+ SyscallSucceedsWithValue(buf.size()));
+ ASSERT_THAT(
+ sendto(sock_.get(), buf.data(), buf.size(), 0, bind_addr_, addrlen_),
+ SyscallSucceedsWithValue(buf.size()));
+ ASSERT_THAT(
+ sendto(sock_.get(), buf.data(), buf.size(), 0, bind_addr_, addrlen_),
+ SyscallSucceedsWithValue(buf.size()));
+ ASSERT_THAT(
+ sendto(sock_.get(), buf.data(), buf.size(), 0, bind_addr_, addrlen_),
+ SyscallSucceedsWithValue(buf.size()));
+ int sent = 4;
+ if (IsRunningOnGvisor() && !IsRunningWithHostinet()) {
+ // Linux seems to drop the 4th packet even though technically it should
+ // fit in the receive buffer.
+ ASSERT_THAT(
+ sendto(sock_.get(), buf.data(), buf.size(), 0, bind_addr_, addrlen_),
+ SyscallSucceedsWithValue(buf.size()));
+ sent++;
+ }
+
+ for (int i = 0; i < sent - 1; i++) {
+ // Receive the data.
+ std::vector<char> received(buf.size());
+ EXPECT_THAT(
+ recv(bind_.get(), received.data(), received.size(), MSG_DONTWAIT),
+ SyscallSucceedsWithValue(received.size()));
+ EXPECT_EQ(memcmp(buf.data(), received.data(), buf.size()), 0);
+ }
+
+ // The last receive should fail with EAGAIN as the last packet should have
+ // been dropped due to lack of space in the receive buffer.
+ std::vector<char> received(buf.size());
+ EXPECT_THAT(
+ recv(bind_.get(), received.data(), received.size(), MSG_DONTWAIT),
+ SyscallFailsWithErrno(EAGAIN));
+ }
+}
+
+#ifndef __fuchsia__
+
+// TODO(gvisor.dev/2746): Support SO_ATTACH_FILTER/SO_DETACH_FILTER.
+// gVisor currently silently ignores attaching a filter.
+TEST_P(UdpSocketTest, SetSocketDetachFilter) {
+ // Program generated using sudo tcpdump -i lo udp and port 1234 -dd
+ struct sock_filter code[] = {
+ {0x28, 0, 0, 0x0000000c}, {0x15, 0, 6, 0x000086dd},
+ {0x30, 0, 0, 0x00000014}, {0x15, 0, 15, 0x00000011},
+ {0x28, 0, 0, 0x00000036}, {0x15, 12, 0, 0x000004d2},
+ {0x28, 0, 0, 0x00000038}, {0x15, 10, 11, 0x000004d2},
+ {0x15, 0, 10, 0x00000800}, {0x30, 0, 0, 0x00000017},
+ {0x15, 0, 8, 0x00000011}, {0x28, 0, 0, 0x00000014},
+ {0x45, 6, 0, 0x00001fff}, {0xb1, 0, 0, 0x0000000e},
+ {0x48, 0, 0, 0x0000000e}, {0x15, 2, 0, 0x000004d2},
+ {0x48, 0, 0, 0x00000010}, {0x15, 0, 1, 0x000004d2},
+ {0x6, 0, 0, 0x00040000}, {0x6, 0, 0, 0x00000000},
+ };
+ struct sock_fprog bpf = {
+ .len = ABSL_ARRAYSIZE(code),
+ .filter = code,
+ };
+ ASSERT_THAT(
+ setsockopt(sock_.get(), SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)),
+ SyscallSucceeds());
+
+ constexpr int val = 0;
+ ASSERT_THAT(
+ setsockopt(sock_.get(), SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val)),
+ SyscallSucceeds());
+}
+
+TEST_P(UdpSocketTest, SetSocketDetachFilterNoInstalledFilter) {
+ // TODO(gvisor.dev/2746): Support SO_ATTACH_FILTER/SO_DETACH_FILTER.
+ SKIP_IF(IsRunningOnGvisor());
+ constexpr int val = 0;
+ ASSERT_THAT(
+ setsockopt(sock_.get(), SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val)),
+ SyscallFailsWithErrno(ENOENT));
+}
+
+TEST_P(UdpSocketTest, GetSocketDetachFilter) {
+ int val = 0;
+ socklen_t val_len = sizeof(val);
+ ASSERT_THAT(
+ getsockopt(sock_.get(), SOL_SOCKET, SO_DETACH_FILTER, &val, &val_len),
+ SyscallFailsWithErrno(ENOPROTOOPT));
+}
+
+#endif // __fuchsia__
+
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/udp_socket_test_cases.h b/test/syscalls/linux/udp_socket_test_cases.h
index 2fd79d99e..f7e25c805 100644
--- a/test/syscalls/linux/udp_socket_test_cases.h
+++ b/test/syscalls/linux/udp_socket_test_cases.h
@@ -15,8 +15,12 @@
#ifndef THIRD_PARTY_GOLANG_GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_H_
#define THIRD_PARTY_GOLANG_GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_H_
+#include <sys/socket.h>
+
#include "gtest/gtest.h"
#include "test/syscalls/linux/socket_test_util.h"
+#include "test/util/file_descriptor.h"
+#include "test/util/posix_error.h"
namespace gvisor {
namespace testing {
@@ -32,42 +36,46 @@ class UdpSocketTest
// Creates two sockets that will be used by test cases.
void SetUp() override;
- // Closes the sockets created by SetUp().
- void TearDown() override {
- EXPECT_THAT(close(s_), SyscallSucceeds());
- EXPECT_THAT(close(t_), SyscallSucceeds());
+ // Binds the socket bind_ to the loopback and updates bind_addr_.
+ PosixError BindLoopback();
- for (size_t i = 0; i < ABSL_ARRAYSIZE(ports_); ++i) {
- ASSERT_NO_ERRNO(FreeAvailablePort(ports_[i]));
- }
- }
+ // Binds the socket bind_ to Any and updates bind_addr_.
+ PosixError BindAny();
- // First UDP socket.
- int s_;
+ // Binds given socket to address addr and updates.
+ PosixError BindSocket(int socket, struct sockaddr* addr);
- // Second UDP socket.
- int t_;
+ // Return initialized Any address to port 0.
+ struct sockaddr_storage InetAnyAddr();
- // The length of the socket address.
- socklen_t addrlen_;
+ // Return initialized Loopback address to port 0.
+ struct sockaddr_storage InetLoopbackAddr();
- // Initialized address pointing to loopback and port TestPort+i.
- struct sockaddr* addr_[3];
+ // Disconnects socket sockfd.
+ void Disconnect(int sockfd);
- // Initialize "any" address.
- struct sockaddr* anyaddr_;
+ // Get family for the test.
+ int GetFamily();
- // Used ports.
- int ports_[3];
+ // Socket used by Bind methods
+ FileDescriptor bind_;
- private:
- // Storage for the loopback addresses.
- struct sockaddr_storage addr_storage_[3];
+ // Second socket used for tests.
+ FileDescriptor sock_;
- // Storage for the "any" address.
- struct sockaddr_storage anyaddr_storage_;
-};
+ // Address for bind_ socket.
+ struct sockaddr* bind_addr_;
+ // Initialized to the length based on GetFamily().
+ socklen_t addrlen_;
+
+ // Storage for bind_addr_.
+ struct sockaddr_storage bind_addr_storage_;
+
+ private:
+ // Helper to initialize addrlen_ for the test case.
+ socklen_t GetAddrLength();
+};
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/xattr.cc b/test/syscalls/linux/xattr.cc
index 3231732ec..cbcf08451 100644
--- a/test/syscalls/linux/xattr.cc
+++ b/test/syscalls/linux/xattr.cc
@@ -73,9 +73,10 @@ TEST_F(XattrTest, XattrLargeName) {
std::string name = "user.";
name += std::string(XATTR_NAME_MAX - name.length(), 'a');
- // An xattr should be whitelisted before it can be accessed--do not allow
- // arbitrary xattrs to be read/written in gVisor.
if (!IsRunningOnGvisor()) {
+ // In gVisor, access to xattrs is controlled with an explicit list of
+ // allowed names. This name isn't going to be configured to allow access, so
+ // don't test it.
EXPECT_THAT(setxattr(path, name.c_str(), nullptr, 0, /*flags=*/0),
SyscallSucceeds());
EXPECT_THAT(getxattr(path, name.c_str(), nullptr, 0),
diff --git a/test/util/fs_util.cc b/test/util/fs_util.cc
index 052781445..5418948fe 100644
--- a/test/util/fs_util.cc
+++ b/test/util/fs_util.cc
@@ -125,12 +125,12 @@ PosixErrorOr<struct stat> Fstat(int fd) {
PosixErrorOr<bool> Exists(absl::string_view path) {
struct stat stat_buf;
- int res = stat(std::string(path).c_str(), &stat_buf);
+ int res = lstat(std::string(path).c_str(), &stat_buf);
if (res < 0) {
if (errno == ENOENT) {
return false;
}
- return PosixError(errno, absl::StrCat("stat ", path));
+ return PosixError(errno, absl::StrCat("lstat ", path));
}
return true;
}
diff --git a/test/util/fs_util.h b/test/util/fs_util.h
index caf19b24d..8cdac23a1 100644
--- a/test/util/fs_util.h
+++ b/test/util/fs_util.h
@@ -44,9 +44,14 @@ PosixErrorOr<std::string> GetCWD();
// can't be determined.
PosixErrorOr<bool> Exists(absl::string_view path);
-// Returns a stat structure for the given path or an error.
+// Returns a stat structure for the given path or an error. If the path
+// represents a symlink, it will be traversed.
PosixErrorOr<struct stat> Stat(absl::string_view path);
+// Returns a stat structure for the given path or an error. If the path
+// represents a symlink, it will not be traversed.
+PosixErrorOr<struct stat> Lstat(absl::string_view path);
+
// Returns a stat struct for the given fd.
PosixErrorOr<struct stat> Fstat(int fd);
diff --git a/test/util/temp_path.cc b/test/util/temp_path.cc
index 9c10b6674..e1bdee7fd 100644
--- a/test/util/temp_path.cc
+++ b/test/util/temp_path.cc
@@ -56,7 +56,7 @@ void TryDeleteRecursively(std::string const& path) {
if (undeleted_dirs || undeleted_files || !status.ok()) {
std::cerr << path << ": failed to delete " << undeleted_dirs
<< " directories and " << undeleted_files
- << " files: " << status;
+ << " files: " << status << std::endl;
}
}
}
diff --git a/test/util/test_util.cc b/test/util/test_util.cc
index b20758626..d0c1d6426 100644
--- a/test/util/test_util.cc
+++ b/test/util/test_util.cc
@@ -40,15 +40,15 @@
namespace gvisor {
namespace testing {
-#define TEST_ON_GVISOR "TEST_ON_GVISOR"
-#define GVISOR_NETWORK "GVISOR_NETWORK"
-#define GVISOR_VFS "GVISOR_VFS"
+constexpr char kGvisorNetwork[] = "GVISOR_NETWORK";
+constexpr char kGvisorVfs[] = "GVISOR_VFS";
+constexpr char kFuseEnabled[] = "FUSE_ENABLED";
bool IsRunningOnGvisor() { return GvisorPlatform() != Platform::kNative; }
const std::string GvisorPlatform() {
// Set by runner.go.
- const char* env = getenv(TEST_ON_GVISOR);
+ const char* env = getenv(kTestOnGvisor);
if (!env) {
return Platform::kNative;
}
@@ -56,12 +56,12 @@ const std::string GvisorPlatform() {
}
bool IsRunningWithHostinet() {
- const char* env = getenv(GVISOR_NETWORK);
+ const char* env = getenv(kGvisorNetwork);
return env && strcmp(env, "host") == 0;
}
bool IsRunningWithVFS1() {
- const char* env = getenv(GVISOR_VFS);
+ const char* env = getenv(kGvisorVfs);
if (env == nullptr) {
// If not set, it's running on Linux.
return false;
@@ -69,6 +69,11 @@ bool IsRunningWithVFS1() {
return strcmp(env, "VFS1") == 0;
}
+bool IsFUSEEnabled() {
+ const char* env = getenv(kFuseEnabled);
+ return env && strcmp(env, "TRUE") == 0;
+}
+
// Inline cpuid instruction. Preserve %ebx/%rbx register. In PIC compilations
// %ebx contains the address of the global offset table. %rbx is occasionally
// used to address stack variables in presence of dynamic allocas.
diff --git a/test/util/test_util.h b/test/util/test_util.h
index 8e3245b27..373c54f32 100644
--- a/test/util/test_util.h
+++ b/test/util/test_util.h
@@ -195,6 +195,8 @@
namespace gvisor {
namespace testing {
+constexpr char kTestOnGvisor[] = "TEST_ON_GVISOR";
+
// TestInit must be called prior to RUN_ALL_TESTS.
//
// This parses all arguments and adjusts argc and argv appropriately.
@@ -215,12 +217,15 @@ namespace Platform {
constexpr char kNative[] = "native";
constexpr char kPtrace[] = "ptrace";
constexpr char kKVM[] = "kvm";
+constexpr char kFuchsia[] = "fuchsia";
} // namespace Platform
bool IsRunningOnGvisor();
const std::string GvisorPlatform();
bool IsRunningWithHostinet();
+// TODO(gvisor.dev/issue/1624): Delete once VFS1 is gone.
bool IsRunningWithVFS1();
+bool IsFUSEEnabled();
#ifdef __linux__
void SetupGvisorDeathTest();
@@ -563,6 +568,25 @@ ssize_t ApplyFileIoSyscall(F const& f, size_t const count) {
} // namespace internal
+inline PosixErrorOr<std::string> ReadAllFd(int fd) {
+ std::string all;
+ all.reserve(128 * 1024); // arbitrary.
+
+ std::vector<char> buffer(16 * 1024);
+ for (;;) {
+ auto const bytes = RetryEINTR(read)(fd, buffer.data(), buffer.size());
+ if (bytes < 0) {
+ return PosixError(errno, "file read");
+ }
+ if (bytes == 0) {
+ return std::move(all);
+ }
+ if (bytes > 0) {
+ all.append(buffer.data(), bytes);
+ }
+ }
+}
+
inline ssize_t ReadFd(int fd, void* buf, size_t count) {
return internal::ApplyFileIoSyscall(
[&](size_t completed) {