summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/benchmarks/BUILD11
-rw-r--r--test/benchmarks/base/BUILD47
-rw-r--r--test/benchmarks/base/base.go81
-rw-r--r--test/benchmarks/base/size_test.go92
-rw-r--r--test/benchmarks/base/startup_test.go75
-rw-r--r--test/benchmarks/base/sysbench_test.go5
-rw-r--r--test/benchmarks/database/BUILD13
-rw-r--r--test/benchmarks/database/database.go15
-rw-r--r--test/benchmarks/database/redis_test.go33
-rw-r--r--test/benchmarks/defs.bzl14
-rw-r--r--test/benchmarks/fs/BUILD34
-rw-r--r--test/benchmarks/fs/bazel_test.go29
-rw-r--r--test/benchmarks/fs/fio_test.go11
-rw-r--r--test/benchmarks/fs/fs.go31
-rw-r--r--test/benchmarks/harness/harness.go15
-rw-r--r--test/benchmarks/harness/util.go9
-rw-r--r--test/benchmarks/media/BUILD10
-rw-r--r--test/benchmarks/media/ffmpeg_test.go12
-rw-r--r--test/benchmarks/media/media.go15
-rw-r--r--test/benchmarks/ml/BUILD10
-rw-r--r--test/benchmarks/ml/ml.go15
-rw-r--r--test/benchmarks/ml/tensorflow_test.go12
-rw-r--r--test/benchmarks/network/BUILD74
-rw-r--r--test/benchmarks/network/httpd_test.go71
-rw-r--r--test/benchmarks/network/iperf_test.go27
-rw-r--r--test/benchmarks/network/network.go69
-rw-r--r--test/benchmarks/network/nginx_test.go75
-rw-r--r--test/benchmarks/network/node_test.go16
-rw-r--r--test/benchmarks/network/ruby_test.go16
-rw-r--r--test/benchmarks/network/static_server.go87
-rw-r--r--test/benchmarks/tcp/tcp_proxy.go3
-rw-r--r--test/benchmarks/tools/iperf.go2
-rw-r--r--test/benchmarks/tools/redis.go6
-rw-r--r--test/cmd/test_app/fds.go5
-rw-r--r--test/e2e/integration_test.go49
-rw-r--r--test/fuse/BUILD5
-rw-r--r--test/fuse/linux/BUILD12
-rw-r--r--test/fuse/linux/mount_test.cc41
-rw-r--r--test/fuse/linux/stat_test.cc10
-rw-r--r--test/iptables/README.md34
-rw-r--r--test/iptables/filter_output.go19
-rw-r--r--test/iptables/iptables_test.go4
-rw-r--r--test/iptables/nat.go15
-rw-r--r--test/packetdrill/BUILD9
-rw-r--r--test/packetdrill/defs.bzl6
-rwxr-xr-xtest/packetdrill/packetdrill_test.sh13
-rw-r--r--test/packetimpact/netdevs/netdevs.go25
-rw-r--r--test/packetimpact/runner/BUILD1
-rw-r--r--test/packetimpact/runner/defs.bzl28
-rw-r--r--test/packetimpact/runner/dut.go447
-rw-r--r--test/packetimpact/runner/packetimpact_test.go2
-rw-r--r--test/packetimpact/testbench/BUILD1
-rw-r--r--test/packetimpact/testbench/connections.go201
-rw-r--r--test/packetimpact/testbench/dut.go36
-rw-r--r--test/packetimpact/testbench/dut_client.go2
-rw-r--r--test/packetimpact/testbench/layers.go151
-rw-r--r--test/packetimpact/testbench/rawsockets.go33
-rw-r--r--test/packetimpact/testbench/testbench.go150
-rw-r--r--test/packetimpact/tests/BUILD47
-rw-r--r--test/packetimpact/tests/fin_wait2_timeout_test.go5
-rw-r--r--test/packetimpact/tests/icmpv6_param_problem_test.go5
-rw-r--r--test/packetimpact/tests/ipv4_fragment_reassembly_test.go191
-rw-r--r--test/packetimpact/tests/ipv4_id_uniqueness_test.go6
-rw-r--r--test/packetimpact/tests/ipv6_fragment_icmp_error_test.go359
-rw-r--r--test/packetimpact/tests/ipv6_fragment_reassembly_test.go270
-rw-r--r--test/packetimpact/tests/ipv6_unknown_options_action_test.go5
-rw-r--r--test/packetimpact/tests/tcp_cork_mss_test.go5
-rw-r--r--test/packetimpact/tests/tcp_handshake_window_size_test.go5
-rw-r--r--test/packetimpact/tests/tcp_linger_test.go6
-rw-r--r--test/packetimpact/tests/tcp_network_unreachable_test.go23
-rw-r--r--test/packetimpact/tests/tcp_noaccept_close_rst_test.go5
-rw-r--r--test/packetimpact/tests/tcp_outside_the_window_test.go5
-rw-r--r--test/packetimpact/tests/tcp_paws_mechanism_test.go5
-rw-r--r--test/packetimpact/tests/tcp_queue_receive_in_syn_sent_test.go8
-rw-r--r--test/packetimpact/tests/tcp_queue_send_in_syn_sent_test.go8
-rw-r--r--test/packetimpact/tests/tcp_rcv_buf_space_test.go5
-rw-r--r--test/packetimpact/tests/tcp_reordering_test.go45
-rw-r--r--test/packetimpact/tests/tcp_retransmits_test.go5
-rw-r--r--test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go8
-rw-r--r--test/packetimpact/tests/tcp_synrcvd_reset_test.go5
-rw-r--r--test/packetimpact/tests/tcp_synsent_reset_test.go39
-rw-r--r--test/packetimpact/tests/tcp_timewait_reset_test.go5
-rw-r--r--test/packetimpact/tests/tcp_unacc_seq_ack_test.go11
-rw-r--r--test/packetimpact/tests/tcp_user_timeout_test.go5
-rw-r--r--test/packetimpact/tests/tcp_window_shrink_test.go5
-rw-r--r--test/packetimpact/tests/tcp_zero_receive_window_test.go115
-rw-r--r--test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go5
-rw-r--r--test/packetimpact/tests/tcp_zero_window_probe_test.go5
-rw-r--r--test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go5
-rw-r--r--test/packetimpact/tests/udp_any_addr_recv_unicast_test.go7
-rw-r--r--test/packetimpact/tests/udp_discard_mcast_source_addr_test.go12
-rw-r--r--test/packetimpact/tests/udp_icmp_error_propagation_test.go10
-rw-r--r--test/packetimpact/tests/udp_recv_mcast_bcast_test.go31
-rw-r--r--test/packetimpact/tests/udp_send_recv_dgram_test.go17
-rw-r--r--test/perf/BUILD3
-rw-r--r--test/root/BUILD14
-rw-r--r--test/root/crictl_test.go4
-rw-r--r--test/root/oom_score_adj_test.go12
-rw-r--r--test/runner/defs.bzl51
-rw-r--r--test/runner/runner.go6
-rw-r--r--test/runtimes/BUILD12
-rw-r--r--test/runtimes/exclude/java11.csv1
-rw-r--r--test/runtimes/exclude/nodejs12.4.0.csv41
-rw-r--r--test/runtimes/exclude/php7.3.6.csv6
-rw-r--r--test/runtimes/exclude/python3.7.3.csv2
-rw-r--r--test/runtimes/proctor/main.go28
-rw-r--r--test/runtimes/runner/lib/lib.go27
-rw-r--r--test/syscalls/BUILD71
-rw-r--r--test/syscalls/linux/BUILD111
-rw-r--r--test/syscalls/linux/chown.cc13
-rw-r--r--test/syscalls/linux/epoll.cc7
-rw-r--r--test/syscalls/linux/exceptions.cc10
-rw-r--r--test/syscalls/linux/fcntl.cc486
-rw-r--r--test/syscalls/linux/kill.cc6
-rw-r--r--test/syscalls/linux/mknod.cc30
-rw-r--r--test/syscalls/linux/mmap.cc6
-rw-r--r--test/syscalls/linux/mount.cc38
-rw-r--r--test/syscalls/linux/open_create.cc8
-rw-r--r--test/syscalls/linux/packet_socket_raw.cc11
-rw-r--r--test/syscalls/linux/partial_bad_buffer.cc5
-rw-r--r--test/syscalls/linux/proc.cc438
-rw-r--r--test/syscalls/linux/proc_pid_smaps.cc2
-rw-r--r--test/syscalls/linux/ptrace.cc43
-rw-r--r--test/syscalls/linux/raw_socket.cc45
-rw-r--r--test/syscalls/linux/raw_socket_icmp.cc13
-rw-r--r--test/syscalls/linux/semaphore.cc327
-rw-r--r--test/syscalls/linux/sendfile.cc71
-rw-r--r--test/syscalls/linux/signalfd.cc2
-rw-r--r--test/syscalls/linux/socket_generic.cc51
-rw-r--r--test/syscalls/linux/socket_inet_loopback.cc86
-rw-r--r--test/syscalls/linux/socket_ip_udp_generic.cc69
-rw-r--r--test/syscalls/linux/socket_ip_unbound.cc18
-rw-r--r--test/syscalls/linux/socket_ip_unbound_netlink.cc104
-rw-r--r--test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.cc66
-rw-r--r--test/syscalls/linux/socket_ipv4_udp_unbound.cc166
-rw-r--r--test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc32
-rw-r--r--test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc28
-rw-r--r--test/syscalls/linux/socket_ipv6_udp_unbound.cc131
-rw-r--r--test/syscalls/linux/socket_ipv6_udp_unbound.h (renamed from test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h)13
-rw-r--r--test/syscalls/linux/socket_ipv6_udp_unbound_loopback.cc (renamed from test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking_test.cc)21
-rw-r--r--test/syscalls/linux/socket_netlink_route.cc63
-rw-r--r--test/syscalls/linux/socket_netlink_route_util.cc22
-rw-r--r--test/syscalls/linux/socket_netlink_route_util.h11
-rw-r--r--test/syscalls/linux/socket_test_util.cc16
-rw-r--r--test/syscalls/linux/socket_test_util.h9
-rw-r--r--test/syscalls/linux/socket_unix_stream.cc13
-rw-r--r--test/syscalls/linux/splice.cc55
-rw-r--r--test/syscalls/linux/stat.cc34
-rw-r--r--test/syscalls/linux/tcp_socket.cc629
-rw-r--r--test/syscalls/linux/timers.cc5
-rw-r--r--test/syscalls/linux/udp_socket.cc135
-rw-r--r--test/util/BUILD4
-rw-r--r--test/util/posix_error.cc2
-rw-r--r--test/util/posix_error.h34
-rw-r--r--test/util/save_util.cc52
-rw-r--r--test/util/save_util.h20
-rw-r--r--test/util/save_util_linux.cc22
-rw-r--r--test/util/save_util_other.cc8
-rw-r--r--test/util/signal_util.h2
-rw-r--r--test/util/test_util.h7
-rw-r--r--test/util/timer_util.h5
161 files changed, 5626 insertions, 1889 deletions
diff --git a/test/benchmarks/BUILD b/test/benchmarks/BUILD
new file mode 100644
index 000000000..faf310676
--- /dev/null
+++ b/test/benchmarks/BUILD
@@ -0,0 +1,11 @@
+load("//tools:defs.bzl", "bzl_library")
+
+package(licenses = ["notice"])
+
+bzl_library(
+ name = "defs_bzl",
+ srcs = ["defs.bzl"],
+ visibility = [
+ "//:sandbox",
+ ],
+)
diff --git a/test/benchmarks/base/BUILD b/test/benchmarks/base/BUILD
index 32c139204..697ab5837 100644
--- a/test/benchmarks/base/BUILD
+++ b/test/benchmarks/base/BUILD
@@ -1,4 +1,5 @@
-load("//tools:defs.bzl", "go_library", "go_test")
+load("//tools:defs.bzl", "go_library")
+load("//test/benchmarks:defs.bzl", "benchmark_test")
package(licenses = ["notice"])
@@ -8,23 +9,41 @@ go_library(
srcs = [
"base.go",
],
- deps = ["//test/benchmarks/harness"],
+ deps = [
+ "//pkg/test/dockerutil",
+ "//test/benchmarks/harness",
+ ],
)
-go_test(
- name = "base_test",
- size = "large",
- srcs = [
- "size_test.go",
- "startup_test.go",
- "sysbench_test.go",
+benchmark_test(
+ name = "startup_test",
+ size = "enormous",
+ srcs = ["startup_test.go"],
+ visibility = ["//:sandbox"],
+ deps = [
+ "//pkg/test/dockerutil",
+ "//test/benchmarks/base",
+ "//test/benchmarks/harness",
],
- library = ":base",
- tags = [
- # Requires docker and runsc to be configured before test runs.
- "manual",
- "local",
+)
+
+benchmark_test(
+ name = "size_test",
+ size = "enormous",
+ srcs = ["size_test.go"],
+ visibility = ["//:sandbox"],
+ deps = [
+ "//pkg/test/dockerutil",
+ "//test/benchmarks/base",
+ "//test/benchmarks/harness",
+ "//test/benchmarks/tools",
],
+)
+
+benchmark_test(
+ name = "sysbench_test",
+ size = "enormous",
+ srcs = ["sysbench_test.go"],
visibility = ["//:sandbox"],
deps = [
"//pkg/test/dockerutil",
diff --git a/test/benchmarks/base/base.go b/test/benchmarks/base/base.go
index 7bac52ff1..979564af9 100644
--- a/test/benchmarks/base/base.go
+++ b/test/benchmarks/base/base.go
@@ -12,20 +12,87 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Package base holds base performance benchmarks.
+// Package base holds utility methods common to the base tests.
package base
import (
- "os"
+ "context"
+ "net"
"testing"
+ "time"
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
"gvisor.dev/gvisor/test/benchmarks/harness"
)
-var testHarness harness.Harness
+// ServerArgs wraps args for startServers and runServerWorkload.
+type ServerArgs struct {
+ Machine harness.Machine
+ Port int
+ RunOpts dockerutil.RunOpts
+ Cmd []string
+}
+
+// StartServers starts b.N containers defined by 'runOpts' and 'cmd' and uses
+// 'machine' to check that each is up.
+func StartServers(ctx context.Context, b *testing.B, args ServerArgs) []*dockerutil.Container {
+ b.Helper()
+ servers := make([]*dockerutil.Container, 0, b.N)
+
+ // Create N servers and wait until each of them is serving.
+ for i := 0; i < b.N; i++ {
+ server := args.Machine.GetContainer(ctx, b)
+ servers = append(servers, server)
+ if err := server.Spawn(ctx, args.RunOpts, args.Cmd...); err != nil {
+ CleanUpContainers(ctx, servers)
+ b.Fatalf("failed to spawn node instance: %v", err)
+ }
+
+ // Get the container IP.
+ servingIP, err := server.FindIP(ctx, false)
+ if err != nil {
+ CleanUpContainers(ctx, servers)
+ b.Fatalf("failed to get ip from server: %v", err)
+ }
+
+ // Wait until the server is up.
+ if err := harness.WaitUntilServing(ctx, args.Machine, servingIP, args.Port); err != nil {
+ CleanUpContainers(ctx, servers)
+ b.Fatalf("failed to wait for serving")
+ }
+ }
+ return servers
+}
+
+// CleanUpContainers cleans up a slice of containers.
+func CleanUpContainers(ctx context.Context, containers []*dockerutil.Container) {
+ for _, c := range containers {
+ if c != nil {
+ c.CleanUp(ctx)
+ }
+ }
+}
+
+// RedisInstance returns a Redis container and its reachable IP.
+func RedisInstance(ctx context.Context, b *testing.B, machine harness.Machine) (*dockerutil.Container, net.IP) {
+ b.Helper()
+ // Spawn a redis instance for the app to use.
+ redis := machine.GetNativeContainer(ctx, b)
+ if err := redis.Spawn(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/redis",
+ }); err != nil {
+ redis.CleanUp(ctx)
+ b.Fatalf("failed to spwan redis instance: %v", err)
+ }
-// TestMain is the main method for package network.
-func TestMain(m *testing.M) {
- testHarness.Init()
- os.Exit(m.Run())
+ if out, err := redis.WaitForOutput(ctx, "Ready to accept connections", 3*time.Second); err != nil {
+ redis.CleanUp(ctx)
+ b.Fatalf("failed to start redis server: %v %s", err, out)
+ }
+ redisIP, err := redis.FindIP(ctx, false)
+ if err != nil {
+ redis.CleanUp(ctx)
+ b.Fatalf("failed to get IP from redis instance: %v", err)
+ }
+ return redis, redisIP
}
diff --git a/test/benchmarks/base/size_test.go b/test/benchmarks/base/size_test.go
index 7d3877459..acc49cc7c 100644
--- a/test/benchmarks/base/size_test.go
+++ b/test/benchmarks/base/size_test.go
@@ -12,18 +12,22 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package base
+package size_test
import (
"context"
+ "os"
"testing"
"time"
"gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/benchmarks/base"
"gvisor.dev/gvisor/test/benchmarks/harness"
"gvisor.dev/gvisor/test/benchmarks/tools"
)
+var testHarness harness.Harness
+
// BenchmarkSizeEmpty creates N empty containers and reads memory usage from
// /proc/meminfo.
func BenchmarkSizeEmpty(b *testing.B) {
@@ -53,11 +57,11 @@ func BenchmarkSizeEmpty(b *testing.B) {
if err := container.Spawn(ctx, dockerutil.RunOpts{
Image: "benchmarks/alpine",
}, "sh", "-c", "echo Hello && sleep 1000"); err != nil {
- cleanUpContainers(ctx, containers)
+ base.CleanUpContainers(ctx, containers)
b.Fatalf("failed to run container: %v", err)
}
if _, err := container.WaitForOutputSubmatch(ctx, "Hello", 5*time.Second); err != nil {
- cleanUpContainers(ctx, containers)
+ base.CleanUpContainers(ctx, containers)
b.Fatalf("failed to read container output: %v", err)
}
}
@@ -67,7 +71,7 @@ func BenchmarkSizeEmpty(b *testing.B) {
// Check available memory after containers are up.
after, err := machine.RunCommand(cmd, args...)
- cleanUpContainers(ctx, containers)
+ base.CleanUpContainers(ctx, containers)
if err != nil {
b.Fatalf("failed to get meminfo: %v", err)
}
@@ -100,14 +104,14 @@ func BenchmarkSizeNginx(b *testing.B) {
Image: "benchmarks/nginx",
}
const port = 80
- servers := startServers(ctx, b,
- serverArgs{
- machine: machine,
- port: port,
- runOpts: runOpts,
- cmd: []string{"nginx", "-c", "/etc/nginx/nginx_gofer.conf"},
+ servers := base.StartServers(ctx, b,
+ base.ServerArgs{
+ Machine: machine,
+ Port: port,
+ RunOpts: runOpts,
+ Cmd: []string{"nginx", "-c", "/etc/nginx/nginx_gofer.conf"},
})
- defer cleanUpContainers(ctx, servers)
+ defer base.CleanUpContainers(ctx, servers)
// DropCaches after servers are created.
harness.DropCaches(machine)
@@ -130,7 +134,7 @@ func BenchmarkSizeNode(b *testing.B) {
// Make a redis instance for Node to connect.
ctx := context.Background()
- redis, redisIP := redisInstance(ctx, b, machine)
+ redis, redisIP := base.RedisInstance(ctx, b, machine)
defer redis.CleanUp(ctx)
// DropCaches after redis is created.
@@ -152,14 +156,14 @@ func BenchmarkSizeNode(b *testing.B) {
}
nodeCmd := []string{"node", "index.js", redisIP.String()}
const port = 8080
- servers := startServers(ctx, b,
- serverArgs{
- machine: machine,
- port: port,
- runOpts: runOpts,
- cmd: nodeCmd,
+ servers := base.StartServers(ctx, b,
+ base.ServerArgs{
+ Machine: machine,
+ Port: port,
+ RunOpts: runOpts,
+ Cmd: nodeCmd,
})
- defer cleanUpContainers(ctx, servers)
+ defer base.CleanUpContainers(ctx, servers)
// DropCaches after servers are created.
harness.DropCaches(machine)
@@ -172,50 +176,8 @@ func BenchmarkSizeNode(b *testing.B) {
meminfo.Report(b, before, after)
}
-// serverArgs wraps args for startServers and runServerWorkload.
-type serverArgs struct {
- machine harness.Machine
- port int
- runOpts dockerutil.RunOpts
- cmd []string
-}
-
-// startServers starts b.N containers defined by 'runOpts' and 'cmd' and uses
-// 'machine' to check that each is up.
-func startServers(ctx context.Context, b *testing.B, args serverArgs) []*dockerutil.Container {
- b.Helper()
- servers := make([]*dockerutil.Container, 0, b.N)
-
- // Create N servers and wait until each of them is serving.
- for i := 0; i < b.N; i++ {
- server := args.machine.GetContainer(ctx, b)
- servers = append(servers, server)
- if err := server.Spawn(ctx, args.runOpts, args.cmd...); err != nil {
- cleanUpContainers(ctx, servers)
- b.Fatalf("failed to spawn node instance: %v", err)
- }
-
- // Get the container IP.
- servingIP, err := server.FindIP(ctx, false)
- if err != nil {
- cleanUpContainers(ctx, servers)
- b.Fatalf("failed to get ip from server: %v", err)
- }
-
- // Wait until the server is up.
- if err := harness.WaitUntilServing(ctx, args.machine, servingIP, args.port); err != nil {
- cleanUpContainers(ctx, servers)
- b.Fatalf("failed to wait for serving")
- }
- }
- return servers
-}
-
-// cleanUpContainers cleans up a slice of containers.
-func cleanUpContainers(ctx context.Context, containers []*dockerutil.Container) {
- for _, c := range containers {
- if c != nil {
- c.CleanUp(ctx)
- }
- }
+// TestMain is the main method for package network.
+func TestMain(m *testing.M) {
+ testHarness.Init()
+ os.Exit(m.Run())
}
diff --git a/test/benchmarks/base/startup_test.go b/test/benchmarks/base/startup_test.go
index c36a544db..8ef9f99c4 100644
--- a/test/benchmarks/base/startup_test.go
+++ b/test/benchmarks/base/startup_test.go
@@ -12,19 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package base
+package startup_test
import (
"context"
"fmt"
- "net"
+ "os"
"testing"
- "time"
"gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/benchmarks/base"
"gvisor.dev/gvisor/test/benchmarks/harness"
)
+var testHarness harness.Harness
+
// BenchmarkStartEmpty times startup time for an empty container.
func BenchmarkStartupEmpty(b *testing.B) {
machine, err := testHarness.GetMachine()
@@ -35,6 +37,7 @@ func BenchmarkStartupEmpty(b *testing.B) {
ctx := context.Background()
for i := 0; i < b.N; i++ {
+ harness.DebugLog(b, "Running container: %d", i)
container := machine.GetContainer(ctx, b)
defer container.CleanUp(ctx)
if _, err := container.Run(ctx, dockerutil.RunOpts{
@@ -42,6 +45,7 @@ func BenchmarkStartupEmpty(b *testing.B) {
}, "true"); err != nil {
b.Fatalf("failed to run container: %v", err)
}
+ harness.DebugLog(b, "Ran container: %d", i)
}
}
@@ -60,11 +64,11 @@ func BenchmarkStartupNginx(b *testing.B) {
Image: "benchmarks/nginx",
}
runServerWorkload(ctx, b,
- serverArgs{
- machine: machine,
- runOpts: runOpts,
- port: 80,
- cmd: []string{"nginx", "-c", "/etc/nginx/nginx_gofer.conf"},
+ base.ServerArgs{
+ Machine: machine,
+ RunOpts: runOpts,
+ Port: 80,
+ Cmd: []string{"nginx", "-c", "/etc/nginx/nginx_gofer.conf"},
})
}
@@ -79,7 +83,7 @@ func BenchmarkStartupNode(b *testing.B) {
defer machine.CleanUp()
ctx := context.Background()
- redis, redisIP := redisInstance(ctx, b, machine)
+ redis, redisIP := base.RedisInstance(ctx, b, machine)
defer redis.CleanUp(ctx)
runOpts := dockerutil.RunOpts{
Image: "benchmarks/node",
@@ -89,67 +93,54 @@ func BenchmarkStartupNode(b *testing.B) {
cmd := []string{"node", "index.js", redisIP.String()}
runServerWorkload(ctx, b,
- serverArgs{
- machine: machine,
- port: 8080,
- runOpts: runOpts,
- cmd: cmd,
+ base.ServerArgs{
+ Machine: machine,
+ Port: 8080,
+ RunOpts: runOpts,
+ Cmd: cmd,
})
}
-// redisInstance returns a Redis container and its reachable IP.
-func redisInstance(ctx context.Context, b *testing.B, machine harness.Machine) (*dockerutil.Container, net.IP) {
- b.Helper()
- // Spawn a redis instance for the app to use.
- redis := machine.GetNativeContainer(ctx, b)
- if err := redis.Spawn(ctx, dockerutil.RunOpts{
- Image: "benchmarks/redis",
- }); err != nil {
- redis.CleanUp(ctx)
- b.Fatalf("failed to spwan redis instance: %v", err)
- }
-
- if out, err := redis.WaitForOutput(ctx, "Ready to accept connections", 3*time.Second); err != nil {
- redis.CleanUp(ctx)
- b.Fatalf("failed to start redis server: %v %s", err, out)
- }
- redisIP, err := redis.FindIP(ctx, false)
- if err != nil {
- redis.CleanUp(ctx)
- b.Fatalf("failed to get IP from redis instance: %v", err)
- }
- return redis, redisIP
-}
-
// runServerWorkload runs a server workload defined by 'runOpts' and 'cmd'.
// 'clientMachine' is used to connect to the server on 'serverMachine'.
-func runServerWorkload(ctx context.Context, b *testing.B, args serverArgs) {
+func runServerWorkload(ctx context.Context, b *testing.B, args base.ServerArgs) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
+ harness.DebugLog(b, "Running iteration: %d", i)
if err := func() error {
- server := args.machine.GetContainer(ctx, b)
+ server := args.Machine.GetContainer(ctx, b)
defer func() {
b.StopTimer()
// Cleanup servers as we run so that we can go indefinitely.
server.CleanUp(ctx)
b.StartTimer()
}()
- if err := server.Spawn(ctx, args.runOpts, args.cmd...); err != nil {
+ harness.DebugLog(b, "Spawning container: %s", args.RunOpts.Image)
+ if err := server.Spawn(ctx, args.RunOpts, args.Cmd...); err != nil {
return fmt.Errorf("failed to spawn node instance: %v", err)
}
+ harness.DebugLog(b, "Finding Container IP")
servingIP, err := server.FindIP(ctx, false)
if err != nil {
return fmt.Errorf("failed to get ip from server: %v", err)
}
+ harness.DebugLog(b, "Waiting for container to start.")
// Wait until the Client sees the server as up.
- if err := harness.WaitUntilServing(ctx, args.machine, servingIP, args.port); err != nil {
+ if err := harness.WaitUntilServing(ctx, args.Machine, servingIP, args.Port); err != nil {
return fmt.Errorf("failed to wait for serving: %v", err)
}
return nil
}(); err != nil {
b.Fatal(err)
}
+ harness.DebugLog(b, "Ran iteration: %d", i)
}
}
+
+// TestMain is the main method for package network.
+func TestMain(m *testing.M) {
+ testHarness.Init()
+ os.Exit(m.Run())
+}
diff --git a/test/benchmarks/base/sysbench_test.go b/test/benchmarks/base/sysbench_test.go
index 39ced3dab..bbb797e14 100644
--- a/test/benchmarks/base/sysbench_test.go
+++ b/test/benchmarks/base/sysbench_test.go
@@ -12,16 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package base
+package sysbench_test
import (
"context"
"testing"
"gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/benchmarks/harness"
"gvisor.dev/gvisor/test/benchmarks/tools"
)
+var testHarness harness.Harness
+
type testCase struct {
name string
test tools.Sysbench
diff --git a/test/benchmarks/database/BUILD b/test/benchmarks/database/BUILD
index 93b380e8a..0b1743603 100644
--- a/test/benchmarks/database/BUILD
+++ b/test/benchmarks/database/BUILD
@@ -1,4 +1,5 @@
-load("//tools:defs.bzl", "go_library", "go_test")
+load("//tools:defs.bzl", "go_library")
+load("//test/benchmarks:defs.bzl", "benchmark_test")
package(licenses = ["notice"])
@@ -6,19 +7,13 @@ go_library(
name = "database",
testonly = 1,
srcs = ["database.go"],
- deps = ["//test/benchmarks/harness"],
)
-go_test(
- name = "database_test",
+benchmark_test(
+ name = "redis_test",
size = "enormous",
srcs = ["redis_test.go"],
library = ":database",
- tags = [
- # Requires docker and runsc to be configured before test runs.
- "manual",
- "local",
- ],
visibility = ["//:sandbox"],
deps = [
"//pkg/test/dockerutil",
diff --git a/test/benchmarks/database/database.go b/test/benchmarks/database/database.go
index 9eeb59f9a..c15ca661c 100644
--- a/test/benchmarks/database/database.go
+++ b/test/benchmarks/database/database.go
@@ -14,18 +14,3 @@
// 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
index 02e67154e..f8075a04b 100644
--- a/test/benchmarks/database/redis_test.go
+++ b/test/benchmarks/database/redis_test.go
@@ -16,6 +16,7 @@ package database
import (
"context"
+ "os"
"testing"
"time"
@@ -24,6 +25,8 @@ import (
"gvisor.dev/gvisor/test/benchmarks/tools"
)
+var h harness.Harness
+
// All possible operations from redis. Note: "ping" will
// run both PING_INLINE and PING_BUILD.
var operations []string = []string{
@@ -111,21 +114,23 @@ func BenchmarkRedis(b *testing.B) {
// 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()
+ client := clientMachine.GetNativeContainer(ctx, b)
+ defer client.CleanUp(ctx)
+ out, err := client.Run(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/redis",
+ }, redis.MakeCmd(ip, serverPort, b.N /*requests*/)...)
+ if err != nil {
+ b.Fatalf("redis-benchmark failed with: %v", err)
}
+
+ // Stop time while we parse results.
+ b.StopTimer()
+ redis.Report(b, out)
})
}
}
+
+func TestMain(m *testing.M) {
+ h.Init()
+ os.Exit(m.Run())
+}
diff --git a/test/benchmarks/defs.bzl b/test/benchmarks/defs.bzl
new file mode 100644
index 000000000..ef44b46e3
--- /dev/null
+++ b/test/benchmarks/defs.bzl
@@ -0,0 +1,14 @@
+"""Defines a rule for benchmark test targets."""
+
+load("//tools:defs.bzl", "go_test")
+
+def benchmark_test(name, tags = [], **kwargs):
+ go_test(
+ name,
+ tags = [
+ # Requires docker and runsc to be configured before the test runs.
+ "local",
+ "manual",
+ ],
+ **kwargs
+ )
diff --git a/test/benchmarks/fs/BUILD b/test/benchmarks/fs/BUILD
index 45f11372b..b4f967441 100644
--- a/test/benchmarks/fs/BUILD
+++ b/test/benchmarks/fs/BUILD
@@ -1,27 +1,23 @@
-load("//tools:defs.bzl", "go_library", "go_test")
+load("//test/benchmarks:defs.bzl", "benchmark_test")
package(licenses = ["notice"])
-go_library(
- name = "fs",
- testonly = 1,
- srcs = ["fs.go"],
- deps = ["//test/benchmarks/harness"],
+benchmark_test(
+ name = "bazel_test",
+ size = "enormous",
+ srcs = ["bazel_test.go"],
+ visibility = ["//:sandbox"],
+ deps = [
+ "//pkg/test/dockerutil",
+ "//test/benchmarks/harness",
+ "//test/benchmarks/tools",
+ ],
)
-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",
- ],
+benchmark_test(
+ name = "fio_test",
+ size = "enormous",
+ srcs = ["fio_test.go"],
visibility = ["//:sandbox"],
deps = [
"//pkg/test/dockerutil",
diff --git a/test/benchmarks/fs/bazel_test.go b/test/benchmarks/fs/bazel_test.go
index 56103639d..3fb4da9d1 100644
--- a/test/benchmarks/fs/bazel_test.go
+++ b/test/benchmarks/fs/bazel_test.go
@@ -11,11 +11,12 @@
// 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
+package bazel_test
import (
"context"
"fmt"
+ "os"
"strings"
"testing"
@@ -24,6 +25,8 @@ import (
"gvisor.dev/gvisor/test/benchmarks/tools"
)
+var h harness.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/...")
@@ -58,10 +61,10 @@ func runBuildBenchmark(b *testing.B, image, workdir, target string) {
for _, bm := range benchmarks {
pageCache := tools.Parameter{
Name: "page_cache",
- Value: "clean",
+ Value: "dirty",
}
if bm.clearCache {
- pageCache.Value = "dirty"
+ pageCache.Value = "clean"
}
filesystem := tools.Parameter{
@@ -126,15 +129,23 @@ func runBuildBenchmark(b *testing.B, image, workdir, target string) {
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: prefix + workdir,
- }, "bazel", "clean")
- if err != nil {
- b.Fatalf("build failed with: %v", err)
+
+ // Clean bazel in the case we are doing another run.
+ if i < b.N-1 {
+ if _, err = container.Exec(ctx, dockerutil.ExecOpts{
+ WorkDir: prefix + workdir,
+ }, "bazel", "clean"); err != nil {
+ b.Fatalf("build failed with: %v", err)
+ }
}
b.StartTimer()
}
})
}
}
+
+// TestMain is the main method for package fs.
+func TestMain(m *testing.M) {
+ h.Init()
+ os.Exit(m.Run())
+}
diff --git a/test/benchmarks/fs/fio_test.go b/test/benchmarks/fs/fio_test.go
index 5ca191404..96340373c 100644
--- a/test/benchmarks/fs/fio_test.go
+++ b/test/benchmarks/fs/fio_test.go
@@ -11,11 +11,12 @@
// 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
+package fio_test
import (
"context"
"fmt"
+ "os"
"path/filepath"
"strings"
"testing"
@@ -26,6 +27,8 @@ import (
"gvisor.dev/gvisor/test/benchmarks/tools"
)
+var h harness.Harness
+
// 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.
@@ -179,3 +182,9 @@ func makeMount(machine harness.Machine, mountType mount.Type, target string) (mo
return mount.Mount{}, func() {}, fmt.Errorf("illegal mount time not supported: %v", mountType)
}
}
+
+// TestMain is the main method for package fs.
+func TestMain(m *testing.M) {
+ h.Init()
+ os.Exit(m.Run())
+}
diff --git a/test/benchmarks/fs/fs.go b/test/benchmarks/fs/fs.go
deleted file mode 100644
index e5ca28c3b..000000000
--- a/test/benchmarks/fs/fs.go
+++ /dev/null
@@ -1,31 +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 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/harness.go b/test/benchmarks/harness/harness.go
index 68bd7b4cf..4c6e724aa 100644
--- a/test/benchmarks/harness/harness.go
+++ b/test/benchmarks/harness/harness.go
@@ -17,17 +17,32 @@ package harness
import (
"flag"
+ "fmt"
+ "os"
"gvisor.dev/gvisor/pkg/test/dockerutil"
)
+var (
+ help = flag.Bool("help", false, "print this usage message")
+ debug = flag.Bool("debug", false, "turns on debug messages for individual benchmarks")
+)
+
// 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.Usage = func() {
+ fmt.Fprintf(os.Stderr, "Usage: %s -- --test.bench=<regex>\n", os.Args[0])
+ flag.PrintDefaults()
+ }
flag.Parse()
+ if *help {
+ flag.Usage()
+ os.Exit(0)
+ }
dockerutil.EnsureSupportedDockerVersion()
return nil
}
diff --git a/test/benchmarks/harness/util.go b/test/benchmarks/harness/util.go
index 86b863f78..aeac7ebff 100644
--- a/test/benchmarks/harness/util.go
+++ b/test/benchmarks/harness/util.go
@@ -18,6 +18,7 @@ import (
"context"
"fmt"
"net"
+ "testing"
"gvisor.dev/gvisor/pkg/test/dockerutil"
"gvisor.dev/gvisor/pkg/test/testutil"
@@ -46,3 +47,11 @@ func DropCaches(machine Machine) error {
}
return nil
}
+
+// DebugLog prints debug messages if the debug flag is set.
+func DebugLog(b *testing.B, msg string, args ...interface{}) {
+ b.Helper()
+ if *debug {
+ b.Logf(msg, args...)
+ }
+}
diff --git a/test/benchmarks/media/BUILD b/test/benchmarks/media/BUILD
index bb242d385..380783f0b 100644
--- a/test/benchmarks/media/BUILD
+++ b/test/benchmarks/media/BUILD
@@ -1,4 +1,5 @@
-load("//tools:defs.bzl", "go_library", "go_test")
+load("//tools:defs.bzl", "go_library")
+load("//test/benchmarks:defs.bzl", "benchmark_test")
package(licenses = ["notice"])
@@ -6,12 +7,11 @@ go_library(
name = "media",
testonly = 1,
srcs = ["media.go"],
- deps = ["//test/benchmarks/harness"],
)
-go_test(
- name = "media_test",
- size = "large",
+benchmark_test(
+ name = "ffmpeg_test",
+ size = "enormous",
srcs = ["ffmpeg_test.go"],
library = ":media",
visibility = ["//:sandbox"],
diff --git a/test/benchmarks/media/ffmpeg_test.go b/test/benchmarks/media/ffmpeg_test.go
index 7822dfad7..a462ec2a6 100644
--- a/test/benchmarks/media/ffmpeg_test.go
+++ b/test/benchmarks/media/ffmpeg_test.go
@@ -15,6 +15,7 @@ package media
import (
"context"
+ "os"
"strings"
"testing"
@@ -22,6 +23,8 @@ import (
"gvisor.dev/gvisor/test/benchmarks/harness"
)
+var h harness.Harness
+
// BenchmarkFfmpeg runs ffmpeg in a container and records runtime.
// BenchmarkFfmpeg should run as root to drop caches.
func BenchmarkFfmpeg(b *testing.B) {
@@ -32,13 +35,13 @@ func BenchmarkFfmpeg(b *testing.B) {
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()
+ container := machine.GetContainer(ctx, b)
+ defer container.CleanUp(ctx)
if err := harness.DropCaches(machine); err != nil {
b.Skipf("failed to drop caches: %v. You probably need root.", err)
}
@@ -51,3 +54,8 @@ func BenchmarkFfmpeg(b *testing.B) {
}
}
}
+
+func TestMain(m *testing.M) {
+ h.Init()
+ os.Exit(m.Run())
+}
diff --git a/test/benchmarks/media/media.go b/test/benchmarks/media/media.go
index c7b35b758..ed7b24651 100644
--- a/test/benchmarks/media/media.go
+++ b/test/benchmarks/media/media.go
@@ -14,18 +14,3 @@
// 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
index 970f52706..285ec35d9 100644
--- a/test/benchmarks/ml/BUILD
+++ b/test/benchmarks/ml/BUILD
@@ -1,4 +1,5 @@
-load("//tools:defs.bzl", "go_library", "go_test")
+load("//tools:defs.bzl", "go_library")
+load("//test/benchmarks:defs.bzl", "benchmark_test")
package(licenses = ["notice"])
@@ -6,12 +7,11 @@ go_library(
name = "ml",
testonly = 1,
srcs = ["ml.go"],
- deps = ["//test/benchmarks/harness"],
)
-go_test(
- name = "ml_test",
- size = "large",
+benchmark_test(
+ name = "tensorflow_test",
+ size = "enormous",
srcs = ["tensorflow_test.go"],
library = ":ml",
visibility = ["//:sandbox"],
diff --git a/test/benchmarks/ml/ml.go b/test/benchmarks/ml/ml.go
index 13282d7bb..d5fc5b7da 100644
--- a/test/benchmarks/ml/ml.go
+++ b/test/benchmarks/ml/ml.go
@@ -14,18 +14,3 @@
// 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
index f7746897d..a55329d82 100644
--- a/test/benchmarks/ml/tensorflow_test.go
+++ b/test/benchmarks/ml/tensorflow_test.go
@@ -15,12 +15,15 @@ package ml
import (
"context"
+ "os"
"testing"
"gvisor.dev/gvisor/pkg/test/dockerutil"
"gvisor.dev/gvisor/test/benchmarks/harness"
)
+var h harness.Harness
+
// BenchmarkTensorflow runs workloads from a TensorFlow tutorial.
// See: https://github.com/aymericdamien/TensorFlow-Examples
func BenchmarkTensorflow(b *testing.B) {
@@ -44,12 +47,12 @@ func BenchmarkTensorflow(b *testing.B) {
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()
+ container := machine.GetContainer(ctx, b)
+ defer container.CleanUp(ctx)
if err := harness.DropCaches(machine); err != nil {
b.Skipf("failed to drop caches: %v. You probably need root.", err)
}
@@ -67,3 +70,8 @@ func BenchmarkTensorflow(b *testing.B) {
}
}
+
+func TestMain(m *testing.M) {
+ h.Init()
+ os.Exit(m.Run())
+}
diff --git a/test/benchmarks/network/BUILD b/test/benchmarks/network/BUILD
index 472b5c387..2741570f5 100644
--- a/test/benchmarks/network/BUILD
+++ b/test/benchmarks/network/BUILD
@@ -1,4 +1,5 @@
-load("//tools:defs.bzl", "go_library", "go_test")
+load("//tools:defs.bzl", "go_library")
+load("//test/benchmarks:defs.bzl", "benchmark_test")
package(licenses = ["notice"])
@@ -7,7 +8,6 @@ go_library(
testonly = 1,
srcs = [
"network.go",
- "static_server.go",
],
deps = [
"//pkg/test/dockerutil",
@@ -16,22 +16,74 @@ go_library(
],
)
-go_test(
- name = "network_test",
- size = "large",
+benchmark_test(
+ name = "iperf_test",
+ size = "enormous",
srcs = [
- "httpd_test.go",
"iperf_test.go",
- "nginx_test.go",
+ ],
+ library = ":network",
+ visibility = ["//:sandbox"],
+ deps = [
+ "//pkg/test/dockerutil",
+ "//pkg/test/testutil",
+ "//test/benchmarks/harness",
+ "//test/benchmarks/tools",
+ ],
+)
+
+benchmark_test(
+ name = "node_test",
+ size = "enormous",
+ srcs = [
"node_test.go",
+ ],
+ library = ":network",
+ visibility = ["//:sandbox"],
+ deps = [
+ "//pkg/test/dockerutil",
+ "//test/benchmarks/harness",
+ "//test/benchmarks/tools",
+ ],
+)
+
+benchmark_test(
+ name = "ruby_test",
+ size = "enormous",
+ srcs = [
"ruby_test.go",
],
library = ":network",
- tags = [
- # Requires docker and runsc to be configured before test runs.
- "manual",
- "local",
+ visibility = ["//:sandbox"],
+ deps = [
+ "//pkg/test/dockerutil",
+ "//test/benchmarks/harness",
+ "//test/benchmarks/tools",
+ ],
+)
+
+benchmark_test(
+ name = "nginx_test",
+ size = "enormous",
+ srcs = [
+ "nginx_test.go",
+ ],
+ library = ":network",
+ visibility = ["//:sandbox"],
+ deps = [
+ "//pkg/test/dockerutil",
+ "//test/benchmarks/harness",
+ "//test/benchmarks/tools",
],
+)
+
+benchmark_test(
+ name = "httpd_test",
+ size = "enormous",
+ srcs = [
+ "httpd_test.go",
+ ],
+ library = ":network",
visibility = ["//:sandbox"],
deps = [
"//pkg/test/dockerutil",
diff --git a/test/benchmarks/network/httpd_test.go b/test/benchmarks/network/httpd_test.go
index 8d7d5f750..b07274662 100644
--- a/test/benchmarks/network/httpd_test.go
+++ b/test/benchmarks/network/httpd_test.go
@@ -14,13 +14,17 @@
package network
import (
+ "os"
"strconv"
"testing"
"gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/benchmarks/harness"
"gvisor.dev/gvisor/test/benchmarks/tools"
)
+var h harness.Harness
+
// see Dockerfile '//images/benchmarks/httpd'.
var httpdDocs = map[string]string{
"notfound": "notfound",
@@ -43,6 +47,22 @@ func BenchmarkReverseHttpd(b *testing.B) {
benchmarkHttpdDocSize(b, true /* reverse */)
}
+// BenchmarkContinuousHttpd runs specific benchmarks for continous jobs.
+// The runtime under test is the server serving a runc client.
+func BenchmarkContinuousHttpd(b *testing.B) {
+ sizes := []string{"10Kb", "100Kb", "1Mb"}
+ threads := []int{1, 25, 100, 1000}
+ benchmarkHttpdContinuous(b, threads, sizes, false /*reverse*/)
+}
+
+// BenchmarkContinuousHttpdReverse runs specific benchmarks for continous jobs.
+// The runtime under test is the client downloading from a runc server.
+func BenchmarkContinuousHttpdReverse(b *testing.B) {
+ sizes := []string{"10Kb", "100Kb", "1Mb"}
+ threads := []int{1, 25, 100, 1000}
+ benchmarkHttpdContinuous(b, threads, sizes, true /*reverse*/)
+}
+
// benchmarkHttpdDocSize iterates through all doc sizes, running subbenchmarks
// for each size.
func benchmarkHttpdDocSize(b *testing.B, reverse bool) {
@@ -62,9 +82,51 @@ func benchmarkHttpdDocSize(b *testing.B, reverse bool) {
if err != nil {
b.Fatalf("Failed to parse parameters: %v", err)
}
+ requests := b.N
+ if requests < c {
+ b.Logf("b.N is %d must be greater than threads %d. Consider running with --test.benchtime=Nx where N >= %d", b.N, c, c)
+ requests = c
+ }
+ b.Run(name, func(b *testing.B) {
+ hey := &tools.Hey{
+ Requests: requests,
+ Concurrency: c,
+ Doc: filename,
+ }
+ runHttpd(b, hey, reverse)
+ })
+ }
+ }
+}
+
+// benchmarkHttpdContinuous iterates through given sizes and concurrencies.
+func benchmarkHttpdContinuous(b *testing.B, concurrency []int, sizes []string, reverse bool) {
+ for _, size := range sizes {
+ filename := httpdDocs[size]
+ for _, c := range concurrency {
+ fsize := tools.Parameter{
+ Name: "filesize",
+ Value: size,
+ }
+
+ threads := tools.Parameter{
+ Name: "concurrency",
+ Value: strconv.Itoa(c),
+ }
+
+ name, err := tools.ParametersToName(fsize, threads)
+ if err != nil {
+ b.Fatalf("Failed to parse parameters: %v", err)
+ }
+
+ requests := b.N
+ if requests < c {
+ b.Logf("b.N is %d must be greater than threads %d. Consider running with --test.benchtime=Nx where N >= %d", b.N, c, c)
+ requests = c
+ }
b.Run(name, func(b *testing.B) {
hey := &tools.Hey{
- Requests: c * b.N,
+ Requests: requests,
Concurrency: c,
Doc: filename,
}
@@ -91,5 +153,10 @@ func runHttpd(b *testing.B, hey *tools.Hey, reverse bool) {
},
}
httpdCmd := []string{"sh", "-c", "mkdir -p /tmp/html; cp -r /local/* /tmp/html/.; apache2 -X"}
- runStaticServer(b, httpdRunOpts, httpdCmd, port, hey, reverse)
+ runStaticServer(b, h, httpdRunOpts, httpdCmd, port, hey, reverse)
+}
+
+func TestMain(m *testing.M) {
+ h.Init()
+ os.Exit(m.Run())
}
diff --git a/test/benchmarks/network/iperf_test.go b/test/benchmarks/network/iperf_test.go
index b8ab7dfb8..9d64db943 100644
--- a/test/benchmarks/network/iperf_test.go
+++ b/test/benchmarks/network/iperf_test.go
@@ -15,6 +15,7 @@ package network
import (
"context"
+ "os"
"testing"
"gvisor.dev/gvisor/pkg/test/dockerutil"
@@ -23,9 +24,11 @@ import (
"gvisor.dev/gvisor/test/benchmarks/tools"
)
+var h harness.Harness
+
func BenchmarkIperf(b *testing.B) {
iperf := tools.Iperf{
- Time: 10, // time in seconds to run client.
+ Time: b.N, // time in seconds to run client.
}
clientMachine, err := h.GetMachine()
@@ -97,17 +100,19 @@ func BenchmarkIperf(b *testing.B) {
// 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()
+ 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)
})
}
}
+
+func TestMain(m *testing.M) {
+ h.Init()
+ os.Exit(m.Run())
+}
diff --git a/test/benchmarks/network/network.go b/test/benchmarks/network/network.go
index ce17ddb94..b18bc2b3c 100644
--- a/test/benchmarks/network/network.go
+++ b/test/benchmarks/network/network.go
@@ -16,16 +16,73 @@
package network
import (
- "os"
+ "context"
"testing"
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
"gvisor.dev/gvisor/test/benchmarks/harness"
+ "gvisor.dev/gvisor/test/benchmarks/tools"
)
-var h harness.Harness
+// runStaticServer runs static serving workloads (httpd, nginx).
+func runStaticServer(b *testing.B, h harness.Harness, serverOpts dockerutil.RunOpts, serverCmd []string, port int, hey *tools.Hey, reverse bool) {
+ ctx := context.Background()
-// TestMain is the main method for package network.
-func TestMain(m *testing.M) {
- h.Init()
- os.Exit(m.Run())
+ // Get two machines: a client and server.
+ 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()
+
+ // Make the containers. 'reverse=true' specifies that the client should use the
+ // runtime under test.
+ var client, server *dockerutil.Container
+ if reverse {
+ client = clientMachine.GetContainer(ctx, b)
+ server = serverMachine.GetNativeContainer(ctx, b)
+ } else {
+ client = clientMachine.GetNativeContainer(ctx, b)
+ server = serverMachine.GetContainer(ctx, b)
+ }
+ defer client.CleanUp(ctx)
+ defer server.CleanUp(ctx)
+
+ // Start the server.
+ if err := server.Spawn(ctx, serverOpts, serverCmd...); err != nil {
+ b.Fatalf("failed to start server: %v", err)
+ }
+
+ // Get its IP.
+ ip, err := serverMachine.IPAddress()
+ if err != nil {
+ b.Fatalf("failed to find server ip: %v", err)
+ }
+
+ // Get the published port.
+ servingPort, err := server.FindPort(ctx, port)
+ if err != nil {
+ b.Fatalf("failed to find server port %d: %v", port, err)
+ }
+
+ // Make sure the server is serving.
+ harness.WaitUntilServing(ctx, clientMachine, ip, servingPort)
+ b.ResetTimer()
+ server.RestartProfiles()
+ 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/nginx_test.go b/test/benchmarks/network/nginx_test.go
index 08565d0b2..87449612a 100644
--- a/test/benchmarks/network/nginx_test.go
+++ b/test/benchmarks/network/nginx_test.go
@@ -14,13 +14,17 @@
package network
import (
+ "os"
"strconv"
"testing"
"gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/benchmarks/harness"
"gvisor.dev/gvisor/test/benchmarks/tools"
)
+var h harness.Harness
+
// see Dockerfile '//images/benchmarks/nginx'.
var nginxDocs = map[string]string{
"notfound": "notfound",
@@ -44,6 +48,22 @@ func BenchmarkReverseNginxDocSize(b *testing.B) {
benchmarkNginxDocSize(b, true /* reverse */, true /* tmpfs */)
}
+// BenchmarkContinuousNginx runs specific benchmarks for continous jobs.
+// The runtime under test is the sever serving a runc client.
+func BenchmarkContinuousNginx(b *testing.B) {
+ sizes := []string{"10Kb", "100Kb", "1Mb"}
+ threads := []int{1, 25, 100, 1000}
+ benchmarkNginxContinuous(b, threads, sizes, false /*reverse*/)
+}
+
+// BenchmarkContinuousNginxReverse runs specific benchmarks for continous jobs.
+// The runtime under test is the client downloading from a runc server.
+func BenchmarkContinuousNginxReverse(b *testing.B) {
+ sizes := []string{"10Kb", "100Kb", "1Mb"}
+ threads := []int{1, 25, 100, 1000}
+ benchmarkNginxContinuous(b, threads, sizes, true /*reverse*/)
+}
+
// benchmarkNginxDocSize iterates through all doc sizes, running subbenchmarks
// for each size.
func benchmarkNginxDocSize(b *testing.B, reverse, tmpfs bool) {
@@ -72,9 +92,14 @@ func benchmarkNginxDocSize(b *testing.B, reverse, tmpfs bool) {
b.Fatalf("Failed to parse parameters: %v", err)
}
+ requests := b.N
+ if requests < c {
+ b.Logf("b.N is %d must be greater than threads %d. Consider running with --test.benchtime=Nx where N >= %d", b.N, c, c)
+ requests = c
+ }
b.Run(name, func(b *testing.B) {
hey := &tools.Hey{
- Requests: c * b.N,
+ Requests: requests,
Concurrency: c,
Doc: filename,
}
@@ -84,6 +109,47 @@ func benchmarkNginxDocSize(b *testing.B, reverse, tmpfs bool) {
}
}
+// benchmarkNginxContinuous iterates through given sizes and concurrencies on a tmpfs mount.
+func benchmarkNginxContinuous(b *testing.B, concurrency []int, sizes []string, reverse bool) {
+ for _, size := range sizes {
+ filename := nginxDocs[size]
+ for _, c := range concurrency {
+ fsize := tools.Parameter{
+ Name: "filesize",
+ Value: size,
+ }
+
+ threads := tools.Parameter{
+ Name: "concurrency",
+ Value: strconv.Itoa(c),
+ }
+
+ fs := tools.Parameter{
+ Name: "filesystem",
+ Value: "tmpfs",
+ }
+
+ name, err := tools.ParametersToName(fsize, threads, fs)
+ if err != nil {
+ b.Fatalf("Failed to parse parameters: %v", err)
+ }
+ requests := b.N
+ if requests < c {
+ b.Logf("b.N is %d must be greater than threads %d. Consider running with --test.benchtime=Nx where N >= %d", b.N, c, c)
+ requests = c
+ }
+ b.Run(name, func(b *testing.B) {
+ hey := &tools.Hey{
+ Requests: requests,
+ Concurrency: c,
+ Doc: filename,
+ }
+ runNginx(b, hey, reverse, true /*tmpfs*/)
+ })
+ }
+ }
+}
+
// runNginx configures the static serving methods to run httpd.
func runNginx(b *testing.B, hey *tools.Hey, reverse, tmpfs bool) {
// nginx runs on port 80.
@@ -99,5 +165,10 @@ func runNginx(b *testing.B, hey *tools.Hey, reverse, tmpfs bool) {
}
// Command copies nginxDocs to tmpfs serving directory and runs nginx.
- runStaticServer(b, nginxRunOpts, nginxCmd, port, hey, reverse)
+ runStaticServer(b, h, nginxRunOpts, nginxCmd, port, hey, reverse)
+}
+
+func TestMain(m *testing.M) {
+ h.Init()
+ os.Exit(m.Run())
}
diff --git a/test/benchmarks/network/node_test.go b/test/benchmarks/network/node_test.go
index 254538899..3e837a9e4 100644
--- a/test/benchmarks/network/node_test.go
+++ b/test/benchmarks/network/node_test.go
@@ -15,6 +15,7 @@ package network
import (
"context"
+ "os"
"strconv"
"testing"
"time"
@@ -24,6 +25,8 @@ import (
"gvisor.dev/gvisor/test/benchmarks/tools"
)
+var h harness.Harness
+
// BenchmarkNode runs 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
@@ -39,9 +42,14 @@ func BenchmarkNode(b *testing.B) {
if err != nil {
b.Fatalf("Failed to parse parameters: %v", err)
}
+ requests := b.N
+ if requests < c {
+ b.Logf("b.N is %d must be greater than threads %d. Consider running with --test.benchtime=Nx where N >= %d", b.N, c, c)
+ requests = c
+ }
b.Run(name, func(b *testing.B) {
hey := &tools.Hey{
- Requests: b.N * c, // Requests b.N requests per thread.
+ Requests: requests,
Concurrency: c,
}
runNode(b, hey)
@@ -131,5 +139,9 @@ func runNode(b *testing.B, hey *tools.Hey) {
// Stop the timer to parse the data and report stats.
b.StopTimer()
hey.Report(b, out)
- b.StartTimer()
+}
+
+func TestMain(m *testing.M) {
+ h.Init()
+ os.Exit(m.Run())
}
diff --git a/test/benchmarks/network/ruby_test.go b/test/benchmarks/network/ruby_test.go
index 0174ff3f3..c89672873 100644
--- a/test/benchmarks/network/ruby_test.go
+++ b/test/benchmarks/network/ruby_test.go
@@ -16,6 +16,7 @@ package network
import (
"context"
"fmt"
+ "os"
"strconv"
"testing"
"time"
@@ -25,6 +26,8 @@ import (
"gvisor.dev/gvisor/test/benchmarks/tools"
)
+var h harness.Harness
+
// BenchmarkRuby runs requests using 'hey' against a ruby application server.
// On start, ruby app generates some random data and pushes it to a redis
// instance. On a request, the app grabs for random entries from the redis
@@ -40,9 +43,14 @@ func BenchmarkRuby(b *testing.B) {
if err != nil {
b.Fatalf("Failed to parse parameters: %v", err)
}
+ requests := b.N
+ if requests < c {
+ b.Logf("b.N is %d must be greater than threads %d. Consider running with --test.benchtime=Nx where N >= %d", b.N, c, c)
+ requests = c
+ }
b.Run(name, func(b *testing.B) {
hey := &tools.Hey{
- Requests: b.N * c, // b.N requests per thread.
+ Requests: requests,
Concurrency: c,
}
runRuby(b, hey)
@@ -52,7 +60,6 @@ func BenchmarkRuby(b *testing.B) {
// runRuby runs the test for a given # of requests and concurrency.
func runRuby(b *testing.B, hey *tools.Hey) {
- b.Helper()
// The machine to hold Redis and the Ruby Server.
serverMachine, err := h.GetMachine()
if err != nil {
@@ -141,3 +148,8 @@ func runRuby(b *testing.B, hey *tools.Hey) {
hey.Report(b, out)
b.StartTimer()
}
+
+func TestMain(m *testing.M) {
+ h.Init()
+ os.Exit(m.Run())
+}
diff --git a/test/benchmarks/network/static_server.go b/test/benchmarks/network/static_server.go
deleted file mode 100644
index e747a1395..000000000
--- a/test/benchmarks/network/static_server.go
+++ /dev/null
@@ -1,87 +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 network
-
-import (
- "context"
- "testing"
-
- "gvisor.dev/gvisor/pkg/test/dockerutil"
- "gvisor.dev/gvisor/test/benchmarks/harness"
- "gvisor.dev/gvisor/test/benchmarks/tools"
-)
-
-// runStaticServer runs static serving workloads (httpd, nginx).
-func runStaticServer(b *testing.B, serverOpts dockerutil.RunOpts, serverCmd []string, port int, hey *tools.Hey, reverse bool) {
- ctx := context.Background()
-
- // Get two machines: a client and server.
- 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()
-
- // Make the containers. 'reverse=true' specifies that the client should use the
- // runtime under test.
- var client, server *dockerutil.Container
- if reverse {
- client = clientMachine.GetContainer(ctx, b)
- server = serverMachine.GetNativeContainer(ctx, b)
- } else {
- client = clientMachine.GetNativeContainer(ctx, b)
- server = serverMachine.GetContainer(ctx, b)
- }
- defer client.CleanUp(ctx)
- defer server.CleanUp(ctx)
-
- // Start the server.
- if err := server.Spawn(ctx, serverOpts, serverCmd...); err != nil {
- b.Fatalf("failed to start server: %v", err)
- }
-
- // Get its IP.
- ip, err := serverMachine.IPAddress()
- if err != nil {
- b.Fatalf("failed to find server ip: %v", err)
- }
-
- // Get the published port.
- servingPort, err := server.FindPort(ctx, port)
- if err != nil {
- b.Fatalf("failed to find server port %d: %v", port, err)
- }
-
- // Make sure the server is serving.
- harness.WaitUntilServing(ctx, clientMachine, ip, servingPort)
- b.ResetTimer()
- server.RestartProfiles()
- 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/tcp/tcp_proxy.go b/test/benchmarks/tcp/tcp_proxy.go
index 5afe10f69..9fe60080c 100644
--- a/test/benchmarks/tcp/tcp_proxy.go
+++ b/test/benchmarks/tcp/tcp_proxy.go
@@ -208,9 +208,6 @@ func newNetstackImpl(mode string) (impl, error) {
if err := s.CreateNIC(nicID, fifo.New(ep, runtime.GOMAXPROCS(0), 1000)); err != nil {
return nil, fmt.Errorf("error creating NIC %q: %v", *iface, err)
}
- if err := s.AddAddress(nicID, arp.ProtocolNumber, arp.ProtocolAddress); err != nil {
- return nil, fmt.Errorf("error adding ARP address to %q: %v", *iface, err)
- }
if err := s.AddAddress(nicID, ipv4.ProtocolNumber, parsedAddr); err != nil {
return nil, fmt.Errorf("error adding IP address to %q: %v", *iface, err)
}
diff --git a/test/benchmarks/tools/iperf.go b/test/benchmarks/tools/iperf.go
index 5c4e7125b..891d32704 100644
--- a/test/benchmarks/tools/iperf.go
+++ b/test/benchmarks/tools/iperf.go
@@ -31,7 +31,7 @@ type Iperf struct {
// 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), " ")
+ return strings.Split(fmt.Sprintf("iperf -f K --realtime --time %d --client %s --port %d", i.Time, ip, port), " ")
}
// Report parses output from iperf client and reports metrics.
diff --git a/test/benchmarks/tools/redis.go b/test/benchmarks/tools/redis.go
index e35886437..a42e3456e 100644
--- a/test/benchmarks/tools/redis.go
+++ b/test/benchmarks/tools/redis.go
@@ -29,17 +29,17 @@ type Redis struct {
}
// MakeCmd returns a redis-benchmark client command.
-func (r *Redis) MakeCmd(ip net.IP, port int) []string {
+func (r *Redis) MakeCmd(ip net.IP, port, requests 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), " ")
+ fmt.Sprintf("redis-benchmark --csv -t ping -h %s -p %d -n %d", ip, port, requests), " ")
}
// 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), " ")
+ fmt.Sprintf("redis-benchmark --csv -t %s -h %s -p %d -n %d", r.Operation, ip, port, requests), " ")
}
// Report parses output from redis-benchmark client and reports metrics.
diff --git a/test/cmd/test_app/fds.go b/test/cmd/test_app/fds.go
index a7658eefd..d4354f0d3 100644
--- a/test/cmd/test_app/fds.go
+++ b/test/cmd/test_app/fds.go
@@ -16,6 +16,7 @@ package main
import (
"context"
+ "io"
"io/ioutil"
"log"
"os"
@@ -168,8 +169,8 @@ func (fdr *fdReceiver) Execute(ctx context.Context, f *flag.FlagSet, args ...int
file := os.NewFile(uintptr(fd), "received file")
defer file.Close()
- if _, err := file.Seek(0, os.SEEK_SET); err != nil {
- log.Fatalf("Seek(0, 0) failed: %v", err)
+ if _, err := file.Seek(0, io.SeekStart); err != nil {
+ log.Fatalf("Error from seek(0, 0): %v", err)
}
got, err := ioutil.ReadAll(file)
diff --git a/test/e2e/integration_test.go b/test/e2e/integration_test.go
index 8425abecb..03bdfa889 100644
--- a/test/e2e/integration_test.go
+++ b/test/e2e/integration_test.go
@@ -494,6 +494,55 @@ func TestLink(t *testing.T) {
}
}
+// This test ensures we can run ping without errors.
+func TestPing4Loopback(t *testing.T) {
+ if testutil.IsRunningWithHostNet() {
+ // TODO(gvisor.dev/issue/5011): support ICMP sockets in hostnet and enable
+ // this test.
+ t.Skip("hostnet only supports TCP/UDP sockets, so ping is not supported.")
+ }
+
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
+
+ if got, err := d.Run(ctx, dockerutil.RunOpts{
+ Image: "basic/ping4test",
+ }, "/root/ping4.sh"); err != nil {
+ t.Fatalf("docker run failed: %s", err)
+ } else if got != "" {
+ t.Errorf("test failed:\n%s", got)
+ }
+}
+
+// This test ensures we can enable ipv6 on loopback and run ping6 without
+// errors.
+func TestPing6Loopback(t *testing.T) {
+ if testutil.IsRunningWithHostNet() {
+ // TODO(gvisor.dev/issue/5011): support ICMP sockets in hostnet and enable
+ // this test.
+ t.Skip("hostnet only supports TCP/UDP sockets, so ping6 is not supported.")
+ }
+
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
+
+ if got, err := d.Run(ctx, dockerutil.RunOpts{
+ Image: "basic/ping6test",
+ // The CAP_NET_ADMIN capability is required to use the `ip` utility, which
+ // we use to enable ipv6 on loopback.
+ //
+ // By default, ipv6 loopback is not enabled by runsc, because docker does
+ // not assign an ipv6 address to the test container.
+ CapAdd: []string{"NET_ADMIN"},
+ }, "/root/ping6.sh"); err != nil {
+ t.Fatalf("docker run failed: %s", err)
+ } else if got != "" {
+ t.Errorf("test failed:\n%s", got)
+ }
+}
+
func TestMain(m *testing.M) {
dockerutil.EnsureSupportedDockerVersion()
flag.Parse()
diff --git a/test/fuse/BUILD b/test/fuse/BUILD
index 8e31fdd41..74500ec84 100644
--- a/test/fuse/BUILD
+++ b/test/fuse/BUILD
@@ -71,3 +71,8 @@ syscall_test(
fuse = "True",
test = "//test/fuse/linux:setstat_test",
)
+
+syscall_test(
+ fuse = "True",
+ test = "//test/fuse/linux:mount_test",
+)
diff --git a/test/fuse/linux/BUILD b/test/fuse/linux/BUILD
index 7673252ec..d1fb178e8 100644
--- a/test/fuse/linux/BUILD
+++ b/test/fuse/linux/BUILD
@@ -228,3 +228,15 @@ cc_binary(
"//test/util:test_util",
],
)
+
+cc_binary(
+ name = "mount_test",
+ testonly = 1,
+ srcs = ["mount_test.cc"],
+ deps = [
+ gtest,
+ "//test/util:temp_path",
+ "//test/util:test_main",
+ "//test/util:test_util",
+ ],
+)
diff --git a/test/fuse/linux/mount_test.cc b/test/fuse/linux/mount_test.cc
new file mode 100644
index 000000000..a5c2fbb01
--- /dev/null
+++ b/test/fuse/linux/mount_test.cc
@@ -0,0 +1,41 @@
+// 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 <errno.h>
+#include <fcntl.h>
+#include <sys/mount.h>
+
+#include "gtest/gtest.h"
+#include "test/util/temp_path.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+TEST(FuseMount, FDNotParsable) {
+ int devfd;
+ EXPECT_THAT(devfd = open("/dev/fuse", O_RDWR), SyscallSucceeds());
+ std::string mount_opts = "fd=thiscantbeparsed";
+ TempPath mount_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ EXPECT_THAT(mount("fuse", mount_dir.path().c_str(), "fuse",
+ MS_NODEV | MS_NOSUID, mount_opts.c_str()),
+ SyscallFailsWithErrno(EINVAL));
+}
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/fuse/linux/stat_test.cc b/test/fuse/linux/stat_test.cc
index 6f032cac1..73321592b 100644
--- a/test/fuse/linux/stat_test.cc
+++ b/test/fuse/linux/stat_test.cc
@@ -76,8 +76,13 @@ TEST_F(StatTest, StatNormal) {
// Check filesystem operation result.
struct stat expected_stat = {
.st_ino = attr.ino,
+#ifdef __aarch64__
+ .st_mode = expected_mode,
+ .st_nlink = attr.nlink,
+#else
.st_nlink = attr.nlink,
.st_mode = expected_mode,
+#endif
.st_uid = attr.uid,
.st_gid = attr.gid,
.st_rdev = attr.rdev,
@@ -152,8 +157,13 @@ TEST_F(StatTest, FstatNormal) {
// Check filesystem operation result.
struct stat expected_stat = {
.st_ino = attr.ino,
+#ifdef __aarch64__
+ .st_mode = expected_mode,
+ .st_nlink = attr.nlink,
+#else
.st_nlink = attr.nlink,
.st_mode = expected_mode,
+#endif
.st_uid = attr.uid,
.st_gid = attr.gid,
.st_rdev = attr.rdev,
diff --git a/test/iptables/README.md b/test/iptables/README.md
index 28ab195ca..1196f8eb5 100644
--- a/test/iptables/README.md
+++ b/test/iptables/README.md
@@ -2,8 +2,38 @@
iptables tests are run via `make iptables-tests`.
-iptables requires raw socket support, so you must add the `--net-raw=true` flag
-to `/etc/docker/daemon.json` in order to use it.
+iptables require some extra Docker configuration to work. Enable IPv6 in
+`/etc/docker/daemon.json` (make sure to restart Docker if you change this file):
+
+```json
+{
+ "experimental": true,
+ "fixed-cidr-v6": "2001:db8:1::/64",
+ "ipv6": true,
+ // Runtimes and other Docker config...
+}
+```
+
+And if you're running manually (i.e. not using the `make` target), you'll need
+to:
+
+* Enable iptables via `modprobe iptables_filter && modprobe ip6table_filter`.
+* Enable `--net-raw` in your chosen runtime in `/etc/docker/daemon.json` (make
+ sure to restart Docker if you change this file).
+
+The resulting runtime should look something like this:
+
+```json
+"runsc": {
+ "path": "/tmp/iptables/runsc",
+ "runtimeArgs": [
+ "--debug-log",
+ "/tmp/iptables/logs/runsc.log.%TEST%.%TIMESTAMP%.%COMMAND%",
+ "--net-raw"
+ ]
+},
+// ...
+```
## Test Structure
diff --git a/test/iptables/filter_output.go b/test/iptables/filter_output.go
index 32bf2a992..f4af45e96 100644
--- a/test/iptables/filter_output.go
+++ b/test/iptables/filter_output.go
@@ -248,7 +248,7 @@ func (FilterOutputOwnerFail) Name() string {
// ContainerAction implements TestCase.ContainerAction.
func (FilterOutputOwnerFail) ContainerAction(ctx context.Context, 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")
+ return fmt.Errorf("invalid argument")
}
return nil
@@ -441,9 +441,20 @@ func (FilterOutputDestination) Name() string {
// ContainerAction implements TestCase.ContainerAction.
func (FilterOutputDestination) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
- rules := [][]string{
- {"-A", "OUTPUT", "-d", ip.String(), "-j", "ACCEPT"},
- {"-P", "OUTPUT", "DROP"},
+ var rules [][]string
+ if ipv6 {
+ rules = [][]string{
+ {"-A", "OUTPUT", "-d", ip.String(), "-j", "ACCEPT"},
+ // Allow solicited node multicast addresses so we can send neighbor
+ // solicitations.
+ {"-A", "OUTPUT", "-d", "ff02::1:ff00:0/104", "-j", "ACCEPT"},
+ {"-P", "OUTPUT", "DROP"},
+ }
+ } else {
+ rules = [][]string{
+ {"-A", "OUTPUT", "-d", ip.String(), "-j", "ACCEPT"},
+ {"-P", "OUTPUT", "DROP"},
+ }
}
if err := filterTableRules(ipv6, rules); err != nil {
return err
diff --git a/test/iptables/iptables_test.go b/test/iptables/iptables_test.go
index 834f7615f..4733146c0 100644
--- a/test/iptables/iptables_test.go
+++ b/test/iptables/iptables_test.go
@@ -89,6 +89,10 @@ func iptablesTest(t *testing.T, test TestCase, ipv6 bool) {
// Get the container IP.
ip, err := d.FindIP(ctx, ipv6)
if err != nil {
+ // If ipv6 is not configured, don't fail.
+ if ipv6 && err == dockerutil.ErrNoIP {
+ t.Skipf("No ipv6 address is available.")
+ }
t.Fatalf("failed to get container IP: %v", err)
}
diff --git a/test/iptables/nat.go b/test/iptables/nat.go
index dd9a18339..b98d99fb8 100644
--- a/test/iptables/nat.go
+++ b/test/iptables/nat.go
@@ -577,11 +577,18 @@ func listenForRedirectedConn(ctx context.Context, ipv6 bool, originalDsts []net.
connCh := make(chan int)
errCh := make(chan error)
go func() {
- connFD, _, err := syscall.Accept(sockfd)
- if err != nil {
- errCh <- err
+ for {
+ connFD, _, err := syscall.Accept(sockfd)
+ if errors.Is(err, syscall.EINTR) {
+ continue
+ }
+ if err != nil {
+ errCh <- err
+ return
+ }
+ connCh <- connFD
+ return
}
- connCh <- connFD
}()
// Wait for accept() to return or for the context to finish.
diff --git a/test/packetdrill/BUILD b/test/packetdrill/BUILD
index 49642f282..5d95516ee 100644
--- a/test/packetdrill/BUILD
+++ b/test/packetdrill/BUILD
@@ -38,6 +38,15 @@ packetdrill_test(
scripts = ["tcp_defer_accept_timeout.pkt"],
)
+test_suite(
+ name = "all_tests",
+ tags = [
+ "manual",
+ "packetdrill",
+ ],
+ tests = existing_rules(),
+)
+
bzl_library(
name = "defs_bzl",
srcs = ["defs.bzl"],
diff --git a/test/packetdrill/defs.bzl b/test/packetdrill/defs.bzl
index fc28ce9ba..a6cbcc376 100644
--- a/test/packetdrill/defs.bzl
+++ b/test/packetdrill/defs.bzl
@@ -15,7 +15,7 @@ def _packetdrill_test_impl(ctx):
# Make sure that everything is readable here.
"find . -type f -exec chmod a+rx {} \\;",
"find . -type d -exec chmod a+rx {} \\;",
- "%s %s --init_script %s $@ -- %s\n" % (
+ "%s %s --init_script %s \"$@\" -- %s\n" % (
test_runner.short_path,
" ".join(ctx.attr.flags),
ctx.files._init_script[0].short_path,
@@ -80,9 +80,7 @@ def packetdrill_netstack_test(name, **kwargs):
kwargs["tags"] = PACKETDRILL_TAGS
_packetdrill_test(
name = name,
- # This is the default runtime unless
- # "--test_arg=--runtime=OTHER_RUNTIME" is used to override the value.
- flags = ["--dut_platform", "netstack", "--runtime", "runsc-d"],
+ flags = ["--dut_platform", "netstack"],
**kwargs
)
diff --git a/test/packetdrill/packetdrill_test.sh b/test/packetdrill/packetdrill_test.sh
index 922547d65..d25cad83a 100755
--- a/test/packetdrill/packetdrill_test.sh
+++ b/test/packetdrill/packetdrill_test.sh
@@ -29,7 +29,7 @@ function failure() {
}
trap 'failure ${LINENO} "$BASH_COMMAND"' ERR
-declare -r LONGOPTS="dut_platform:,init_script:,runtime:"
+declare -r LONGOPTS="dut_platform:,init_script:,runtime:,partition:,total_partitions:"
# Don't use declare below so that the error from getopt will end the script.
PARSED=$(getopt --options "" --longoptions=$LONGOPTS --name "$0" -- "$@")
@@ -48,12 +48,17 @@ while true; do
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
;;
+ --partition)
+ # Ignored.
+ shift 2
+ ;;
+ --total_partitions)
+ # Ignored.
+ shift 2
+ ;;
--)
shift
break
diff --git a/test/packetimpact/netdevs/netdevs.go b/test/packetimpact/netdevs/netdevs.go
index eecfe0730..25dcfbf60 100644
--- a/test/packetimpact/netdevs/netdevs.go
+++ b/test/packetimpact/netdevs/netdevs.go
@@ -40,17 +40,13 @@ 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:/]+)`)
+ inet6Line = regexp.MustCompile(`^\s*inet6 ([0-9a-fA-F:/]+)`)
)
-// 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) {
+// ParseDevicesWithRegex will parse the output with the given regexps to produce
+// a map from device name to device information. It is assumed that deviceLine
+// contains both a name and an ID.
+func ParseDevicesWithRegex(cmdOutput string, deviceLine, linkLine, inetLine, inet6Line *regexp.Regexp) (map[string]DeviceInfo, error) {
var currentDevice string
var currentInfo DeviceInfo
deviceInfos := make(map[string]DeviceInfo)
@@ -93,6 +89,17 @@ func ParseDevices(cmdOutput string) (map[string]DeviceInfo, error) {
return deviceInfos, nil
}
+// 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) {
+ return ParseDevicesWithRegex(cmdOutput, deviceLine, linkLine, inetLine, inet6Line)
+}
+
// 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 {
diff --git a/test/packetimpact/runner/BUILD b/test/packetimpact/runner/BUILD
index 605dd4972..888c44343 100644
--- a/test/packetimpact/runner/BUILD
+++ b/test/packetimpact/runner/BUILD
@@ -32,6 +32,7 @@ go_library(
deps = [
"//pkg/test/dockerutil",
"//test/packetimpact/netdevs",
+ "//test/packetimpact/testbench",
"@com_github_docker_docker//api/types/mount:go_default_library",
],
)
diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl
index 1546d0d51..c6c95546a 100644
--- a/test/packetimpact/runner/defs.bzl
+++ b/test/packetimpact/runner/defs.bzl
@@ -12,10 +12,11 @@ def _packetimpact_test_impl(ctx):
# current user, and no other users will be mapped in that namespace.
# Make sure that everything is readable here.
"find . -type f -or -type d -exec chmod a+rx {} \\;",
- "%s %s --testbench_binary %s $@\n" % (
+ "%s %s --testbench_binary %s --num_duts %d $@\n" % (
test_runner.short_path,
" ".join(ctx.attr.flags),
ctx.files.testbench_binary[0].short_path,
+ ctx.attr.num_duts,
),
])
ctx.actions.write(bench, bench_content, is_executable = True)
@@ -51,6 +52,10 @@ _packetimpact_test = rule(
mandatory = False,
default = [],
),
+ "num_duts": attr.int(
+ mandatory = False,
+ default = 1,
+ ),
},
test = True,
implementation = _packetimpact_test_impl,
@@ -110,24 +115,27 @@ def packetimpact_netstack_test(
**kwargs
)
-def packetimpact_go_test(name, expect_native_failure = False, expect_netstack_failure = False):
+def packetimpact_go_test(name, expect_native_failure = False, expect_netstack_failure = False, num_duts = 1):
"""Add packetimpact tests written in go.
Args:
name: name of the test
expect_native_failure: the test must fail natively
expect_netstack_failure: the test must fail for Netstack
+ num_duts: how many DUTs are needed for the test
"""
testbench_binary = name + "_test"
packetimpact_native_test(
name = name,
expect_failure = expect_native_failure,
testbench_binary = testbench_binary,
+ num_duts = num_duts,
)
packetimpact_netstack_test(
name = name,
expect_failure = expect_netstack_failure,
testbench_binary = testbench_binary,
+ num_duts = num_duts,
)
def packetimpact_testbench(name, size = "small", pure = True, **kwargs):
@@ -153,7 +161,7 @@ def packetimpact_testbench(name, size = "small", pure = True, **kwargs):
PacketimpactTestInfo = provider(
doc = "Provide information for packetimpact tests",
- fields = ["name", "expect_netstack_failure"],
+ fields = ["name", "expect_netstack_failure", "num_duts"],
)
ALL_TESTS = [
@@ -216,6 +224,9 @@ ALL_TESTS = [
name = "tcp_user_timeout",
),
PacketimpactTestInfo(
+ name = "tcp_zero_receive_window",
+ ),
+ PacketimpactTestInfo(
name = "tcp_queue_receive_in_syn_sent",
),
PacketimpactTestInfo(
@@ -243,18 +254,21 @@ ALL_TESTS = [
),
PacketimpactTestInfo(
name = "icmpv6_param_problem",
- # TODO(b/153485026): Fix netstack then remove the line below.
- expect_netstack_failure = True,
),
PacketimpactTestInfo(
name = "ipv6_unknown_options_action",
- # TODO(b/159928940): Fix netstack then remove the line below.
- expect_netstack_failure = True,
+ ),
+ PacketimpactTestInfo(
+ name = "ipv4_fragment_reassembly",
),
PacketimpactTestInfo(
name = "ipv6_fragment_reassembly",
),
PacketimpactTestInfo(
+ name = "ipv6_fragment_icmp_error",
+ num_duts = 3,
+ ),
+ PacketimpactTestInfo(
name = "udp_send_recv_dgram",
),
PacketimpactTestInfo(
diff --git a/test/packetimpact/runner/dut.go b/test/packetimpact/runner/dut.go
index 59bb68eb1..3e26c73cb 100644
--- a/test/packetimpact/runner/dut.go
+++ b/test/packetimpact/runner/dut.go
@@ -17,6 +17,7 @@ package runner
import (
"context"
+ "encoding/json"
"flag"
"fmt"
"io/ioutil"
@@ -34,6 +35,7 @@ import (
"github.com/docker/docker/api/types/mount"
"gvisor.dev/gvisor/pkg/test/dockerutil"
"gvisor.dev/gvisor/test/packetimpact/netdevs"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
)
// stringList implements flag.Value.
@@ -56,9 +58,10 @@ var (
tshark = false
extraTestArgs = stringList{}
expectFailure = false
+ numDUTs = 1
- // DutAddr is the IP addres for DUT.
- DutAddr = net.IPv4(0, 0, 0, 10)
+ // DUTAddr is the IP addres for DUT.
+ DUTAddr = net.IPv4(0, 0, 0, 10)
testbenchAddr = net.IPv4(0, 0, 0, 20)
)
@@ -71,10 +74,15 @@ func RegisterFlags(fs *flag.FlagSet) {
fs.BoolVar(&tshark, "tshark", false, "use more verbose tshark in logs instead of tcpdump")
fs.Var(&extraTestArgs, "extra_test_arg", "extra arguments to pass to the testbench")
fs.BoolVar(&expectFailure, "expect_failure", false, "expect that the test will fail when run")
+ fs.IntVar(&numDUTs, "num_duts", numDUTs, "the number of duts to create")
}
-// CtrlPort is the port that posix_server listens on.
-const CtrlPort = "40000"
+const (
+ // CtrlPort is the port that posix_server listens on.
+ CtrlPort uint16 = 40000
+ // testOutputDir is the directory in each container that holds test output.
+ testOutputDir = "/tmp/testoutput"
+)
// logger implements testutil.Logger.
//
@@ -95,16 +103,21 @@ func (l logger) Logf(format string, args ...interface{}) {
}
}
-// TestWithDUT runs a packetimpact test with the given information.
-func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Container) DUT, containerAddr net.IP) {
- if testbenchBinary == "" {
- t.Fatal("--testbench_binary is missing")
- }
- dockerutil.EnsureSupportedDockerVersion()
+// dutInfo encapsulates all the essential information to set up testbench
+// container.
+type dutInfo struct {
+ dut DUT
+ ctrlNet, testNet *dockerutil.Network
+ netInfo *testbench.DUTTestNet
+}
- // 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.
+// setUpDUT will set up one DUT and return information for setting up the
+// container for testbench.
+func setUpDUT(ctx context.Context, t *testing.T, id int, mkDevice func(*dockerutil.Container) DUT) (dutInfo, error) {
+ // 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.
+ var info dutInfo
ctrlNet := dockerutil.NewNetwork(ctx, logger("ctrlNet"))
testNet := dockerutil.NewNetwork(ctx, logger("testNet"))
for _, dn := range []*dockerutil.Network{ctrlNet, testNet} {
@@ -113,8 +126,8 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co
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.
+ // This can fail if another docker network claimed the same IP so we
+ // will just try again.
time.Sleep(wait)
continue
}
@@ -128,115 +141,204 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co
})
// Sanity check.
if inspect, err := dn.Inspect(ctx); err != nil {
- t.Fatalf("failed to inspect network %s: %v", dn.Name, err)
+ return dutInfo{}, fmt.Errorf("failed to inspect network %s: %w", dn.Name, err)
} else if inspect.Name != dn.Name {
- t.Fatalf("name mismatch for network want: %s got: %s", dn.Name, inspect.Name)
+ return dutInfo{}, fmt.Errorf("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)
- }
- t.Cleanup(func() {
- if err := exec.Command("/bin/cp", "-r", tmpDir, os.Getenv("TEST_UNDECLARED_OUTPUTS_DIR")).Run(); err != nil {
- t.Errorf("unable to copy container output files: %s", err)
- }
- if err := os.RemoveAll(tmpDir); err != nil {
- t.Errorf("failed to remove tmpDir %s: %s", tmpDir, err)
- }
- })
-
- const testOutputDir = "/tmp/testoutput"
+ info.ctrlNet = ctrlNet
+ info.testNet = testNet
// Create the Docker container for the DUT.
- var dut *dockerutil.Container
+ var dut DUT
if native {
- dut = dockerutil.MakeNativeContainer(ctx, logger("dut"))
+ dut = mkDevice(dockerutil.MakeNativeContainer(ctx, logger(fmt.Sprintf("dut-%d", id))))
} else {
- dut = dockerutil.MakeContainer(ctx, logger("dut"))
+ dut = mkDevice(dockerutil.MakeContainer(ctx, logger(fmt.Sprintf("dut-%d", id))))
}
- t.Cleanup(func() {
- dut.CleanUp(ctx)
- })
+ info.dut = dut
runOpts := dockerutil.RunOpts{
Image: "packetimpact",
CapAdd: []string{"NET_ADMIN"},
- Mounts: []mount.Mount{{
- Type: mount.TypeBind,
- Source: tmpDir,
- Target: testOutputDir,
- ReadOnly: false,
- }},
+ }
+ if _, err := MountTempDirectory(t, &runOpts, "dut-output", testOutputDir); err != nil {
+ return dutInfo{}, err
}
- device := mkDevice(dut)
- remoteIPv6, remoteMAC, dutDeviceID, dutTestNetDev := device.Prepare(ctx, t, runOpts, ctrlNet, testNet, containerAddr)
+ ipv4PrefixLength, _ := testNet.Subnet.Mask.Size()
+ remoteIPv6, remoteMAC, dutDeviceID, dutTestNetDev, err := dut.Prepare(ctx, t, runOpts, ctrlNet, testNet)
+ if err != nil {
+ return dutInfo{}, err
+ }
+ info.netInfo = &testbench.DUTTestNet{
+ RemoteMAC: remoteMAC,
+ RemoteIPv4: AddressInSubnet(DUTAddr, *testNet.Subnet),
+ RemoteIPv6: remoteIPv6,
+ RemoteDevID: dutDeviceID,
+ RemoteDevName: dutTestNetDev,
+ LocalIPv4: AddressInSubnet(testbenchAddr, *testNet.Subnet),
+ IPv4PrefixLength: ipv4PrefixLength,
+ POSIXServerIP: AddressInSubnet(DUTAddr, *ctrlNet.Subnet),
+ POSIXServerPort: CtrlPort,
+ }
+ return info, nil
+}
- // Create the Docker container for the testbench.
- testbench := dockerutil.MakeNativeContainer(ctx, logger("testbench"))
+// TestWithDUT runs a packetimpact test with the given information.
+func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Container) DUT) {
+ if testbenchBinary == "" {
+ t.Fatal("--testbench_binary is missing")
+ }
+ dockerutil.EnsureSupportedDockerVersion()
- tbb := path.Base(testbenchBinary)
- containerTestbenchBinary := filepath.Join("/packetimpact", tbb)
- testbench.CopyFiles(&runOpts, "/packetimpact", filepath.Join("test/packetimpact/tests", tbb))
-
- // snifferNetDev is a network device on the test orchestrator that we will
- // run sniffer (tcpdump or tshark) on and inject traffic to, not to be
- // confused with the device on the DUT.
- const snifferNetDev = "eth2"
- // 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", snifferNetDev,
- "-w", testOutputDir + "/dump.pcap",
+ dutInfoChan := make(chan dutInfo, numDUTs)
+ errChan := make(chan error, numDUTs)
+ var dockerNetworks []*dockerutil.Network
+ var dutTestNets []*testbench.DUTTestNet
+ var duts []DUT
+
+ setUpCtx, cancelSetup := context.WithCancel(ctx)
+ t.Cleanup(cancelSetup)
+ for i := 0; i < numDUTs; i++ {
+ go func(i int) {
+ info, err := setUpDUT(setUpCtx, t, i, mkDevice)
+ if err != nil {
+ errChan <- err
+ } else {
+ dutInfoChan <- info
+ }
+ }(i)
}
- 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", snifferNetDev,
- "-o", "tcp.check_checksum:TRUE",
- "-o", "udp.check_checksum:TRUE",
+ for i := 0; i < numDUTs; i++ {
+ select {
+ case info := <-dutInfoChan:
+ dockerNetworks = append(dockerNetworks, info.ctrlNet, info.testNet)
+ dutTestNets = append(dutTestNets, info.netInfo)
+ duts = append(duts, info.dut)
+ case err := <-errChan:
+ t.Fatal(err)
}
- snifferRegex = "Capturing on.*\n"
}
+ // Create the Docker container for the testbench.
+ testbenchContainer := dockerutil.MakeNativeContainer(ctx, logger("testbench"))
+
+ runOpts := dockerutil.RunOpts{
+ Image: "packetimpact",
+ CapAdd: []string{"NET_ADMIN"},
+ }
+ if _, err := MountTempDirectory(t, &runOpts, "testbench-output", testOutputDir); err != nil {
+ t.Fatal(err)
+ }
+ tbb := path.Base(testbenchBinary)
+ containerTestbenchBinary := filepath.Join("/packetimpact", tbb)
+ testbenchContainer.CopyFiles(&runOpts, "/packetimpact", filepath.Join("test/packetimpact/tests", tbb))
+
if err := StartContainer(
ctx,
runOpts,
- testbench,
+ testbenchContainer,
testbenchAddr,
- []*dockerutil.Network{ctrlNet, testNet},
- snifferArgs...,
+ dockerNetworks,
+ "tail", "-f", "/dev/null",
); err != nil {
- t.Fatalf("failed to start docker container for testbench sniffer: %s", err)
+ t.Fatalf("cannot start testbench container: %s", err)
}
- // Kill so that it will flush output.
- t.Cleanup(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)
+ for i := range dutTestNets {
+ name, info, err := deviceByIP(ctx, testbenchContainer, dutTestNets[i].LocalIPv4)
+ if err != nil {
+ t.Fatalf("failed to get the device name associated with %s: %s", dutTestNets[i].LocalIPv4, err)
+ }
+ dutTestNets[i].LocalDevName = name
+ dutTestNets[i].LocalDevID = info.ID
+ dutTestNets[i].LocalMAC = info.MAC
+ localIPv6, err := getOrAssignIPv6Addr(ctx, testbenchContainer, name)
+ if err != nil {
+ t.Fatalf("failed to get IPV6 address on %s: %s", testbenchContainer.Name, err)
+ }
+ dutTestNets[i].LocalIPv6 = localIPv6
+ }
+ dutTestNetsBytes, err := json.Marshal(dutTestNets)
+ if err != nil {
+ t.Fatalf("failed to marshal %v into json: %s", dutTestNets, err)
}
- // When the Linux kernel receives a SYN-ACK for a SYN it didn't send, it
- // will respond with an RST. In most packetimpact tests, the SYN is sent
- // by the raw socket and the kernel knows nothing about the connection, this
- // behavior will break lots of TCP related packetimpact tests. To prevent
- // this, we can install the following iptables rules. The raw socket that
- // packetimpact tests use will still be able to see everything.
- for _, bin := range []string{"iptables", "ip6tables"} {
- if logs, err := testbench.Exec(ctx, dockerutil.ExecOpts{}, bin, "-A", "INPUT", "-i", snifferNetDev, "-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)
+ baseSnifferArgs := []string{
+ "tcpdump",
+ "-vvv",
+ "--absolute-tcp-sequence-numbers",
+ "--packet-buffered",
+ // Disable DNS resolution.
+ "-n",
+ // run tcpdump as root since the output directory is owned by root. From
+ // `man tcpdump`:
+ //
+ // -Z user
+ // --relinquish-privileges=user
+ // If tcpdump is running as root, after opening the capture device
+ // or input savefile, change the user ID to user and the group ID to
+ // the primary group of user.
+ // This behavior is enabled by default (-Z tcpdump), and can be
+ // disabled by -Z root.
+ "-Z", "root",
+ }
+ if tshark {
+ baseSnifferArgs = []string{
+ "tshark",
+ "-V",
+ "-o", "tcp.check_checksum:TRUE",
+ "-o", "udp.check_checksum:TRUE",
+ // Disable buffering.
+ "-l",
+ // Disable DNS resolution.
+ "-n",
+ }
+ }
+ for _, n := range dutTestNets {
+ snifferArgs := append(baseSnifferArgs, "-i", n.LocalDevName)
+ if !tshark {
+ snifferArgs = append(
+ snifferArgs,
+ "-w",
+ filepath.Join(testOutputDir, fmt.Sprintf("%s.pcap", n.LocalDevName)),
+ )
+ }
+ p, err := testbenchContainer.ExecProcess(ctx, dockerutil.ExecOpts{}, snifferArgs...)
+ if err != nil {
+ t.Fatalf("failed to start exec a sniffer on %s: %s", n.LocalDevName, err)
+ }
+ t.Cleanup(func() {
+ if snifferOut, err := p.Logs(); err != nil {
+ t.Errorf("sniffer logs failed: %s\n%s", err, snifferOut)
+ } else {
+ t.Logf("sniffer logs:\n%s", snifferOut)
+ }
+ })
+ // When the Linux kernel receives a SYN-ACK for a SYN it didn't send, it
+ // will respond with an RST. In most packetimpact tests, the SYN is sent
+ // by the raw socket, the kernel knows nothing about the connection, this
+ // behavior will break lots of TCP related packetimpact tests. To prevent
+ // this, we can install the following iptables rules. The raw socket that
+ // packetimpact tests use will still be able to see everything.
+ for _, bin := range []string{"iptables", "ip6tables"} {
+ if logs, err := testbenchContainer.Exec(ctx, dockerutil.ExecOpts{}, bin, "-A", "INPUT", "-i", n.LocalDevName, "-p", "tcp", "-j", "DROP"); err != nil {
+ t.Fatalf("unable to Exec %s on container %s: %s, logs from testbench:\n%s", bin, testbenchContainer.Name, err, logs)
+ }
}
}
+ t.Cleanup(func() {
+ // Wait 1 second before killing tcpdump to give it time to flush
+ // any packets. On linux tests killing it immediately can
+ // sometimes result in partial pcaps.
+ time.Sleep(1 * time.Second)
+ if logs, err := testbenchContainer.Exec(ctx, dockerutil.ExecOpts{}, "killall", baseSnifferArgs[0]); err != nil {
+ t.Errorf("failed to kill all sniffers: %s, logs: %s", 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.
@@ -248,31 +350,29 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co
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", dutDeviceID),
- "--local_device", snifferNetDev,
- "--remote_device", dutTestNetDev,
fmt.Sprintf("--native=%t", native),
+ "--dut_test_nets_json", string(dutTestNetsBytes),
)
- testbenchLogs, err := testbench.Exec(ctx, dockerutil.ExecOpts{}, testArgs...)
+ testbenchLogs, err := testbenchContainer.Exec(ctx, dockerutil.ExecOpts{}, testArgs...)
if (err != nil) != expectFailure {
var dutLogs string
- if logs, err := device.Logs(ctx); err != nil {
- dutLogs = fmt.Sprintf("failed to fetch DUT logs: %s", err)
- } else {
- dutLogs = logs
+ for i, dut := range duts {
+ logs, err := dut.Logs(ctx)
+ if err != nil {
+ logs = fmt.Sprintf("failed to fetch DUT logs: %s", err)
+ }
+ dutLogs = fmt.Sprintf(`%s====== Begin of DUT-%d Logs ======
+
+%s
+
+====== End of DUT-%d Logs ======
+
+`, dutLogs, i, logs, i)
}
t.Errorf(`test error: %v, expect failure: %t
-%s
-
-====== Begin of Testbench Logs ======
+%s====== Begin of Testbench Logs ======
%s
@@ -285,7 +385,9 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co
type DUT interface {
// Prepare prepares the dut, starts posix_server and returns the IPv6, MAC
// address, the interface ID, and the interface name for the testNet on DUT.
- Prepare(ctx context.Context, t *testing.T, runOpts dockerutil.RunOpts, ctrlNet, testNet *dockerutil.Network, containerAddr net.IP) (net.IP, net.HardwareAddr, uint32, string)
+ // The t parameter is supposed to be used for t.Cleanup. Don't use it for
+ // t.Fatal/FailNow functions.
+ Prepare(ctx context.Context, t *testing.T, runOpts dockerutil.RunOpts, ctrlNet, testNet *dockerutil.Network) (net.IP, net.HardwareAddr, uint32, string, error)
// Logs retrieves the logs from the dut.
Logs(ctx context.Context) (string, error)
}
@@ -303,7 +405,7 @@ func NewDockerDUT(c *dockerutil.Container) DUT {
}
// Prepare implements DUT.Prepare.
-func (dut *DockerDUT) Prepare(ctx context.Context, t *testing.T, runOpts dockerutil.RunOpts, ctrlNet, testNet *dockerutil.Network, containerAddr net.IP) (net.IP, net.HardwareAddr, uint32, string) {
+func (dut *DockerDUT) Prepare(ctx context.Context, _ *testing.T, runOpts dockerutil.RunOpts, ctrlNet, testNet *dockerutil.Network) (net.IP, net.HardwareAddr, uint32, string, error) {
const containerPosixServerBinary = "/packetimpact/posix_server"
dut.c.CopyFiles(&runOpts, "/packetimpact", "test/packetimpact/dut/posix_server")
@@ -311,45 +413,31 @@ func (dut *DockerDUT) Prepare(ctx context.Context, t *testing.T, runOpts dockeru
ctx,
runOpts,
dut.c,
- containerAddr,
+ DUTAddr,
[]*dockerutil.Network{ctrlNet, testNet},
containerPosixServerBinary,
"--ip=0.0.0.0",
- "--port="+CtrlPort,
+ fmt.Sprintf("--port=%d", CtrlPort),
); err != nil {
- t.Fatalf("failed to start docker container for DUT: %s", err)
+ return nil, nil, 0, "", fmt.Errorf("failed to start docker container for DUT: %w", err)
}
if _, err := dut.c.WaitForOutput(ctx, "Server listening.*\n", 60*time.Second); err != nil {
- t.Fatalf("%s on container %s never listened: %s", containerPosixServerBinary, dut.c.Name, err)
+ return nil, nil, 0, "", fmt.Errorf("%s on container %s never listened: %s", containerPosixServerBinary, dut.c.Name, err)
}
- dutTestDevice, dutDeviceInfo, err := deviceByIP(ctx, dut.c, AddressInSubnet(containerAddr, *testNet.Subnet))
+ dutTestDevice, dutDeviceInfo, err := deviceByIP(ctx, dut.c, AddressInSubnet(DUTAddr, *testNet.Subnet))
if err != nil {
- t.Fatal(err)
+ return nil, nil, 0, "", 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.c.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.c.Name, err)
- }
- // Now try again, to make sure that it worked.
- _, dutDeviceInfo, err = deviceByIP(ctx, dut.c, AddressInSubnet(containerAddr, *testNet.Subnet))
- if err != nil {
- t.Fatal(err)
- }
- remoteIPv6 = dutDeviceInfo.IPv6Addr
- if remoteIPv6 == nil {
- t.Fatalf("unable to set IPv6 address on container %s", dut.c.Name)
- }
+ remoteIPv6, err := getOrAssignIPv6Addr(ctx, dut.c, dutTestDevice)
+ if err != nil {
+ return nil, nil, 0, "", fmt.Errorf("failed to get IPv6 address on %s: %s", dut.c.Name, err)
}
const testNetDev = "eth2"
- return remoteIPv6, dutDeviceInfo.MAC, dutDeviceInfo.ID, testNetDev
+ return remoteIPv6, dutDeviceInfo.MAC, dutDeviceInfo.ID, testNetDev, nil
}
// Logs implements DUT.Logs.
@@ -358,11 +446,7 @@ func (dut *DockerDUT) Logs(ctx context.Context) (string, error) {
if err != nil {
return "", err
}
- return fmt.Sprintf(`====== Begin of DUT Logs ======
-
-%s
-
-====== End of DUT Logs ======`, logs), nil
+ return logs, nil
}
// AddNetworks connects docker network with the container and assigns the specific IP.
@@ -378,25 +462,35 @@ func AddNetworks(ctx context.Context, d *dockerutil.Container, addr net.IP, netw
}
// 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.
+// 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
+ var octets net.IP
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)
+ return octets
}
-// 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) {
+// devicesInfo will run "ip addr show" on the container and parse the output
+// to a map[string]netdevs.DeviceInfo.
+func devicesInfo(ctx context.Context, d *dockerutil.Container) (map[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\n%s", d.Name, err, out)
+ return map[string]netdevs.DeviceInfo{}, fmt.Errorf("listing devices on %s container: %w\n%s", d.Name, err, out)
}
devs, err := netdevs.ParseDevices(out)
if err != nil {
- return "", netdevs.DeviceInfo{}, fmt.Errorf("parsing devices from %s container: %w\n%s", d.Name, err, out)
+ return map[string]netdevs.DeviceInfo{}, fmt.Errorf("parsing devices from %s container: %w\n%s", d.Name, err, out)
+ }
+ return devs, nil
+}
+
+// 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) {
+ devs, err := devicesInfo(ctx, d)
+ if err != nil {
+ return "", netdevs.DeviceInfo{}, err
}
testDevice, deviceInfo, err := netdevs.FindDeviceByIP(ip, devs)
if err != nil {
@@ -405,6 +499,36 @@ func deviceByIP(ctx context.Context, d *dockerutil.Container, ip net.IP) (string
return testDevice, deviceInfo, nil
}
+// getOrAssignIPv6Addr will try to get the IPv6 address for the interface; if an
+// address was not assigned, a link-local address based on MAC will be assigned
+// to that interface.
+func getOrAssignIPv6Addr(ctx context.Context, d *dockerutil.Container, iface string) (net.IP, error) {
+ devs, err := devicesInfo(ctx, d)
+ if err != nil {
+ return net.IP{}, err
+ }
+ info := devs[iface]
+ if info.IPv6Addr != nil {
+ return info.IPv6Addr, nil
+ }
+ if info.MAC == nil {
+ return nil, fmt.Errorf("unable to find MAC address of %s", iface)
+ }
+ if logs, err := d.Exec(ctx, dockerutil.ExecOpts{}, "ip", "addr", "add", netdevs.MACToIP(info.MAC).String(), "scope", "link", "dev", iface); err != nil {
+ return net.IP{}, fmt.Errorf("unable to ip addr add on container %s: %w, logs: %s", d.Name, err, logs)
+ }
+ // Now try again, to make sure that it worked.
+ devs, err = devicesInfo(ctx, d)
+ if err != nil {
+ return net.IP{}, err
+ }
+ info = devs[iface]
+ if info.IPv6Addr == nil {
+ return net.IP{}, fmt.Errorf("unable to set IPv6 address on container %s", d.Name)
+ }
+ return info.IPv6Addr, nil
+}
+
// 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 {
@@ -440,3 +564,30 @@ func StartContainer(ctx context.Context, runOpts dockerutil.RunOpts, c *dockerut
}
return nil
}
+
+// MountTempDirectory creates a temporary directory on host with the template
+// and then mounts it into the container under the name provided. The temporary
+// directory name is returned. Content in that directory will be copied to
+// TEST_UNDECLARED_OUTPUTS_DIR in cleanup phase.
+func MountTempDirectory(t *testing.T, runOpts *dockerutil.RunOpts, hostDirTemplate, containerDir string) (string, error) {
+ t.Helper()
+ tmpDir, err := ioutil.TempDir("", hostDirTemplate)
+ if err != nil {
+ return "", fmt.Errorf("failed to create a temp dir: %w", err)
+ }
+ t.Cleanup(func() {
+ if err := exec.Command("/bin/cp", "-r", tmpDir, os.Getenv("TEST_UNDECLARED_OUTPUTS_DIR")).Run(); err != nil {
+ t.Errorf("unable to copy container output files: %s", err)
+ }
+ if err := os.RemoveAll(tmpDir); err != nil {
+ t.Errorf("failed to remove tmpDir %s: %s", tmpDir, err)
+ }
+ })
+ runOpts.Mounts = append(runOpts.Mounts, mount.Mount{
+ Type: mount.TypeBind,
+ Source: tmpDir,
+ Target: containerDir,
+ ReadOnly: false,
+ })
+ return tmpDir, nil
+}
diff --git a/test/packetimpact/runner/packetimpact_test.go b/test/packetimpact/runner/packetimpact_test.go
index c598bfc29..46334b7ab 100644
--- a/test/packetimpact/runner/packetimpact_test.go
+++ b/test/packetimpact/runner/packetimpact_test.go
@@ -28,5 +28,5 @@ func init() {
}
func TestOne(t *testing.T) {
- runner.TestWithDUT(context.Background(), t, runner.NewDockerDUT, runner.DutAddr)
+ runner.TestWithDUT(context.Background(), t, runner.NewDockerDUT)
}
diff --git a/test/packetimpact/testbench/BUILD b/test/packetimpact/testbench/BUILD
index 5a0ee1367..983c2c030 100644
--- a/test/packetimpact/testbench/BUILD
+++ b/test/packetimpact/testbench/BUILD
@@ -21,7 +21,6 @@ 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",
diff --git a/test/packetimpact/testbench/connections.go b/test/packetimpact/testbench/connections.go
index a90046f69..576577310 100644
--- a/test/packetimpact/testbench/connections.go
+++ b/test/packetimpact/testbench/connections.go
@@ -12,14 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Package testbench has utilities to send and receive packets and also command
-// the DUT to run POSIX functions.
package testbench
import (
"fmt"
"math/rand"
- "net"
"testing"
"time"
@@ -44,7 +41,7 @@ func portFromSockaddr(sa unix.Sockaddr) (uint16, error) {
// 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, port uint16, err error) {
+func (n *DUTTestNet) pickPort(domain, typ int) (fd int, port uint16, err error) {
fd, err = unix.Socket(domain, typ, 0)
if err != nil {
return -1, 0, fmt.Errorf("creating socket: %w", err)
@@ -60,11 +57,11 @@ func pickPort(domain, typ int) (fd int, port uint16, err error) {
switch domain {
case unix.AF_INET:
var sa4 unix.SockaddrInet4
- copy(sa4.Addr[:], net.ParseIP(LocalIPv4).To4())
+ copy(sa4.Addr[:], n.LocalIPv4)
sa = &sa4
case unix.AF_INET6:
- sa6 := unix.SockaddrInet6{ZoneId: uint32(LocalInterfaceID)}
- copy(sa6.Addr[:], net.ParseIP(LocalIPv6).To16())
+ sa6 := unix.SockaddrInet6{ZoneId: n.LocalDevID}
+ copy(sa6.Addr[:], n.LocalIPv6)
sa = &sa6
default:
return -1, 0, fmt.Errorf("invalid domain %d, it should be one of unix.AF_INET or unix.AF_INET6", domain)
@@ -74,7 +71,7 @@ func pickPort(domain, typ int) (fd int, port uint16, err error) {
}
sa, err = unix.Getsockname(fd)
if err != nil {
- return -1, 0, fmt.Errorf("Getsocketname(%d): %w", fd, err)
+ return -1, 0, fmt.Errorf("unix.Getsocketname(%d): %w", fd, err)
}
port, err = portFromSockaddr(sa)
if err != nil {
@@ -102,9 +99,9 @@ type layerState interface {
// as it was sent is available.
sent(sent Layer) error
- // received updates the layerState based on a Layer that is receieved. The
+ // received updates the layerState based on a Layer that is received. The
// input is a Layer with all prev and next pointers populated so that the
- // entire frame as it was receieved is available.
+ // entire frame as it was received is available.
received(received Layer) error
// close frees associated resources held by the LayerState.
@@ -119,19 +116,12 @@ type etherState struct {
var _ layerState = (*etherState)(nil)
// newEtherState creates a new etherState.
-func newEtherState(out, in Ether) (*etherState, error) {
- lMAC, err := tcpip.ParseMACAddress(LocalMAC)
- if err != nil {
- return nil, fmt.Errorf("parsing local MAC: %q: %w", LocalMAC, err)
- }
-
- rMAC, err := tcpip.ParseMACAddress(RemoteMAC)
- if err != nil {
- return nil, fmt.Errorf("parsing remote MAC: %q: %w", RemoteMAC, err)
- }
+func (n *DUTTestNet) newEtherState(out, in Ether) (*etherState, error) {
+ lmac := tcpip.LinkAddress(n.LocalMAC)
+ rmac := tcpip.LinkAddress(n.RemoteMAC)
s := etherState{
- out: Ether{SrcAddr: &lMAC, DstAddr: &rMAC},
- in: Ether{SrcAddr: &rMAC, DstAddr: &lMAC},
+ out: Ether{SrcAddr: &lmac, DstAddr: &rmac},
+ in: Ether{SrcAddr: &rmac, DstAddr: &lmac},
}
if err := s.out.merge(&out); err != nil {
return nil, err
@@ -171,9 +161,9 @@ type ipv4State struct {
var _ layerState = (*ipv4State)(nil)
// newIPv4State creates a new ipv4State.
-func newIPv4State(out, in IPv4) (*ipv4State, error) {
- lIP := tcpip.Address(net.ParseIP(LocalIPv4).To4())
- rIP := tcpip.Address(net.ParseIP(RemoteIPv4).To4())
+func (n *DUTTestNet) newIPv4State(out, in IPv4) (*ipv4State, error) {
+ lIP := tcpip.Address(n.LocalIPv4)
+ rIP := tcpip.Address(n.RemoteIPv4)
s := ipv4State{
out: IPv4{SrcAddr: &lIP, DstAddr: &rIP},
in: IPv4{SrcAddr: &rIP, DstAddr: &lIP},
@@ -216,9 +206,9 @@ type ipv6State struct {
var _ layerState = (*ipv6State)(nil)
// newIPv6State creates a new ipv6State.
-func newIPv6State(out, in IPv6) (*ipv6State, error) {
- lIP := tcpip.Address(net.ParseIP(LocalIPv6).To16())
- rIP := tcpip.Address(net.ParseIP(RemoteIPv6).To16())
+func (n *DUTTestNet) newIPv6State(out, in IPv6) (*ipv6State, error) {
+ lIP := tcpip.Address(n.LocalIPv6)
+ rIP := tcpip.Address(n.RemoteIPv6)
s := ipv6State{
out: IPv6{SrcAddr: &lIP, DstAddr: &rIP},
in: IPv6{SrcAddr: &rIP, DstAddr: &lIP},
@@ -274,8 +264,8 @@ func SeqNumValue(v seqnum.Value) *seqnum.Value {
}
// newTCPState creates a new TCPState.
-func newTCPState(domain int, out, in TCP) (*tcpState, error) {
- portPickerFD, localPort, err := pickPort(domain, unix.SOCK_STREAM)
+func (n *DUTTestNet) newTCPState(domain int, out, in TCP) (*tcpState, error) {
+ portPickerFD, localPort, err := n.pickPort(domain, unix.SOCK_STREAM)
if err != nil {
return nil, err
}
@@ -316,11 +306,11 @@ func (s *tcpState) incoming(received Layer) Layer {
if s.remoteSeqNum != nil {
newIn.SeqNum = Uint32(uint32(*s.remoteSeqNum))
}
- if s.localSeqNum != nil && (*tcpReceived.Flags&header.TCPFlagAck) != 0 {
+ if seq, flags := s.localSeqNum, tcpReceived.Flags; seq != nil && flags != nil && *flags&header.TCPFlagAck != 0 {
// The caller didn't specify an AckNum so we'll expect the calculated one,
// but only if the ACK flag is set because the AckNum is not valid in a
// header if ACK is not set.
- newIn.AckNum = Uint32(uint32(*s.localSeqNum))
+ newIn.AckNum = Uint32(uint32(*seq))
}
return &newIn
}
@@ -378,8 +368,8 @@ type udpState struct {
var _ layerState = (*udpState)(nil)
// newUDPState creates a new udpState.
-func newUDPState(domain int, out, in UDP) (*udpState, error) {
- portPickerFD, localPort, err := pickPort(domain, unix.SOCK_DGRAM)
+func (n *DUTTestNet) newUDPState(domain int, out, in UDP) (*udpState, error) {
+ portPickerFD, localPort, err := n.pickPort(domain, unix.SOCK_DGRAM)
if err != nil {
return nil, fmt.Errorf("picking port: %w", err)
}
@@ -475,12 +465,12 @@ func (conn *Connection) Close(t *testing.T) {
}
}
-// CreateFrame builds a frame for the connection with defaults overriden
+// CreateFrame builds a frame for the connection with defaults overridden
// 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-
+// overridden 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 {
@@ -608,14 +598,14 @@ func (conn *Connection) ExpectFrame(t *testing.T, layers Layers, timeout time.Du
var errs error
for {
var gotLayers Layers
- if timeout = time.Until(deadline); timeout > 0 {
+ if timeout := time.Until(deadline); timeout > 0 {
gotLayers = conn.recvFrame(t, timeout)
}
if gotLayers == nil {
if errs == nil {
- return nil, fmt.Errorf("got no frames matching %v during %s", layers, timeout)
+ return nil, fmt.Errorf("got no frames matching %s during %s", layers, timeout)
}
- return nil, fmt.Errorf("got frames %w want %v during %s", errs, layers, timeout)
+ return nil, fmt.Errorf("got frames:\n%w want %s during %s", errs, layers, timeout)
}
if conn.match(layers, gotLayers) {
for i, s := range conn.layerStates {
@@ -625,7 +615,12 @@ func (conn *Connection) ExpectFrame(t *testing.T, layers Layers, timeout time.Du
}
return gotLayers, nil
}
- errs = multierr.Combine(errs, &layersError{got: gotLayers, want: conn.incoming(gotLayers)})
+ want := conn.incoming(layers)
+ if err := want.merge(layers); err != nil {
+ errs = multierr.Combine(errs, err)
+ } else {
+ errs = multierr.Combine(errs, &layersError{got: gotLayers, want: want})
+ }
}
}
@@ -641,26 +636,26 @@ func (conn *Connection) Drain(t *testing.T) {
type TCPIPv4 Connection
// NewTCPIPv4 creates a new TCPIPv4 connection with reasonable defaults.
-func NewTCPIPv4(t *testing.T, outgoingTCP, incomingTCP TCP) TCPIPv4 {
+func (n *DUTTestNet) NewTCPIPv4(t *testing.T, outgoingTCP, incomingTCP TCP) TCPIPv4 {
t.Helper()
- etherState, err := newEtherState(Ether{}, Ether{})
+ etherState, err := n.newEtherState(Ether{}, Ether{})
if err != nil {
t.Fatalf("can't make etherState: %s", err)
}
- ipv4State, err := newIPv4State(IPv4{}, IPv4{})
+ ipv4State, err := n.newIPv4State(IPv4{}, IPv4{})
if err != nil {
t.Fatalf("can't make ipv4State: %s", err)
}
- tcpState, err := newTCPState(unix.AF_INET, outgoingTCP, incomingTCP)
+ tcpState, err := n.newTCPState(unix.AF_INET, outgoingTCP, incomingTCP)
if err != nil {
t.Fatalf("can't make tcpState: %s", err)
}
- injector, err := NewInjector(t)
+ injector, err := n.NewInjector(t)
if err != nil {
t.Fatalf("can't make injector: %s", err)
}
- sniffer, err := NewSniffer(t)
+ sniffer, err := n.NewSniffer(t)
if err != nil {
t.Fatalf("can't make sniffer: %s", err)
}
@@ -711,7 +706,7 @@ func (conn *TCPIPv4) ConnectWithOptions(t *testing.T, options []byte) {
}
// ExpectData is a convenient method that expects a Layer and the Layer after
-// it. If it doens't arrive in time, it returns nil.
+// it. If it doesn't arrive in time, it returns nil.
func (conn *TCPIPv4) ExpectData(t *testing.T, tcp *TCP, payload *Payload, timeout time.Duration) (Layers, error) {
t.Helper()
@@ -839,27 +834,82 @@ func (conn *TCPIPv4) Drain(t *testing.T) {
conn.sniffer.Drain(t)
}
+// IPv4Conn maintains the state for all the layers in a IPv4 connection.
+type IPv4Conn Connection
+
+// NewIPv4Conn creates a new IPv4Conn connection with reasonable defaults.
+func (n *DUTTestNet) NewIPv4Conn(t *testing.T, outgoingIPv4, incomingIPv4 IPv4) IPv4Conn {
+ t.Helper()
+
+ etherState, err := n.newEtherState(Ether{}, Ether{})
+ if err != nil {
+ t.Fatalf("can't make EtherState: %s", err)
+ }
+ ipv4State, err := n.newIPv4State(outgoingIPv4, incomingIPv4)
+ if err != nil {
+ t.Fatalf("can't make IPv4State: %s", err)
+ }
+
+ injector, err := n.NewInjector(t)
+ if err != nil {
+ t.Fatalf("can't make injector: %s", err)
+ }
+ sniffer, err := n.NewSniffer(t)
+ if err != nil {
+ t.Fatalf("can't make sniffer: %s", err)
+ }
+
+ return IPv4Conn{
+ layerStates: []layerState{etherState, ipv4State},
+ injector: injector,
+ sniffer: sniffer,
+ }
+}
+
+// Send sends a frame with ipv4 overriding the IPv4 layer defaults and
+// additionalLayers added after it.
+func (c *IPv4Conn) Send(t *testing.T, ipv4 IPv4, additionalLayers ...Layer) {
+ t.Helper()
+
+ (*Connection)(c).send(t, Layers{&ipv4}, additionalLayers...)
+}
+
+// Close cleans up any resources held.
+func (c *IPv4Conn) Close(t *testing.T) {
+ t.Helper()
+
+ (*Connection)(c).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 (c *IPv4Conn) ExpectFrame(t *testing.T, frame Layers, timeout time.Duration) (Layers, error) {
+ t.Helper()
+
+ return (*Connection)(c).ExpectFrame(t, frame, timeout)
+}
+
// IPv6Conn maintains the state for all the layers in a IPv6 connection.
type IPv6Conn Connection
// NewIPv6Conn creates a new IPv6Conn connection with reasonable defaults.
-func NewIPv6Conn(t *testing.T, outgoingIPv6, incomingIPv6 IPv6) IPv6Conn {
+func (n *DUTTestNet) NewIPv6Conn(t *testing.T, outgoingIPv6, incomingIPv6 IPv6) IPv6Conn {
t.Helper()
- etherState, err := newEtherState(Ether{}, Ether{})
+ etherState, err := n.newEtherState(Ether{}, Ether{})
if err != nil {
t.Fatalf("can't make EtherState: %s", err)
}
- ipv6State, err := newIPv6State(outgoingIPv6, incomingIPv6)
+ ipv6State, err := n.newIPv6State(outgoingIPv6, incomingIPv6)
if err != nil {
t.Fatalf("can't make IPv6State: %s", err)
}
- injector, err := NewInjector(t)
+ injector, err := n.NewInjector(t)
if err != nil {
t.Fatalf("can't make injector: %s", err)
}
- sniffer, err := NewSniffer(t)
+ sniffer, err := n.NewSniffer(t)
if err != nil {
t.Fatalf("can't make sniffer: %s", err)
}
@@ -898,26 +948,26 @@ func (conn *IPv6Conn) ExpectFrame(t *testing.T, frame Layers, timeout time.Durat
type UDPIPv4 Connection
// NewUDPIPv4 creates a new UDPIPv4 connection with reasonable defaults.
-func NewUDPIPv4(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv4 {
+func (n *DUTTestNet) NewUDPIPv4(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv4 {
t.Helper()
- etherState, err := newEtherState(Ether{}, Ether{})
+ etherState, err := n.newEtherState(Ether{}, Ether{})
if err != nil {
t.Fatalf("can't make etherState: %s", err)
}
- ipv4State, err := newIPv4State(IPv4{}, IPv4{})
+ ipv4State, err := n.newIPv4State(IPv4{}, IPv4{})
if err != nil {
t.Fatalf("can't make ipv4State: %s", err)
}
- udpState, err := newUDPState(unix.AF_INET, outgoingUDP, incomingUDP)
+ udpState, err := n.newUDPState(unix.AF_INET, outgoingUDP, incomingUDP)
if err != nil {
t.Fatalf("can't make udpState: %s", err)
}
- injector, err := NewInjector(t)
+ injector, err := n.NewInjector(t)
if err != nil {
t.Fatalf("can't make injector: %s", err)
}
- sniffer, err := NewSniffer(t)
+ sniffer, err := n.NewSniffer(t)
if err != nil {
t.Fatalf("can't make sniffer: %s", err)
}
@@ -991,7 +1041,7 @@ func (conn *UDPIPv4) Expect(t *testing.T, udp UDP, timeout time.Duration) (*UDP,
}
// ExpectData is a convenient method that expects a Layer and the Layer after
-// it. If it doens't arrive in time, it returns nil.
+// it. If it doesn'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()
@@ -1022,26 +1072,26 @@ func (conn *UDPIPv4) Drain(t *testing.T) {
type UDPIPv6 Connection
// NewUDPIPv6 creates a new UDPIPv6 connection with reasonable defaults.
-func NewUDPIPv6(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv6 {
+func (n *DUTTestNet) NewUDPIPv6(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv6 {
t.Helper()
- etherState, err := newEtherState(Ether{}, Ether{})
+ etherState, err := n.newEtherState(Ether{}, Ether{})
if err != nil {
t.Fatalf("can't make etherState: %s", err)
}
- ipv6State, err := newIPv6State(IPv6{}, IPv6{})
+ ipv6State, err := n.newIPv6State(IPv6{}, IPv6{})
if err != nil {
t.Fatalf("can't make IPv6State: %s", err)
}
- udpState, err := newUDPState(unix.AF_INET6, outgoingUDP, incomingUDP)
+ udpState, err := n.newUDPState(unix.AF_INET6, outgoingUDP, incomingUDP)
if err != nil {
t.Fatalf("can't make udpState: %s", err)
}
- injector, err := NewInjector(t)
+ injector, err := n.NewInjector(t)
if err != nil {
t.Fatalf("can't make injector: %s", err)
}
- sniffer, err := NewSniffer(t)
+ sniffer, err := n.NewSniffer(t)
if err != nil {
t.Fatalf("can't make sniffer: %s", err)
}
@@ -1073,14 +1123,14 @@ func (conn *UDPIPv6) ipv6State(t *testing.T) *ipv6State {
}
// LocalAddr gets the local socket address of this connection.
-func (conn *UDPIPv6) LocalAddr(t *testing.T) *unix.SockaddrInet6 {
+func (conn *UDPIPv6) LocalAddr(t *testing.T, zoneID uint32) *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),
+ ZoneId: zoneID,
}
copy(sa.Addr[:], *conn.ipv6State(t).out.SrcAddr)
return sa
@@ -1119,7 +1169,7 @@ func (conn *UDPIPv6) Expect(t *testing.T, udp UDP, timeout time.Duration) (*UDP,
}
// ExpectData is a convenient method that expects a Layer and the Layer after
-// it. If it doens't arrive in time, it returns nil.
+// it. If it doesn't arrive in time, it returns nil.
func (conn *UDPIPv6) ExpectData(t *testing.T, udp UDP, payload Payload, timeout time.Duration) (Layers, error) {
t.Helper()
@@ -1150,24 +1200,24 @@ func (conn *UDPIPv6) Drain(t *testing.T) {
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{})
+func (n *DUTTestNet) NewTCPIPv6(t *testing.T, outgoingTCP, incomingTCP TCP) TCPIPv6 {
+ etherState, err := n.newEtherState(Ether{}, Ether{})
if err != nil {
t.Fatalf("can't make etherState: %s", err)
}
- ipv6State, err := newIPv6State(IPv6{}, IPv6{})
+ ipv6State, err := n.newIPv6State(IPv6{}, IPv6{})
if err != nil {
t.Fatalf("can't make ipv6State: %s", err)
}
- tcpState, err := newTCPState(unix.AF_INET6, outgoingTCP, incomingTCP)
+ tcpState, err := n.newTCPState(unix.AF_INET6, outgoingTCP, incomingTCP)
if err != nil {
t.Fatalf("can't make tcpState: %s", err)
}
- injector, err := NewInjector(t)
+ injector, err := n.NewInjector(t)
if err != nil {
t.Fatalf("can't make injector: %s", err)
}
- sniffer, err := NewSniffer(t)
+ sniffer, err := n.NewSniffer(t)
if err != nil {
t.Fatalf("can't make sniffer: %s", err)
}
@@ -1179,13 +1229,14 @@ func NewTCPIPv6(t *testing.T, outgoingTCP, incomingTCP TCP) TCPIPv6 {
}
}
+// SrcPort returns the source port from the given Connection.
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.
+// it. If it doesn'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()
diff --git a/test/packetimpact/testbench/dut.go b/test/packetimpact/testbench/dut.go
index 6165ab293..66a0255b8 100644
--- a/test/packetimpact/testbench/dut.go
+++ b/test/packetimpact/testbench/dut.go
@@ -17,9 +17,8 @@ package testbench
import (
"context"
"encoding/binary"
- "flag"
+ "fmt"
"net"
- "strconv"
"syscall"
"testing"
"time"
@@ -35,18 +34,26 @@ import (
type DUT struct {
conn *grpc.ClientConn
posixServer POSIXClient
+ Net *DUTTestNet
}
// NewDUT creates a new connection with the DUT over gRPC.
func NewDUT(t *testing.T) DUT {
t.Helper()
+ n := GetDUTTestNet()
+ dut := n.ConnectToDUT(t)
+ t.Cleanup(func() {
+ dut.TearDownConnection()
+ dut.Net.Release()
+ })
+ return dut
+}
- flag.Parse()
- if err := genPseudoFlags(); err != nil {
- t.Fatal("generating psuedo flags:", err)
- }
+// ConnectToDUT connects to DUT through gRPC.
+func (n *DUTTestNet) ConnectToDUT(t *testing.T) DUT {
+ t.Helper()
- posixServerAddress := POSIXServerIP + ":" + strconv.Itoa(POSIXServerPort)
+ posixServerAddress := net.JoinHostPort(n.POSIXServerIP.String(), fmt.Sprintf("%d", n.POSIXServerPort))
conn, err := grpc.Dial(posixServerAddress, grpc.WithInsecure(), grpc.WithKeepaliveParams(keepalive.ClientParameters{Timeout: RPCKeepalive}))
if err != nil {
t.Fatalf("failed to grpc.Dial(%s): %s", posixServerAddress, err)
@@ -55,11 +62,12 @@ func NewDUT(t *testing.T) DUT {
return DUT{
conn: conn,
posixServer: posixServer,
+ Net: n,
}
}
-// TearDown closes the underlying connection.
-func (dut *DUT) TearDown() {
+// TearDownConnection closes the underlying connection.
+func (dut *DUT) TearDownConnection() {
dut.conn.Close()
}
@@ -132,7 +140,7 @@ func (dut *DUT) CreateBoundSocket(t *testing.T, typ, proto int32, addr net.IP) (
fd = dut.Socket(t, unix.AF_INET6, typ, proto)
sa := unix.SockaddrInet6{}
copy(sa.Addr[:], addr.To16())
- sa.ZoneId = uint32(RemoteInterfaceID)
+ sa.ZoneId = dut.Net.RemoteDevID
dut.Bind(t, fd, &sa)
} else {
t.Fatalf("invalid IP address: %s", addr)
@@ -154,7 +162,7 @@ func (dut *DUT) CreateBoundSocket(t *testing.T, typ, proto int32, addr net.IP) (
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))
+ fd, remotePort := dut.CreateBoundSocket(t, typ, proto, dut.Net.RemoteIPv4)
dut.Listen(t, fd, backlog)
return fd, remotePort
}
@@ -717,9 +725,9 @@ func (dut *DUT) SetSockLingerOption(t *testing.T, sockfd int32, timeout time.Dur
dut.SetSockOpt(t, sockfd, unix.SOL_SOCKET, unix.SO_LINGER, buf)
}
-// Shutdown calls shutdown 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
-// ShutdownWithErrno.
+// Shutdown calls shutdown 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 ShutdownWithErrno.
func (dut *DUT) Shutdown(t *testing.T, fd, how int32) error {
t.Helper()
diff --git a/test/packetimpact/testbench/dut_client.go b/test/packetimpact/testbench/dut_client.go
index d0e68c5da..0fc3d97b4 100644
--- a/test/packetimpact/testbench/dut_client.go
+++ b/test/packetimpact/testbench/dut_client.go
@@ -19,7 +19,7 @@ import (
pb "gvisor.dev/gvisor/test/packetimpact/proto/posix_server_go_proto"
)
-// PosixClient is a gRPC client for the Posix service.
+// POSIXClient is a gRPC client for the Posix service.
type POSIXClient pb.PosixClient
// NewPOSIXClient makes a new gRPC client for the POSIX service.
diff --git a/test/packetimpact/testbench/layers.go b/test/packetimpact/testbench/layers.go
index a35562ca8..19e6b8d7d 100644
--- a/test/packetimpact/testbench/layers.go
+++ b/test/packetimpact/testbench/layers.go
@@ -286,6 +286,7 @@ type IPv4 struct {
Checksum *uint16
SrcAddr *tcpip.Address
DstAddr *tcpip.Address
+ Options *header.IPv4Options
}
func (l *IPv4) String() string {
@@ -294,10 +295,20 @@ func (l *IPv4) String() string {
// ToBytes implements Layer.ToBytes.
func (l *IPv4) ToBytes() ([]byte, error) {
- b := make([]byte, header.IPv4MinimumSize)
+ // An IPv4 header is variable length depending on the size of the Options.
+ hdrLen := header.IPv4MinimumSize
+ if l.Options != nil {
+ if len(*l.Options)%4 != 0 {
+ return nil, fmt.Errorf("invalid header options '%x (len=%d)'; must be 32 bit aligned", *l.Options, len(*l.Options))
+ }
+ hdrLen += len(*l.Options)
+ if hdrLen > header.IPv4MaximumHeaderSize {
+ return nil, fmt.Errorf("IPv4 Options %d bytes, Max %d", len(*l.Options), header.IPv4MaximumOptionsSize)
+ }
+ }
+ b := make([]byte, hdrLen)
h := header.IPv4(b)
fields := &header.IPv4Fields{
- IHL: 20,
TOS: 0,
TotalLength: 0,
ID: 0,
@@ -308,6 +319,7 @@ func (l *IPv4) ToBytes() ([]byte, error) {
Checksum: 0,
SrcAddr: tcpip.Address(""),
DstAddr: tcpip.Address(""),
+ Options: nil,
}
if l.TOS != nil {
fields.TOS = *l.TOS
@@ -355,13 +367,31 @@ func (l *IPv4) ToBytes() ([]byte, error) {
if l.DstAddr != nil {
fields.DstAddr = *l.DstAddr
}
- if l.Checksum != nil {
- fields.Checksum = *l.Checksum
- }
+
h.Encode(fields)
+
+ // Put raw option bytes from test definition in header. Options as raw bytes
+ // allows us to serialize malformed options, which is not possible with
+ // the provided serialization functions.
+ if l.Options != nil {
+ h.SetHeaderLength(h.HeaderLength() + uint8(len(*l.Options)))
+ if got, want := copy(h.Options(), *l.Options), len(*l.Options); got != want {
+ return nil, fmt.Errorf("failed to copy option bytes into header, got %d want %d", got, want)
+ }
+ }
+
+ // Encode cannot set this incorrectly so we need to overwrite what it wrote
+ // in order to test handling of a bad IHL value.
+ if l.IHL != nil {
+ h.SetHeaderLength(*l.IHL)
+ }
+
if l.Checksum == nil {
h.SetChecksum(^h.CalculateChecksum())
+ } else {
+ h.SetChecksum(*l.Checksum)
}
+
return h, nil
}
@@ -377,8 +407,8 @@ func Uint8(v uint8) *uint8 {
return &v
}
-// Address is a helper routine that allocates a new tcpip.Address value to store
-// v and returns a pointer to it.
+// Address is a helper routine that allocates a new tcpip.Address value to
+// store v and returns a pointer to it.
func Address(v tcpip.Address) *tcpip.Address {
return &v
}
@@ -387,6 +417,7 @@ func Address(v tcpip.Address) *tcpip.Address {
// continues parsing further encapsulations.
func parseIPv4(b []byte) (Layer, layerParser) {
h := header.IPv4(b)
+ options := h.Options()
tos, _ := h.TOS()
ipv4 := IPv4{
IHL: Uint8(h.HeaderLength()),
@@ -400,8 +431,13 @@ func parseIPv4(b []byte) (Layer, layerParser) {
Checksum: Uint16(h.Checksum()),
SrcAddr: Address(h.SourceAddress()),
DstAddr: Address(h.DestinationAddress()),
+ Options: &options,
}
var nextParser layerParser
+ // If it is a fragment, don't treat it as having a transport protocol.
+ if h.FragmentOffset() != 0 || h.More() {
+ return &ipv4, parsePayload
+ }
switch h.TransportProtocol() {
case header.TCPProtocolNumber:
nextParser = parseTCP
@@ -469,13 +505,13 @@ func (l *IPv6) ToBytes() ([]byte, error) {
}
}
if l.NextHeader != nil {
- fields.NextHeader = *l.NextHeader
+ fields.TransportProtocol = tcpip.TransportProtocolNumber(*l.NextHeader)
} else {
nh, err := nextHeaderByLayer(l.next())
if err != nil {
return nil, err
}
- fields.NextHeader = nh
+ fields.TransportProtocol = tcpip.TransportProtocolNumber(nh)
}
if l.HopLimit != nil {
fields.HopLimit = *l.HopLimit
@@ -709,12 +745,17 @@ func parseIPv6FragmentExtHdr(b []byte) (Layer, layerParser) {
nextHeader := b[0]
var extHdr header.IPv6FragmentExtHdr
copy(extHdr[:], b[2:])
- return &IPv6FragmentExtHdr{
+ fragLayer := IPv6FragmentExtHdr{
NextHeader: IPv6ExtHdrIdent(header.IPv6ExtensionHeaderIdentifier(nextHeader)),
FragmentOffset: Uint16(extHdr.FragmentOffset()),
MoreFragments: Bool(extHdr.More()),
Identification: Uint32(extHdr.ID()),
- }, nextIPv6PayloadParser(nextHeader)
+ }
+ // If it is a fragment, we can't interpret it.
+ if extHdr.FragmentOffset() != 0 || extHdr.More() {
+ return &fragLayer, parsePayload
+ }
+ return &fragLayer, nextIPv6PayloadParser(nextHeader)
}
func (l *IPv6HopByHopOptionsExtHdr) length() int {
@@ -796,7 +837,9 @@ func (l *ICMPv6) ToBytes() ([]byte, error) {
if l.Code != nil {
h.SetCode(*l.Code)
}
- copy(h.NDPPayload(), l.Payload)
+ if n := copy(h.MessageBody(), l.Payload); n != len(l.Payload) {
+ panic(fmt.Sprintf("copied %d bytes, expected to copy %d bytes", n, len(l.Payload)))
+ }
if l.Checksum != nil {
h.SetChecksum(*l.Checksum)
} else {
@@ -842,7 +885,7 @@ func parseICMPv6(b []byte) (Layer, layerParser) {
Type: ICMPv6Type(h.Type()),
Code: ICMPv6Code(h.Code()),
Checksum: Uint16(h.Checksum()),
- Payload: h.NDPPayload(),
+ Payload: h.MessageBody(),
}
return &icmpv6, nil
}
@@ -861,33 +904,37 @@ func (l *ICMPv6) merge(other Layer) error {
return mergeLayer(l, other)
}
-// ICMPv4Type is a helper routine that allocates a new header.ICMPv4Type value
-// to store t and returns a pointer to it.
-func ICMPv4Type(t header.ICMPv4Type) *header.ICMPv4Type {
- return &t
-}
-
-// ICMPv4Code is a helper routine that allocates a new header.ICMPv4Code value
-// to store t and returns a pointer to it.
-func ICMPv4Code(t header.ICMPv4Code) *header.ICMPv4Code {
- return &t
-}
-
// ICMPv4 can construct and match an ICMPv4 encapsulation.
type ICMPv4 struct {
LayerBase
Type *header.ICMPv4Type
Code *header.ICMPv4Code
Checksum *uint16
+ Ident *uint16 // Only in Echo Request/Reply.
+ Sequence *uint16 // Only in Echo Request/Reply.
+ Pointer *uint8 // Only in Parameter Problem.
+ Payload []byte
}
func (l *ICMPv4) String() string {
return stringLayer(l)
}
+// ICMPv4Type is a helper routine that allocates a new header.ICMPv4Type value
+// to store t and returns a pointer to it.
+func ICMPv4Type(t header.ICMPv4Type) *header.ICMPv4Type {
+ return &t
+}
+
+// ICMPv4Code is a helper routine that allocates a new header.ICMPv4Code value
+// to store t and returns a pointer to it.
+func ICMPv4Code(t header.ICMPv4Code) *header.ICMPv4Code {
+ return &t
+}
+
// ToBytes implements Layer.ToBytes.
func (l *ICMPv4) ToBytes() ([]byte, error) {
- b := make([]byte, header.ICMPv4MinimumSize)
+ b := make([]byte, header.ICMPv4MinimumSize+len(l.Payload))
h := header.ICMPv4(b)
if l.Type != nil {
h.SetType(*l.Type)
@@ -895,15 +942,41 @@ func (l *ICMPv4) ToBytes() ([]byte, error) {
if l.Code != nil {
h.SetCode(*l.Code)
}
+ if copied := copy(h.Payload(), l.Payload); copied != len(l.Payload) {
+ panic(fmt.Sprintf("wrong number of bytes copied into h.Payload(): got = %d, want = %d", len(h.Payload()), len(l.Payload)))
+ }
+ typ := h.Type()
+ switch typ {
+ case header.ICMPv4EchoReply, header.ICMPv4Echo:
+ if l.Ident != nil {
+ h.SetIdent(*l.Ident)
+ }
+ if l.Sequence != nil {
+ h.SetSequence(*l.Sequence)
+ }
+ case header.ICMPv4ParamProblem:
+ if l.Pointer != nil {
+ h.SetPointer(*l.Pointer)
+ }
+ }
+
+ // The checksum must be handled last because the ICMPv4 header fields are
+ // included in the computation.
if l.Checksum != nil {
h.SetChecksum(*l.Checksum)
- return h, nil
- }
- payload, err := payload(l)
- if err != nil {
- return nil, err
+ } else {
+ // Compute the checksum based on the ICMPv4.Payload and also the subsequent
+ // layers.
+ payload, err := payload(l)
+ if err != nil {
+ return nil, err
+ }
+ var vv buffer.VectorisedView
+ vv.AppendView(buffer.View(l.Payload))
+ vv.Append(payload)
+ h.SetChecksum(header.ICMPv4Checksum(h, vv))
}
- h.SetChecksum(header.ICMPv4Checksum(h, payload))
+
return h, nil
}
@@ -911,12 +984,22 @@ func (l *ICMPv4) ToBytes() ([]byte, error) {
// parser for the encapsulated payload.
func parseICMPv4(b []byte) (Layer, layerParser) {
h := header.ICMPv4(b)
+
+ msgType := h.Type()
icmpv4 := ICMPv4{
- Type: ICMPv4Type(h.Type()),
+ Type: ICMPv4Type(msgType),
Code: ICMPv4Code(h.Code()),
Checksum: Uint16(h.Checksum()),
+ Payload: h.Payload(),
+ }
+ switch msgType {
+ case header.ICMPv4EchoReply, header.ICMPv4Echo:
+ icmpv4.Ident = Uint16(h.Ident())
+ icmpv4.Sequence = Uint16(h.Sequence())
+ case header.ICMPv4ParamProblem:
+ icmpv4.Pointer = Uint8(h.Pointer())
}
- return &icmpv4, parsePayload
+ return &icmpv4, nil
}
func (l *ICMPv4) match(other Layer) bool {
diff --git a/test/packetimpact/testbench/rawsockets.go b/test/packetimpact/testbench/rawsockets.go
index 193bb2dc8..1ac96626a 100644
--- a/test/packetimpact/testbench/rawsockets.go
+++ b/test/packetimpact/testbench/rawsockets.go
@@ -38,13 +38,27 @@ func htons(x uint16) uint16 {
}
// NewSniffer creates a Sniffer connected to *device.
-func NewSniffer(t *testing.T) (Sniffer, error) {
+func (n *DUTTestNet) NewSniffer(t *testing.T) (Sniffer, error) {
t.Helper()
+ ifInfo, err := net.InterfaceByName(n.LocalDevName)
+ if err != nil {
+ return Sniffer{}, err
+ }
+
+ var haddr [8]byte
+ copy(haddr[:], ifInfo.HardwareAddr)
+ sa := unix.SockaddrLinklayer{
+ Protocol: htons(unix.ETH_P_ALL),
+ Ifindex: ifInfo.Index,
+ }
snifferFd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, int(htons(unix.ETH_P_ALL)))
if err != nil {
return Sniffer{}, err
}
+ if err := unix.Bind(snifferFd, &sa); err != nil {
+ return Sniffer{}, err
+ }
if err := unix.SetsockoptInt(snifferFd, unix.SOL_SOCKET, unix.SO_RCVBUFFORCE, 1); err != nil {
t.Fatalf("can't set sockopt SO_RCVBUFFORCE to 1: %s", err)
}
@@ -60,7 +74,8 @@ func NewSniffer(t *testing.T) (Sniffer, error) {
// packet too large for the buffer arrives, the test will get a fatal error.
const maxReadSize int = 65536
-// Recv tries to read one frame until the timeout is up.
+// Recv tries to read one frame until the timeout is up. If the timeout given
+// is 0, then no read attempt will be made.
func (s *Sniffer) Recv(t *testing.T, timeout time.Duration) []byte {
t.Helper()
@@ -73,9 +88,13 @@ func (s *Sniffer) Recv(t *testing.T, timeout time.Duration) []byte {
whole, frac := math.Modf(timeout.Seconds())
tv := unix.Timeval{
Sec: int64(whole),
- Usec: int64(frac * float64(time.Microsecond/time.Second)),
+ Usec: int64(frac * float64(time.Second/time.Microsecond)),
+ }
+ // The following should never happen, but having this guard here is better
+ // than blocking indefinitely in the future.
+ if tv.Sec == 0 && tv.Usec == 0 {
+ t.Fatal("setting SO_RCVTIMEO to 0 means blocking indefinitely")
}
-
if err := unix.SetsockoptTimeval(s.fd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv); err != nil {
t.Fatalf("can't setsockopt SO_RCVTIMEO: %s", err)
}
@@ -136,10 +155,10 @@ type Injector struct {
}
// NewInjector creates a new injector on *device.
-func NewInjector(t *testing.T) (Injector, error) {
+func (n *DUTTestNet) NewInjector(t *testing.T) (Injector, error) {
t.Helper()
- ifInfo, err := net.InterfaceByName(LocalDevice)
+ ifInfo, err := net.InterfaceByName(n.LocalDevName)
if err != nil {
return Injector{}, err
}
@@ -147,7 +166,7 @@ func NewInjector(t *testing.T) (Injector, error) {
var haddr [8]byte
copy(haddr[:], ifInfo.HardwareAddr)
sa := unix.SockaddrLinklayer{
- Protocol: unix.ETH_P_IP,
+ Protocol: htons(unix.ETH_P_IP),
Ifindex: ifInfo.Index,
Halen: uint8(len(ifInfo.HardwareAddr)),
Addr: haddr,
diff --git a/test/packetimpact/testbench/testbench.go b/test/packetimpact/testbench/testbench.go
index 3c85ebbee..891897d55 100644
--- a/test/packetimpact/testbench/testbench.go
+++ b/test/packetimpact/testbench/testbench.go
@@ -12,112 +12,110 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Package testbench is the packetimpact test API.
+// Package testbench has utilities to send and receive packets, and also command
+// the DUT to run POSIX functions. It is the packetimpact test API.
package testbench
import (
+ "encoding/json"
"flag"
"fmt"
"math/rand"
"net"
- "os/exec"
"testing"
"time"
-
- "gvisor.dev/gvisor/test/packetimpact/netdevs"
)
var (
// Native indicates that the test is being run natively.
Native = false
- // LocalDevice is the device that testbench uses to inject traffic.
- LocalDevice = ""
- // RemoteDevice is the device name on the DUT, individual tests can
- // use the name to construct tests.
- RemoteDevice = ""
+ // RPCKeepalive is the gRPC keepalive.
+ RPCKeepalive = 10 * time.Second
+ // RPCTimeout is the gRPC timeout.
+ RPCTimeout = 100 * time.Millisecond
+
+ // dutTestNetsJSON is the json string that describes all the test networks to
+ // duts available to use.
+ dutTestNetsJSON string
+ // dutTestNets is the pool among which the testbench can choose a DUT to work
+ // with.
+ dutTestNets chan *DUTTestNet
+)
+// DUTTestNet describes the test network setup on dut and how the testbench
+// should connect with an existing DUT.
+type DUTTestNet struct {
+ // LocalMAC is the local MAC address on the test network.
+ LocalMAC net.HardwareAddr
+ // RemoteMAC is the DUT's MAC address on the test network.
+ RemoteMAC net.HardwareAddr
// LocalIPv4 is the local IPv4 address on the test network.
- LocalIPv4 = ""
+ LocalIPv4 net.IP
// RemoteIPv4 is the DUT's IPv4 address on the test network.
- RemoteIPv4 = ""
+ RemoteIPv4 net.IP
// IPv4PrefixLength is the network prefix length of the IPv4 test network.
- IPv4PrefixLength = 0
-
+ IPv4PrefixLength int
// LocalIPv6 is the local IPv6 address on the test network.
- LocalIPv6 = ""
+ LocalIPv6 net.IP
// RemoteIPv6 is the DUT's IPv6 address on the test network.
- RemoteIPv6 = ""
+ RemoteIPv6 net.IP
+ // LocalDevID is the ID of the local interface on the test network.
+ LocalDevID uint32
+ // RemoteDevID is the ID of the remote interface on the test network.
+ RemoteDevID uint32
+ // LocalDevName is the device that testbench uses to inject traffic.
+ LocalDevName string
+ // RemoteDevName is the device name on the DUT, individual tests can
+ // use the name to construct tests.
+ RemoteDevName string
- // 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 = ""
+ // The following two fields on actually on the control network instead
+ // of the test network, including them for convenience.
// POSIXServerIP is the POSIX server's IP address on the control network.
- POSIXServerIP = ""
+ POSIXServerIP net.IP
// POSIXServerPort is the UDP port the POSIX server is bound to on the
// control network.
- POSIXServerPort = 40000
-
- // RPCKeepalive is the gRPC keepalive.
- RPCKeepalive = 10 * time.Second
- // RPCTimeout is the gRPC timeout.
- RPCTimeout = 100 * time.Millisecond
-)
+ POSIXServerPort uint16
+}
-// RegisterFlags defines flags and associates them with the package-level
+// registerFlags defines flags and associates them with the package-level
// exported variables above. It should be called by tests in their init
// functions.
-func RegisterFlags(fs *flag.FlagSet) {
- fs.StringVar(&POSIXServerIP, "posix_server_ip", POSIXServerIP, "ip address to listen to for UDP commands")
- fs.IntVar(&POSIXServerPort, "posix_server_port", POSIXServerPort, "port to listen to for UDP commands")
+func registerFlags(fs *flag.FlagSet) {
+ fs.BoolVar(&Native, "native", Native, "whether the test is running natively")
fs.DurationVar(&RPCTimeout, "rpc_timeout", RPCTimeout, "gRPC timeout")
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(&RemoteIPv6, "remote_ipv6", RemoteIPv6, "remote IPv6 address for test packets")
- fs.StringVar(&RemoteMAC, "remote_mac", RemoteMAC, "remote mac address for test packets")
- fs.StringVar(&LocalDevice, "local_device", LocalDevice, "local device to inject traffic")
- fs.StringVar(&RemoteDevice, "remote_device", RemoteDevice, "remote device on the DUT")
- fs.BoolVar(&Native, "native", Native, "whether the test is running natively")
- fs.Uint64Var(&RemoteInterfaceID, "remote_interface_id", RemoteInterfaceID, "remote interface ID for test packets")
+ fs.StringVar(&dutTestNetsJSON, "dut_test_nets_json", dutTestNetsJSON, "path to the dut test nets json file")
}
-// 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)
+// Initialize initializes the testbench, it parse the flags and sets up the
+// pool of test networks for testbench's later use.
+func Initialize(fs *flag.FlagSet) {
+ registerFlags(fs)
+ flag.Parse()
+ if err := loadDUTTestNets(); err != nil {
+ panic(err)
}
+}
- _, deviceInfo, err := netdevs.FindDeviceByIP(net.ParseIP(LocalIPv4), devs)
- if err != nil {
- return fmt.Errorf("can't find deviceInfo: %w", err)
+// loadDUTTestNets loads available DUT test networks from the json file, it
+// must be called after flag.Parse().
+func loadDUTTestNets() error {
+ var parsedTestNets []DUTTestNet
+ if err := json.Unmarshal([]byte(dutTestNetsJSON), &parsedTestNets); err != nil {
+ return fmt.Errorf("failed to unmarshal JSON: %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()
+ if got, want := len(parsedTestNets), 1; got < want {
+ return fmt.Errorf("got %d DUTs, the test requires at least %d DUTs", got, want)
+ }
+ // Using a buffered channel as semaphore
+ dutTestNets = make(chan *DUTTestNet, len(parsedTestNets))
+ for i := range parsedTestNets {
+ parsedTestNets[i].LocalIPv4 = parsedTestNets[i].LocalIPv4.To4()
+ parsedTestNets[i].RemoteIPv4 = parsedTestNets[i].RemoteIPv4.To4()
+ dutTestNets <- &parsedTestNets[i]
}
-
return nil
}
@@ -131,3 +129,15 @@ func GenerateRandomPayload(t *testing.T, n int) []byte {
}
return buf
}
+
+// GetDUTTestNet gets a usable DUTTestNet, the function will block until any
+// becomes available.
+func GetDUTTestNet() *DUTTestNet {
+ return <-dutTestNets
+}
+
+// Release releases the DUTTestNet back to the pool so that some other test
+// can use.
+func (n *DUTTestNet) Release() {
+ dutTestNets <- n
+}
diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD
index 8c2de5a9f..b1b3c578b 100644
--- a/test/packetimpact/tests/BUILD
+++ b/test/packetimpact/tests/BUILD
@@ -298,6 +298,18 @@ packetimpact_testbench(
)
packetimpact_testbench(
+ name = "ipv4_fragment_reassembly",
+ srcs = ["ipv4_fragment_reassembly_test.go"],
+ deps = [
+ "//pkg/tcpip/buffer",
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@com_github_google_go_cmp//cmp:go_default_library",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+packetimpact_testbench(
name = "ipv6_fragment_reassembly",
srcs = ["ipv6_fragment_reassembly_test.go"],
deps = [
@@ -305,6 +317,21 @@ packetimpact_testbench(
"//pkg/tcpip/buffer",
"//pkg/tcpip/header",
"//test/packetimpact/testbench",
+ "@com_github_google_go_cmp//cmp:go_default_library",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+packetimpact_testbench(
+ name = "ipv6_fragment_icmp_error",
+ srcs = ["ipv6_fragment_icmp_error_test.go"],
+ deps = [
+ "//pkg/tcpip",
+ "//pkg/tcpip/buffer",
+ "//pkg/tcpip/header",
+ "//pkg/tcpip/network/ipv6",
+ "//test/packetimpact/testbench",
+ "@com_github_google_go_cmp//cmp:go_default_library",
"@org_golang_x_sys//unix:go_default_library",
],
)
@@ -339,9 +366,29 @@ packetimpact_testbench(
],
)
+packetimpact_testbench(
+ name = "tcp_zero_receive_window",
+ srcs = ["tcp_zero_receive_window_test.go"],
+ deps = [
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
validate_all_tests()
[packetimpact_go_test(
name = t.name,
expect_netstack_failure = hasattr(t, "expect_netstack_failure"),
+ num_duts = t.num_duts if hasattr(t, "num_duts") else 1,
) for t in ALL_TESTS]
+
+test_suite(
+ name = "all_tests",
+ tags = [
+ "manual",
+ "packetimpact",
+ ],
+ tests = existing_rules(),
+)
diff --git a/test/packetimpact/tests/fin_wait2_timeout_test.go b/test/packetimpact/tests/fin_wait2_timeout_test.go
index a61054c2c..11f0fcd1e 100644
--- a/test/packetimpact/tests/fin_wait2_timeout_test.go
+++ b/test/packetimpact/tests/fin_wait2_timeout_test.go
@@ -25,7 +25,7 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
func TestFinWait2Timeout(t *testing.T) {
@@ -38,10 +38,9 @@ func TestFinWait2Timeout(t *testing.T) {
} {
t.Run(tt.description, 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})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
conn.Connect(t)
diff --git a/test/packetimpact/tests/icmpv6_param_problem_test.go b/test/packetimpact/tests/icmpv6_param_problem_test.go
index 2d59d552d..40d7a491d 100644
--- a/test/packetimpact/tests/icmpv6_param_problem_test.go
+++ b/test/packetimpact/tests/icmpv6_param_problem_test.go
@@ -25,15 +25,14 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(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 := testbench.NewDUT(t)
- defer dut.TearDown()
- conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
+ conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
defer conn.Close(t)
ipv6 := testbench.IPv6{
// 254 is reserved and used for experimentation and testing. This should
diff --git a/test/packetimpact/tests/ipv4_fragment_reassembly_test.go b/test/packetimpact/tests/ipv4_fragment_reassembly_test.go
new file mode 100644
index 000000000..d2203082d
--- /dev/null
+++ b/test/packetimpact/tests/ipv4_fragment_reassembly_test.go
@@ -0,0 +1,191 @@
+// 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_fragment_reassembly_test
+
+import (
+ "flag"
+ "math/rand"
+ "testing"
+ "time"
+
+ "github.com/google/go-cmp/cmp"
+ "gvisor.dev/gvisor/pkg/tcpip/buffer"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+func init() {
+ testbench.Initialize(flag.CommandLine)
+}
+
+type fragmentInfo struct {
+ offset uint16
+ size uint16
+ more uint8
+ id uint16
+}
+
+func TestIPv4FragmentReassembly(t *testing.T) {
+ icmpv4ProtoNum := uint8(header.ICMPv4ProtocolNumber)
+
+ tests := []struct {
+ description string
+ ipPayloadLen int
+ fragments []fragmentInfo
+ expectReply bool
+ skip bool
+ skipReason string
+ }{
+ {
+ description: "basic reassembly",
+ ipPayloadLen: 3000,
+ fragments: []fragmentInfo{
+ {offset: 0, size: 1000, id: 5, more: header.IPv4FlagMoreFragments},
+ {offset: 1000, size: 1000, id: 5, more: header.IPv4FlagMoreFragments},
+ {offset: 2000, size: 1000, id: 5, more: 0},
+ },
+ expectReply: true,
+ },
+ {
+ description: "out of order fragments",
+ ipPayloadLen: 3000,
+ fragments: []fragmentInfo{
+ {offset: 2000, size: 1000, id: 6, more: 0},
+ {offset: 0, size: 1000, id: 6, more: header.IPv4FlagMoreFragments},
+ {offset: 1000, size: 1000, id: 6, more: header.IPv4FlagMoreFragments},
+ },
+ expectReply: true,
+ },
+ {
+ description: "duplicated fragments",
+ ipPayloadLen: 3000,
+ fragments: []fragmentInfo{
+ {offset: 0, size: 1000, id: 7, more: header.IPv4FlagMoreFragments},
+ {offset: 1000, size: 1000, id: 7, more: header.IPv4FlagMoreFragments},
+ {offset: 1000, size: 1000, id: 7, more: header.IPv4FlagMoreFragments},
+ {offset: 2000, size: 1000, id: 7, more: 0},
+ },
+ expectReply: true,
+ skip: true,
+ skipReason: "gvisor.dev/issues/4971",
+ },
+ {
+ description: "fragment subset",
+ ipPayloadLen: 3000,
+ fragments: []fragmentInfo{
+ {offset: 0, size: 1000, id: 8, more: header.IPv4FlagMoreFragments},
+ {offset: 1000, size: 1000, id: 8, more: header.IPv4FlagMoreFragments},
+ {offset: 512, size: 256, id: 8, more: header.IPv4FlagMoreFragments},
+ {offset: 2000, size: 1000, id: 8, more: 0},
+ },
+ expectReply: true,
+ skip: true,
+ skipReason: "gvisor.dev/issues/4971",
+ },
+ {
+ description: "fragment overlap",
+ ipPayloadLen: 3000,
+ fragments: []fragmentInfo{
+ {offset: 0, size: 1000, id: 9, more: header.IPv4FlagMoreFragments},
+ {offset: 1512, size: 1000, id: 9, more: header.IPv4FlagMoreFragments},
+ {offset: 1000, size: 1000, id: 9, more: header.IPv4FlagMoreFragments},
+ {offset: 2000, size: 1000, id: 9, more: 0},
+ },
+ expectReply: false,
+ skip: true,
+ skipReason: "gvisor.dev/issues/4971",
+ },
+ }
+
+ for _, test := range tests {
+ if test.skip {
+ t.Skip("%s test skipped: %s", test.description, test.skipReason)
+ continue
+ }
+ t.Run(test.description, func(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ conn := dut.Net.NewIPv4Conn(t, testbench.IPv4{}, testbench.IPv4{})
+ defer conn.Close(t)
+
+ data := make([]byte, test.ipPayloadLen)
+ icmp := header.ICMPv4(data[:header.ICMPv4MinimumSize])
+ icmp.SetType(header.ICMPv4Echo)
+ icmp.SetCode(header.ICMPv4UnusedCode)
+ icmp.SetChecksum(0)
+ icmp.SetSequence(0)
+ icmp.SetIdent(0)
+ originalPayload := data[header.ICMPv4MinimumSize:]
+ if _, err := rand.Read(originalPayload); err != nil {
+ t.Fatalf("rand.Read: %s", err)
+ }
+ cksum := header.ICMPv4Checksum(
+ icmp,
+ buffer.NewVectorisedView(len(originalPayload), []buffer.View{originalPayload}),
+ )
+ icmp.SetChecksum(cksum)
+
+ for _, fragment := range test.fragments {
+ conn.Send(t,
+ testbench.IPv4{
+ Protocol: &icmpv4ProtoNum,
+ FragmentOffset: testbench.Uint16(fragment.offset),
+ Flags: testbench.Uint8(fragment.more),
+ ID: testbench.Uint16(fragment.id),
+ },
+ &testbench.Payload{
+ Bytes: data[fragment.offset:][:fragment.size],
+ })
+ }
+
+ var bytesReceived int
+ reassembledPayload := make([]byte, test.ipPayloadLen)
+ // We are sending a packet fragmented into smaller parts but the
+ // response may also be large enough to require fragmentation.
+ // Therefore we only look for payload for an IPv4 packet not ICMP.
+ for {
+ incomingFrame, err := conn.ExpectFrame(t, testbench.Layers{
+ &testbench.Ether{},
+ &testbench.IPv4{},
+ }, time.Second)
+ if err != nil {
+ // Either an unexpected frame was received, or none at all.
+ if test.expectReply && bytesReceived < test.ipPayloadLen {
+ t.Fatalf("received %d bytes out of %d, then conn.ExpectFrame(_, _, time.Second) failed with %s", bytesReceived, test.ipPayloadLen, err)
+ }
+ break
+ }
+ if !test.expectReply {
+ t.Fatalf("unexpected reply received:\n%s", incomingFrame)
+ }
+ // We only asked for Ethernet and IPv4 so the rest should be payload.
+ ipPayload, err := incomingFrame[2 /* Payload */].ToBytes()
+ if err != nil {
+ t.Fatalf("failed to parse payload: incomingPacket[2].ToBytes() = (_, %s)", err)
+ }
+ offset := *incomingFrame[1 /* IPv4 */].(*testbench.IPv4).FragmentOffset
+ if copied := copy(reassembledPayload[offset:], ipPayload); copied != len(ipPayload) {
+ t.Fatalf("wrong number of bytes copied into reassembledPayload: got = %d, want = %d", copied, len(ipPayload))
+ }
+ bytesReceived += len(ipPayload)
+ }
+
+ if test.expectReply {
+ if diff := cmp.Diff(originalPayload, reassembledPayload[header.ICMPv4MinimumSize:]); diff != "" {
+ t.Fatalf("reassembledPayload mismatch (-want +got):\n%s", diff)
+ }
+ }
+ })
+ }
+}
diff --git a/test/packetimpact/tests/ipv4_id_uniqueness_test.go b/test/packetimpact/tests/ipv4_id_uniqueness_test.go
index 7f7a768d3..a63b41366 100644
--- a/test/packetimpact/tests/ipv4_id_uniqueness_test.go
+++ b/test/packetimpact/tests/ipv4_id_uniqueness_test.go
@@ -28,7 +28,7 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
func recvTCPSegment(t *testing.T, conn *testbench.TCPIPv4, expect *testbench.TCP, expectPayload *testbench.Payload) (uint16, error) {
@@ -67,12 +67,10 @@ func TestIPv4RetransmitIdentificationUniqueness(t *testing.T) {
} {
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})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
conn.Connect(t)
diff --git a/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go b/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go
new file mode 100644
index 000000000..a37867e85
--- /dev/null
+++ b/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go
@@ -0,0 +1,359 @@
+// 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_icmp_error_test
+
+import (
+ "flag"
+ "testing"
+ "time"
+
+ "github.com/google/go-cmp/cmp"
+ "gvisor.dev/gvisor/pkg/tcpip"
+ "gvisor.dev/gvisor/pkg/tcpip/buffer"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ "gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+const (
+ data = "IPV6_PROTOCOL_TESTER_FOR_FRAGMENT"
+ fragmentID = 1
+ reassemblyTimeout = ipv6.ReassembleTimeout + 5*time.Second
+)
+
+func init() {
+ testbench.Initialize(flag.CommandLine)
+}
+
+func fragmentedICMPEchoRequest(t *testing.T, n *testbench.DUTTestNet, conn *testbench.Connection, firstPayloadLength uint16, payload []byte, secondFragmentOffset uint16) ([]testbench.Layers, [][]byte) {
+ t.Helper()
+
+ icmpv6Header := header.ICMPv6(make([]byte, header.ICMPv6EchoMinimumSize))
+ icmpv6Header.SetType(header.ICMPv6EchoRequest)
+ icmpv6Header.SetCode(header.ICMPv6UnusedCode)
+ icmpv6Header.SetIdent(0)
+ icmpv6Header.SetSequence(0)
+ cksum := header.ICMPv6Checksum(
+ icmpv6Header,
+ tcpip.Address(n.LocalIPv6),
+ tcpip.Address(n.RemoteIPv6),
+ buffer.NewVectorisedView(len(payload), []buffer.View{payload}),
+ )
+ icmpv6Header.SetChecksum(cksum)
+ icmpv6Bytes := append([]byte(icmpv6Header), payload...)
+
+ icmpv6ProtoNum := header.IPv6ExtensionHeaderIdentifier(header.ICMPv6ProtocolNumber)
+
+ firstFragment := conn.CreateFrame(t, testbench.Layers{&testbench.IPv6{}},
+ &testbench.IPv6FragmentExtHdr{
+ NextHeader: &icmpv6ProtoNum,
+ FragmentOffset: testbench.Uint16(0),
+ MoreFragments: testbench.Bool(true),
+ Identification: testbench.Uint32(fragmentID),
+ },
+ &testbench.Payload{
+ Bytes: icmpv6Bytes[:header.ICMPv6PayloadOffset+firstPayloadLength],
+ },
+ )
+ firstIPv6 := firstFragment[1:]
+ firstIPv6Bytes, err := firstIPv6.ToBytes()
+ if err != nil {
+ t.Fatalf("failed to convert first %s to bytes: %s", firstIPv6, err)
+ }
+
+ secondFragment := conn.CreateFrame(t, testbench.Layers{&testbench.IPv6{}},
+ &testbench.IPv6FragmentExtHdr{
+ NextHeader: &icmpv6ProtoNum,
+ FragmentOffset: testbench.Uint16(secondFragmentOffset),
+ MoreFragments: testbench.Bool(false),
+ Identification: testbench.Uint32(fragmentID),
+ },
+ &testbench.Payload{
+ Bytes: icmpv6Bytes[header.ICMPv6PayloadOffset+firstPayloadLength:],
+ },
+ )
+ secondIPv6 := secondFragment[1:]
+ secondIPv6Bytes, err := secondIPv6.ToBytes()
+ if err != nil {
+ t.Fatalf("failed to convert second %s to bytes: %s", secondIPv6, err)
+ }
+
+ return []testbench.Layers{firstFragment, secondFragment}, [][]byte{firstIPv6Bytes, secondIPv6Bytes}
+}
+
+func TestIPv6ICMPEchoRequestFragmentReassembly(t *testing.T) {
+ tests := []struct {
+ name string
+ firstPayloadLength uint16
+ payload []byte
+ secondFragmentOffset uint16
+ sendFrameOrder []int
+ }{
+ {
+ name: "reassemble two fragments",
+ firstPayloadLength: 8,
+ payload: []byte(data)[:20],
+ secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8,
+ sendFrameOrder: []int{1, 2},
+ },
+ {
+ name: "reassemble two fragments in reverse order",
+ firstPayloadLength: 8,
+ payload: []byte(data)[:20],
+ secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8,
+ sendFrameOrder: []int{2, 1},
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ t.Parallel()
+ dut := testbench.NewDUT(t)
+ ipv6Conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
+ conn := (*testbench.Connection)(&ipv6Conn)
+ defer ipv6Conn.Close(t)
+
+ fragments, _ := fragmentedICMPEchoRequest(t, dut.Net, conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset)
+
+ for _, i := range test.sendFrameOrder {
+ conn.SendFrame(t, fragments[i-1])
+ }
+
+ gotEchoReply, err := ipv6Conn.ExpectFrame(t, testbench.Layers{
+ &testbench.Ether{},
+ &testbench.IPv6{},
+ &testbench.ICMPv6{
+ Type: testbench.ICMPv6Type(header.ICMPv6EchoReply),
+ Code: testbench.ICMPv6Code(header.ICMPv6UnusedCode),
+ },
+ }, time.Second)
+ if err != nil {
+ t.Fatalf("didn't receive an ICMPv6 Echo Reply: %s", err)
+ }
+ gotPayload, err := gotEchoReply[len(gotEchoReply)-1].ToBytes()
+ if err != nil {
+ t.Fatalf("failed to convert ICMPv6 to bytes: %s", err)
+ }
+ icmpPayload := gotPayload[header.ICMPv6EchoMinimumSize:]
+ wantPayload := test.payload
+ if diff := cmp.Diff(wantPayload, icmpPayload); diff != "" {
+ t.Fatalf("payload mismatch (-want +got):\n%s", diff)
+ }
+ })
+ }
+}
+
+func TestIPv6FragmentReassemblyTimeout(t *testing.T) {
+ type icmpFramePattern struct {
+ typ header.ICMPv6Type
+ code header.ICMPv6Code
+ }
+
+ type icmpReassemblyTimeoutDetail struct {
+ payloadFragment int // 1: first fragment, 2: second fragnemt.
+ }
+
+ tests := []struct {
+ name string
+ firstPayloadLength uint16
+ payload []byte
+ secondFragmentOffset uint16
+ sendFrameOrder []int
+ replyFilter icmpFramePattern
+ expectErrorReply bool
+ expectICMPReassemblyTimeout icmpReassemblyTimeoutDetail
+ }{
+ {
+ name: "reassembly timeout (first fragment only)",
+ firstPayloadLength: 8,
+ payload: []byte(data)[:20],
+ secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8,
+ sendFrameOrder: []int{1},
+ replyFilter: icmpFramePattern{
+ typ: header.ICMPv6TimeExceeded,
+ code: header.ICMPv6ReassemblyTimeout,
+ },
+ expectErrorReply: true,
+ expectICMPReassemblyTimeout: icmpReassemblyTimeoutDetail{
+ payloadFragment: 1,
+ },
+ },
+ {
+ name: "reassembly timeout (second fragment only)",
+ firstPayloadLength: 8,
+ payload: []byte(data)[:20],
+ secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8,
+ sendFrameOrder: []int{2},
+ replyFilter: icmpFramePattern{
+ typ: header.ICMPv6TimeExceeded,
+ code: header.ICMPv6ReassemblyTimeout,
+ },
+ expectErrorReply: false,
+ },
+ {
+ name: "reassembly timeout (two fragments with a gap)",
+ firstPayloadLength: 8,
+ payload: []byte(data)[:20],
+ secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 16) / 8,
+ sendFrameOrder: []int{1, 2},
+ replyFilter: icmpFramePattern{
+ typ: header.ICMPv6TimeExceeded,
+ code: header.ICMPv6ReassemblyTimeout,
+ },
+ expectErrorReply: true,
+ expectICMPReassemblyTimeout: icmpReassemblyTimeoutDetail{
+ payloadFragment: 1,
+ },
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ t.Parallel()
+ dut := testbench.NewDUT(t)
+ ipv6Conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
+ conn := (*testbench.Connection)(&ipv6Conn)
+ defer ipv6Conn.Close(t)
+
+ fragments, ipv6Bytes := fragmentedICMPEchoRequest(t, dut.Net, conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset)
+
+ for _, i := range test.sendFrameOrder {
+ conn.SendFrame(t, fragments[i-1])
+ }
+
+ gotErrorMessage, err := ipv6Conn.ExpectFrame(t, testbench.Layers{
+ &testbench.Ether{},
+ &testbench.IPv6{},
+ &testbench.ICMPv6{
+ Type: testbench.ICMPv6Type(test.replyFilter.typ),
+ Code: testbench.ICMPv6Code(test.replyFilter.code),
+ },
+ }, reassemblyTimeout)
+ if !test.expectErrorReply {
+ if err == nil {
+ t.Fatalf("shouldn't receive an ICMPv6 Error Message with type=%d and code=%d", test.replyFilter.typ, test.replyFilter.code)
+ }
+ return
+ }
+ if err != nil {
+ t.Fatalf("didn't receive an ICMPv6 Error Message with type=%d and code=%d: err", test.replyFilter.typ, test.replyFilter.code, err)
+ }
+ gotPayload, err := gotErrorMessage[len(gotErrorMessage)-1].ToBytes()
+ if err != nil {
+ t.Fatalf("failed to convert ICMPv6 to bytes: %s", err)
+ }
+ icmpPayload := gotPayload[header.ICMPv6ErrorHeaderSize:]
+ wantPayload := ipv6Bytes[test.expectICMPReassemblyTimeout.payloadFragment-1]
+ if diff := cmp.Diff(wantPayload, icmpPayload); diff != "" {
+ t.Fatalf("payload mismatch (-want +got):\n%s", diff)
+ }
+ })
+ }
+}
+
+func TestIPv6FragmentParamProblem(t *testing.T) {
+ type icmpFramePattern struct {
+ typ header.ICMPv6Type
+ code header.ICMPv6Code
+ }
+
+ type icmpParamProblemDetail struct {
+ pointer uint32
+ payloadFragment int // 1: first fragment, 2: second fragnemt.
+ }
+
+ tests := []struct {
+ name string
+ firstPayloadLength uint16
+ payload []byte
+ secondFragmentOffset uint16
+ sendFrameOrder []int
+ replyFilter icmpFramePattern
+ expectICMPParamProblem icmpParamProblemDetail
+ }{
+ {
+ name: "payload size not a multiple of 8",
+ firstPayloadLength: 9,
+ payload: []byte(data)[:20],
+ secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8,
+ sendFrameOrder: []int{1},
+ replyFilter: icmpFramePattern{
+ typ: header.ICMPv6ParamProblem,
+ code: header.ICMPv6ErroneousHeader,
+ },
+ expectICMPParamProblem: icmpParamProblemDetail{
+ pointer: 4,
+ payloadFragment: 1,
+ },
+ },
+ {
+ name: "payload length error",
+ firstPayloadLength: 16,
+ payload: []byte(data)[:33],
+ secondFragmentOffset: 65520 / 8,
+ sendFrameOrder: []int{1, 2},
+ replyFilter: icmpFramePattern{
+ typ: header.ICMPv6ParamProblem,
+ code: header.ICMPv6ErroneousHeader,
+ },
+ expectICMPParamProblem: icmpParamProblemDetail{
+ pointer: 42,
+ payloadFragment: 2,
+ },
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ t.Parallel()
+ dut := testbench.NewDUT(t)
+ ipv6Conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
+ conn := (*testbench.Connection)(&ipv6Conn)
+ defer ipv6Conn.Close(t)
+
+ fragments, ipv6Bytes := fragmentedICMPEchoRequest(t, dut.Net, conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset)
+
+ for _, i := range test.sendFrameOrder {
+ conn.SendFrame(t, fragments[i-1])
+ }
+
+ gotErrorMessage, err := ipv6Conn.ExpectFrame(t, testbench.Layers{
+ &testbench.Ether{},
+ &testbench.IPv6{},
+ &testbench.ICMPv6{
+ Type: testbench.ICMPv6Type(test.replyFilter.typ),
+ Code: testbench.ICMPv6Code(test.replyFilter.code),
+ },
+ }, time.Second)
+ if err != nil {
+ t.Fatalf("didn't receive an ICMPv6 Error Message with type=%d and code=%d: err", test.replyFilter.typ, test.replyFilter.code, err)
+ }
+ gotPayload, err := gotErrorMessage[len(gotErrorMessage)-1].ToBytes()
+ if err != nil {
+ t.Fatalf("failed to convert ICMPv6 to bytes: %s", err)
+ }
+ gotPointer := header.ICMPv6(gotPayload).TypeSpecific()
+ wantPointer := test.expectICMPParamProblem.pointer
+ if gotPointer != wantPointer {
+ t.Fatalf("got pointer = %d, want = %d", gotPointer, wantPointer)
+ }
+ icmpPayload := gotPayload[header.ICMPv6ErrorHeaderSize:]
+ wantPayload := ipv6Bytes[test.expectICMPParamProblem.payloadFragment-1]
+ if diff := cmp.Diff(wantPayload, icmpPayload); diff != "" {
+ t.Fatalf("payload mismatch (-want +got):\n%s", diff)
+ }
+ })
+ }
+}
diff --git a/test/packetimpact/tests/ipv6_fragment_reassembly_test.go b/test/packetimpact/tests/ipv6_fragment_reassembly_test.go
index a24c85566..dd98ee7a1 100644
--- a/test/packetimpact/tests/ipv6_fragment_reassembly_test.go
+++ b/test/packetimpact/tests/ipv6_fragment_reassembly_test.go
@@ -15,154 +15,168 @@
package ipv6_fragment_reassembly_test
import (
- "bytes"
- "encoding/binary"
- "encoding/hex"
"flag"
- "net"
+ "math/rand"
"testing"
"time"
+ "github.com/google/go-cmp/cmp"
"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)
+ testbench.Initialize(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.ICMPv6Code(header.ICMPv6UnusedCode),
- 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.ICMPv6Code(header.ICMPv6UnusedCode),
- Payload: icmpv6EchoPayload,
- Checksum: &cksum,
- })
+type fragmentInfo struct {
+ offset uint16
+ size uint16
+ more bool
+ id uint32
+}
+func TestIPv6FragmentReassembly(t *testing.T) {
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),
+ tests := []struct {
+ description string
+ ipPayloadLen int
+ fragments []fragmentInfo
+ expectReply bool
+ }{
+ {
+ description: "basic reassembly",
+ ipPayloadLen: 3000,
+ fragments: []fragmentInfo{
+ {offset: 0, size: 1000, id: 100, more: true},
+ {offset: 1000, size: 1000, id: 100, more: true},
+ {offset: 2000, size: 1000, id: 100, more: false},
+ },
+ expectReply: true,
},
- &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),
+ {
+ description: "out of order fragments",
+ ipPayloadLen: 3000,
+ fragments: []fragmentInfo{
+ {offset: 0, size: 1000, id: 101, more: true},
+ {offset: 2000, size: 1000, id: 101, more: false},
+ {offset: 1000, size: 1000, id: 101, more: true},
+ },
+ expectReply: true,
},
- &testbench.ICMPv6{
- Type: testbench.ICMPv6Type(header.ICMPv6EchoReply),
- Code: testbench.ICMPv6Code(header.ICMPv6UnusedCode),
+ {
+ description: "duplicated fragments",
+ ipPayloadLen: 3000,
+ fragments: []fragmentInfo{
+ {offset: 0, size: 1000, id: 102, more: true},
+ {offset: 1000, size: 1000, id: 102, more: true},
+ {offset: 1000, size: 1000, id: 102, more: true},
+ {offset: 2000, size: 1000, id: 102, more: false},
+ },
+ expectReply: true,
},
- }, 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,
+ {
+ description: "fragment subset",
+ ipPayloadLen: 3000,
+ fragments: []fragmentInfo{
+ {offset: 0, size: 1000, id: 103, more: true},
+ {offset: 1000, size: 1000, id: 103, more: true},
+ {offset: 512, size: 256, id: 103, more: true},
+ {offset: 2000, size: 1000, id: 103, more: false},
+ },
+ expectReply: true,
+ },
+ {
+ description: "fragment overlap",
+ ipPayloadLen: 3000,
+ fragments: []fragmentInfo{
+ {offset: 0, size: 1000, id: 104, more: true},
+ {offset: 1512, size: 1000, id: 104, more: true},
+ {offset: 1000, size: 1000, id: 104, more: true},
+ {offset: 2000, size: 1000, id: 104, more: false},
+ },
+ expectReply: false,
},
- &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))
+
+ for _, test := range tests {
+ t.Run(test.description, func(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
+ defer conn.Close(t)
+
+ lIP := tcpip.Address(dut.Net.LocalIPv6)
+ rIP := tcpip.Address(dut.Net.RemoteIPv6)
+
+ data := make([]byte, test.ipPayloadLen)
+ icmp := header.ICMPv6(data[:header.ICMPv6HeaderSize])
+ icmp.SetType(header.ICMPv6EchoRequest)
+ icmp.SetCode(header.ICMPv6UnusedCode)
+ icmp.SetChecksum(0)
+ originalPayload := data[header.ICMPv6HeaderSize:]
+ if _, err := rand.Read(originalPayload); err != nil {
+ t.Fatalf("rand.Read: %s", err)
+ }
+
+ cksum := header.ICMPv6Checksum(
+ icmp,
+ lIP,
+ rIP,
+ buffer.NewVectorisedView(len(originalPayload), []buffer.View{originalPayload}),
+ )
+ icmp.SetChecksum(cksum)
+
+ for _, fragment := range test.fragments {
+ conn.Send(t, testbench.IPv6{},
+ &testbench.IPv6FragmentExtHdr{
+ NextHeader: &icmpv6ProtoNum,
+ FragmentOffset: testbench.Uint16(fragment.offset / header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit),
+ MoreFragments: testbench.Bool(fragment.more),
+ Identification: testbench.Uint32(fragment.id),
+ },
+ &testbench.Payload{
+ Bytes: data[fragment.offset:][:fragment.size],
+ })
+ }
+
+ var bytesReceived int
+ reassembledPayload := make([]byte, test.ipPayloadLen)
+ for {
+ incomingFrame, err := conn.ExpectFrame(t, testbench.Layers{
+ &testbench.Ether{},
+ &testbench.IPv6{},
+ &testbench.IPv6FragmentExtHdr{},
+ }, time.Second)
+ if err != nil {
+ // Either an unexpected frame was received, or none at all.
+ if test.expectReply && bytesReceived < test.ipPayloadLen {
+ t.Fatalf("received %d bytes out of %d, then conn.ExpectFrame(_, _, time.Second) failed with %s", bytesReceived, test.ipPayloadLen, err)
+ }
+ break
+ }
+ if !test.expectReply {
+ t.Fatalf("unexpected reply received:\n%s", incomingFrame)
+ }
+ ipPayload, err := incomingFrame[3 /* Payload */].ToBytes()
+ if err != nil {
+ t.Fatalf("failed to parse ICMPv6 header: incomingPacket[3].ToBytes() = (_, %s)", err)
+ }
+ offset := *incomingFrame[2 /* IPv6FragmentExtHdr */].(*testbench.IPv6FragmentExtHdr).FragmentOffset
+ offset *= header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit
+ if copied := copy(reassembledPayload[offset:], ipPayload); copied != len(ipPayload) {
+ t.Fatalf("wrong number of bytes copied into reassembledPayload: got = %d, want = %d", copied, len(ipPayload))
+ }
+ bytesReceived += len(ipPayload)
+ }
+
+ if test.expectReply {
+ if diff := cmp.Diff(originalPayload, reassembledPayload[header.ICMPv6HeaderSize:]); diff != "" {
+ t.Fatalf("reassembledPayload mismatch (-want +got):\n%s", diff)
+ }
+ }
+ })
}
}
diff --git a/test/packetimpact/tests/ipv6_unknown_options_action_test.go b/test/packetimpact/tests/ipv6_unknown_options_action_test.go
index e79d74476..cb5396417 100644
--- a/test/packetimpact/tests/ipv6_unknown_options_action_test.go
+++ b/test/packetimpact/tests/ipv6_unknown_options_action_test.go
@@ -27,7 +27,7 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
func mkHopByHopOptionsExtHdr(optType byte) testbench.Layer {
@@ -141,8 +141,7 @@ func TestIPv6UnknownOptionAction(t *testing.T) {
} {
t.Run(tt.description, func(t *testing.T) {
dut := testbench.NewDUT(t)
- defer dut.TearDown()
- ipv6Conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
+ ipv6Conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
conn := (*testbench.Connection)(&ipv6Conn)
defer ipv6Conn.Close(t)
diff --git a/test/packetimpact/tests/tcp_cork_mss_test.go b/test/packetimpact/tests/tcp_cork_mss_test.go
index 8feea4a82..a7ba5035e 100644
--- a/test/packetimpact/tests/tcp_cork_mss_test.go
+++ b/test/packetimpact/tests/tcp_cork_mss_test.go
@@ -25,16 +25,15 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(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})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
const mss = uint32(header.TCPDefaultMSS)
diff --git a/test/packetimpact/tests/tcp_handshake_window_size_test.go b/test/packetimpact/tests/tcp_handshake_window_size_test.go
index 22937d92f..5d1266f3c 100644
--- a/test/packetimpact/tests/tcp_handshake_window_size_test.go
+++ b/test/packetimpact/tests/tcp_handshake_window_size_test.go
@@ -25,17 +25,16 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(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})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
// Start handshake with zero window size.
diff --git a/test/packetimpact/tests/tcp_linger_test.go b/test/packetimpact/tests/tcp_linger_test.go
index b9a0409aa..bc4b64388 100644
--- a/test/packetimpact/tests/tcp_linger_test.go
+++ b/test/packetimpact/tests/tcp_linger_test.go
@@ -27,12 +27,12 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
func createSocket(t *testing.T, dut testbench.DUT) (int32, int32, testbench.TCPIPv4) {
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 := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
conn.Connect(t)
acceptFD, _ := dut.Accept(t, listenFD)
return acceptFD, listenFD, conn
@@ -41,7 +41,6 @@ func createSocket(t *testing.T, dut testbench.DUT) (int32, int32, testbench.TCPI
func closeAll(t *testing.T, dut testbench.DUT, listenFD int32, conn testbench.TCPIPv4) {
conn.Close(t)
dut.Close(t, listenFD)
- dut.TearDown()
}
// lingerDuration is the timeout value used with SO_LINGER socket option.
@@ -266,5 +265,4 @@ func TestTCPLingerNonEstablished(t *testing.T) {
if diff > lingerDuration {
t.Errorf("expected close to return within %s, but returned after %s", lingerDuration, diff)
}
- dut.TearDown()
}
diff --git a/test/packetimpact/tests/tcp_network_unreachable_test.go b/test/packetimpact/tests/tcp_network_unreachable_test.go
index 2f57dff19..6cd6d2edf 100644
--- a/test/packetimpact/tests/tcp_network_unreachable_test.go
+++ b/test/packetimpact/tests/tcp_network_unreachable_test.go
@@ -17,7 +17,6 @@ package tcp_synsent_reset_test
import (
"context"
"flag"
- "net"
"syscall"
"testing"
"time"
@@ -28,7 +27,7 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
// TestTCPSynSentUnreachable verifies that TCP connections fail immediately when
@@ -37,17 +36,16 @@ func init() {
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))
+ clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv4)
port := uint16(9001)
- conn := testbench.NewTCPIPv4(t, testbench.TCP{SrcPort: &port, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &port})
+ conn := dut.Net.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())
+ copy(sa.Addr[:], dut.Net.LocalIPv4)
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)
}
@@ -74,7 +72,9 @@ func TestTCPSynSentUnreachable(t *testing.T) {
}
var icmpv4 testbench.ICMPv4 = testbench.ICMPv4{
Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable),
- Code: testbench.ICMPv4Code(header.ICMPv4HostUnreachable)}
+ Code: testbench.ICMPv4Code(header.ICMPv4HostUnreachable),
+ }
+
layers = append(layers, &icmpv4, ip, tcp)
rawConn.SendFrameStateless(t, layers)
@@ -89,9 +89,8 @@ func TestTCPSynSentUnreachable(t *testing.T) {
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})
+ clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv6)
+ conn := dut.Net.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.
@@ -99,9 +98,9 @@ func TestTCPSynSentUnreachable6(t *testing.T) {
defer cancel()
sa := unix.SockaddrInet6{
Port: int(conn.SrcPort()),
- ZoneId: uint32(testbench.RemoteInterfaceID),
+ ZoneId: dut.Net.RemoteDevID,
}
- copy(sa.Addr[:], net.IP(net.ParseIP(testbench.LocalIPv6)).To16())
+ copy(sa.Addr[:], dut.Net.LocalIPv6)
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)
}
diff --git a/test/packetimpact/tests/tcp_noaccept_close_rst_test.go b/test/packetimpact/tests/tcp_noaccept_close_rst_test.go
index 82b7a85ff..f0af5352d 100644
--- a/test/packetimpact/tests/tcp_noaccept_close_rst_test.go
+++ b/test/packetimpact/tests/tcp_noaccept_close_rst_test.go
@@ -25,14 +25,13 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
func TestTcpNoAcceptCloseReset(t *testing.T) {
dut := testbench.NewDUT(t)
- defer dut.TearDown()
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 := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
conn.Connect(t)
defer conn.Close(t)
dut.Close(t, listenFd)
diff --git a/test/packetimpact/tests/tcp_outside_the_window_test.go b/test/packetimpact/tests/tcp_outside_the_window_test.go
index 08f759f7c..1b041932a 100644
--- a/test/packetimpact/tests/tcp_outside_the_window_test.go
+++ b/test/packetimpact/tests/tcp_outside_the_window_test.go
@@ -27,7 +27,7 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
// TestTCPOutsideTheWindows tests the behavior of the DUT when packets arrive
@@ -62,10 +62,9 @@ func TestTCPOutsideTheWindow(t *testing.T) {
} {
t.Run(fmt.Sprintf("%s%d", tt.description, tt.seqNumOffset), 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})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
conn.Connect(t)
acceptFD, _ := dut.Accept(t, listenFD)
diff --git a/test/packetimpact/tests/tcp_paws_mechanism_test.go b/test/packetimpact/tests/tcp_paws_mechanism_test.go
index 37f3b56dd..24d9ef4ec 100644
--- a/test/packetimpact/tests/tcp_paws_mechanism_test.go
+++ b/test/packetimpact/tests/tcp_paws_mechanism_test.go
@@ -26,15 +26,14 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
func TestPAWSMechanism(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})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
options := make([]byte, header.TCPOptionTSLength)
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
index d9f3ea0f2..646c93216 100644
--- a/test/packetimpact/tests/tcp_queue_receive_in_syn_sent_test.go
+++ b/test/packetimpact/tests/tcp_queue_receive_in_syn_sent_test.go
@@ -20,7 +20,6 @@ import (
"encoding/hex"
"errors"
"flag"
- "net"
"sync"
"syscall"
"testing"
@@ -32,7 +31,7 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
// TestQueueReceiveInSynSent tests receive behavior when the TCP state
@@ -50,10 +49,9 @@ func TestQueueReceiveInSynSent(t *testing.T) {
} {
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})
+ socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4)
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
sampleData := []byte("Sample Data")
diff --git a/test/packetimpact/tests/tcp_queue_send_in_syn_sent_test.go b/test/packetimpact/tests/tcp_queue_send_in_syn_sent_test.go
index 0ec8fd748..29e51cae3 100644
--- a/test/packetimpact/tests/tcp_queue_send_in_syn_sent_test.go
+++ b/test/packetimpact/tests/tcp_queue_send_in_syn_sent_test.go
@@ -18,7 +18,6 @@ import (
"context"
"errors"
"flag"
- "net"
"sync"
"syscall"
"testing"
@@ -30,7 +29,7 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
// TestQueueSendInSynSent tests send behavior when the TCP state
@@ -48,10 +47,9 @@ func TestQueueSendInSynSent(t *testing.T) {
} {
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})
+ socket, remotePort := dut.CreateBoundSocket(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, dut.Net.RemoteIPv4)
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
sampleData := []byte("Sample Data")
diff --git a/test/packetimpact/tests/tcp_rcv_buf_space_test.go b/test/packetimpact/tests/tcp_rcv_buf_space_test.go
index cfbba1e8e..d6ad5cda6 100644
--- a/test/packetimpact/tests/tcp_rcv_buf_space_test.go
+++ b/test/packetimpact/tests/tcp_rcv_buf_space_test.go
@@ -26,7 +26,7 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
// TestReduceRecvBuf tests that a packet within window is still dropped
@@ -34,10 +34,9 @@ func init() {
// segment.
func TestReduceRecvBuf(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})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
conn.Connect(t)
diff --git a/test/packetimpact/tests/tcp_reordering_test.go b/test/packetimpact/tests/tcp_reordering_test.go
index b4aeaab57..ca352dbc7 100644
--- a/test/packetimpact/tests/tcp_reordering_test.go
+++ b/test/packetimpact/tests/tcp_reordering_test.go
@@ -22,19 +22,18 @@ 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.Initialize(flag.CommandLine)
}
func TestReorderingWindow(t *testing.T) {
- dut := tb.NewDUT(t)
- defer dut.TearDown()
+ dut := testbench.NewDUT(t)
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})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
// Enable SACK.
@@ -54,13 +53,13 @@ func TestReorderingWindow(t *testing.T) {
acceptFd, _ := dut.Accept(t, listenFd)
defer dut.Close(t, acceptFd)
- if tb.Native {
+ if testbench.Native {
// 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.Native {
+ if !testbench.Native {
// 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.
@@ -75,14 +74,14 @@ func TestReorderingWindow(t *testing.T) {
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)
+ gotOne, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(sn))}, time.Second)
sn.UpdateForward(seqnum.Size(len(payload)))
if err != nil {
- t.Errorf("Expect #%d: %s", i+1, err)
+ t.Fatalf("Expect #%d: %s", i+1, err)
continue
}
if gotOne == nil {
- t.Errorf("#%d: expected a packet within a second but got none", i+1)
+ t.Fatalf("#%d: expected a packet within a second but got none", i+1)
}
}
@@ -97,13 +96,13 @@ func TestReorderingWindow(t *testing.T) {
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]})
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.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)))})
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1) + uint32(len(payload)))})
// Check for retransmit.
- gotOne, err := conn.Expect(t, tb.TCP{SeqNum: tb.Uint32(uint32(seqNum1))}, time.Second)
+ gotOne, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, time.Second)
if err != nil {
t.Error("Expect for retransmit:", err)
}
@@ -123,29 +122,29 @@ func TestReorderingWindow(t *testing.T) {
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]})
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.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)
+ gotOne, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(sn))}, time.Second)
sn.UpdateForward(seqnum.Size(len(payload)))
if err != nil {
- t.Errorf("Expect #%d: %s", i+1, err)
+ t.Fatalf("Expect #%d: %s", i+1, err)
continue
}
if gotOne == nil {
- t.Errorf("#%d: expected a packet within a second but got none", i+1)
+ t.Fatalf("#%d: expected a packet within a second but got none", i+1)
}
}
- if !tb.Native {
+ if !testbench.Native {
// 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 {
+ if got, err := conn.Expect(t, testbench.TCP{}, 100*time.Millisecond); got != nil || err == nil {
t.Fatalf("expected no packets within 100 millisecond, but got one: %s", got)
}
return
@@ -155,20 +154,20 @@ func TestReorderingWindow(t *testing.T) {
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)
+ gotOne, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(sn))}, time.Second)
sn.UpdateForward(seqnum.Size(len(payload)))
if err != nil {
- t.Errorf("Expect #%d: %s", i+1, err)
+ t.Fatalf("Expect #%d: %s", i+1, err)
continue
}
if gotOne == nil {
- t.Errorf("#%d: expected a packet within a second but got none", i+1)
+ t.Fatalf("#%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 {
+ if got, err := conn.Expect(t, testbench.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 072014ff8..27e9641b1 100644
--- a/test/packetimpact/tests/tcp_retransmits_test.go
+++ b/test/packetimpact/tests/tcp_retransmits_test.go
@@ -25,17 +25,16 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
// TestRetransmits tests retransmits occur at exponentially increasing
// time intervals.
func TestRetransmits(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})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
conn.Connect(t)
diff --git a/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go b/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go
index f91b06ba1..418393796 100644
--- a/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go
+++ b/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go
@@ -16,7 +16,6 @@ package tcp_send_window_sizes_piggyback_test
import (
"flag"
- "fmt"
"testing"
"time"
@@ -26,7 +25,7 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
// TestSendWindowSizesPiggyback tests cases where segment sizes are close to
@@ -58,13 +57,12 @@ func TestSendWindowSizesPiggyback(t *testing.T) {
// 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) {
+ t.Run(tt.description, 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})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort, WindowSize: testbench.Uint16(tt.windowSize)}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
conn.Connect(t)
diff --git a/test/packetimpact/tests/tcp_synrcvd_reset_test.go b/test/packetimpact/tests/tcp_synrcvd_reset_test.go
index 57d034dd1..c5bbd29ee 100644
--- a/test/packetimpact/tests/tcp_synrcvd_reset_test.go
+++ b/test/packetimpact/tests/tcp_synrcvd_reset_test.go
@@ -25,16 +25,15 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(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})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
// Expect dut connection to have transitioned to SYN-RCVD state.
diff --git a/test/packetimpact/tests/tcp_synsent_reset_test.go b/test/packetimpact/tests/tcp_synsent_reset_test.go
index eac8eb19d..2c8bb101b 100644
--- a/test/packetimpact/tests/tcp_synsent_reset_test.go
+++ b/test/packetimpact/tests/tcp_synsent_reset_test.go
@@ -16,34 +16,33 @@ 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"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
)
func init() {
- tb.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
// dutSynSentState sets up the dut connection in SYN-SENT state.
-func dutSynSentState(t *testing.T) (*tb.DUT, *tb.TCPIPv4, uint16, uint16) {
+func dutSynSentState(t *testing.T) (*testbench.DUT, *testbench.TCPIPv4, uint16, uint16) {
t.Helper()
- dut := tb.NewDUT(t)
+ dut := testbench.NewDUT(t)
- clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, net.ParseIP(tb.RemoteIPv4))
+ clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv4)
port := uint16(9001)
- conn := tb.NewTCPIPv4(t, tb.TCP{SrcPort: &port, DstPort: &clientPort}, tb.TCP{SrcPort: &clientPort, DstPort: &port})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &port, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &port})
sa := unix.SockaddrInet4{Port: int(port)}
- copy(sa.Addr[:], net.IP(net.ParseIP(tb.LocalIPv4)).To4())
+ copy(sa.Addr[:], dut.Net.LocalIPv4)
// 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 {
+ if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}, nil, time.Second); err != nil {
t.Fatalf("expected SYN\n")
}
@@ -52,14 +51,13 @@ func dutSynSentState(t *testing.T) (*tb.DUT, *tb.TCPIPv4, uint16, uint16) {
// TestTCPSynSentReset tests RFC793, p67: SYN-SENT to CLOSED transition.
func TestTCPSynSentReset(t *testing.T) {
- dut, conn, _, _ := dutSynSentState(t)
+ _, conn, _, _ := dutSynSentState(t)
defer conn.Close(t)
- defer dut.TearDown()
- conn.Send(t, tb.TCP{Flags: tb.Uint8(header.TCPFlagRst | header.TCPFlagAck)})
+ conn.Send(t, testbench.TCP{Flags: testbench.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 {
+ 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")
}
}
@@ -68,23 +66,22 @@ func TestTCPSynSentReset(t *testing.T) {
// 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})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &remotePort, DstPort: &clientPort}, testbench.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 {
+ 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\n", err)
}
- conn.Send(t, tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)})
+ 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, tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)})
- if _, err := conn.ExpectData(t, &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/tcp_timewait_reset_test.go b/test/packetimpact/tests/tcp_timewait_reset_test.go
index 2f76a6531..d1d2fb83d 100644
--- a/test/packetimpact/tests/tcp_timewait_reset_test.go
+++ b/test/packetimpact/tests/tcp_timewait_reset_test.go
@@ -25,16 +25,15 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
// TestTimeWaitReset tests handling of RST when in TIME_WAIT state.
func TestTimeWaitReset(t *testing.T) {
dut := testbench.NewDUT(t)
- defer dut.TearDown()
listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/)
defer dut.Close(t, listenFD)
- conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
conn.Connect(t)
diff --git a/test/packetimpact/tests/tcp_unacc_seq_ack_test.go b/test/packetimpact/tests/tcp_unacc_seq_ack_test.go
index d078bbf15..ea962c818 100644
--- a/test/packetimpact/tests/tcp_unacc_seq_ack_test.go
+++ b/test/packetimpact/tests/tcp_unacc_seq_ack_test.go
@@ -28,7 +28,7 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
func TestEstablishedUnaccSeqAck(t *testing.T) {
@@ -48,10 +48,9 @@ func TestEstablishedUnaccSeqAck(t *testing.T) {
} {
t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) {
dut := testbench.NewDUT(t)
- defer dut.TearDown()
listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/)
defer dut.Close(t, listenFD)
- conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
conn.Connect(t)
@@ -102,10 +101,9 @@ func TestPassiveCloseUnaccSeqAck(t *testing.T) {
} {
t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) {
dut := testbench.NewDUT(t)
- defer dut.TearDown()
listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/)
defer dut.Close(t, listenFD)
- conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
conn.Connect(t)
@@ -164,10 +162,9 @@ func TestActiveCloseUnaccpSeqAck(t *testing.T) {
} {
t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) {
dut := testbench.NewDUT(t)
- defer dut.TearDown()
listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/)
defer dut.Close(t, listenFD)
- conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
conn.Connect(t)
diff --git a/test/packetimpact/tests/tcp_user_timeout_test.go b/test/packetimpact/tests/tcp_user_timeout_test.go
index 551dc78e7..b16e65366 100644
--- a/test/packetimpact/tests/tcp_user_timeout_test.go
+++ b/test/packetimpact/tests/tcp_user_timeout_test.go
@@ -25,7 +25,7 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
func sendPayload(t *testing.T, conn *testbench.TCPIPv4, dut *testbench.DUT, fd int32) {
@@ -64,10 +64,9 @@ func TestTCPUserTimeout(t *testing.T) {
t.Run(tt.description+ttf.description, func(t *testing.T) {
// Create a socket, listen, TCP handshake, and accept.
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})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
conn.Connect(t)
acceptFD, _ := dut.Accept(t, listenFD)
diff --git a/test/packetimpact/tests/tcp_window_shrink_test.go b/test/packetimpact/tests/tcp_window_shrink_test.go
index 5b001fbec..093484721 100644
--- a/test/packetimpact/tests/tcp_window_shrink_test.go
+++ b/test/packetimpact/tests/tcp_window_shrink_test.go
@@ -25,15 +25,14 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
func TestWindowShrink(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})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
conn.Connect(t)
diff --git a/test/packetimpact/tests/tcp_zero_receive_window_test.go b/test/packetimpact/tests/tcp_zero_receive_window_test.go
new file mode 100644
index 000000000..b494e179b
--- /dev/null
+++ b/test/packetimpact/tests/tcp_zero_receive_window_test.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 tcp_zero_receive_window_test
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "testing"
+ "time"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+func init() {
+ testbench.Initialize(flag.CommandLine)
+}
+
+// TestZeroReceiveWindow tests if the DUT sends a zero receive window eventually.
+func TestZeroReceiveWindow(t *testing.T) {
+ for _, payloadLen := range []int{64, 512, 1024} {
+ t.Run(fmt.Sprintf("TestZeroReceiveWindow_with_%dbytes_payload", payloadLen), func(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(t, listenFd)
+ conn := dut.Net.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)
+
+ dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
+
+ samplePayload := &testbench.Payload{Bytes: testbench.GenerateRandomPayload(t, payloadLen)}
+ // Expect the DUT to eventually advertise zero receive window.
+ // The test would timeout otherwise.
+ for {
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload)
+ gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second)
+ if err != nil {
+ t.Fatalf("expected packet was not received: %s", err)
+ }
+ windowSize := *gotTCP.WindowSize
+ t.Logf("got window size = %d", windowSize)
+ if windowSize == 0 {
+ break
+ }
+ }
+ })
+ }
+}
+
+// TestNonZeroReceiveWindow tests for the DUT to never send a zero receive
+// window when the data is being read from the socket buffer.
+func TestNonZeroReceiveWindow(t *testing.T) {
+ for _, payloadLen := range []int{64, 512, 1024} {
+ t.Run(fmt.Sprintf("TestZeroReceiveWindow_with_%dbytes_payload", payloadLen), func(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(t, listenFd)
+ conn := dut.Net.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)
+
+ dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
+
+ samplePayload := &testbench.Payload{Bytes: testbench.GenerateRandomPayload(t, payloadLen)}
+ var rcvWindow uint16
+ initRcv := false
+ // This loop keeps a running rcvWindow value from the initial ACK for the data
+ // we sent. Once we have received ACKs with non-zero receive windows, we break
+ // the loop.
+ for {
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload)
+ gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second)
+ if err != nil {
+ t.Fatalf("expected packet was not received: %s", err)
+ }
+ if ret, _, err := dut.RecvWithErrno(context.Background(), t, acceptFd, int32(payloadLen), 0); ret == -1 {
+ t.Fatalf("dut.RecvWithErrno(ctx, t, %d, %d, 0) = %d,_, %s", acceptFd, payloadLen, ret, err)
+ }
+
+ if *gotTCP.WindowSize == 0 {
+ t.Fatalf("expected non-zero receive window.")
+ }
+ if !initRcv {
+ rcvWindow = uint16(*gotTCP.WindowSize)
+ initRcv = true
+ }
+ if rcvWindow <= uint16(payloadLen) {
+ break
+ }
+ rcvWindow -= uint16(payloadLen)
+ }
+ })
+ }
+}
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 da93267d6..1ab9ee1b2 100644
--- a/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go
+++ b/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go
@@ -25,17 +25,16 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
// TestZeroWindowProbeRetransmit tests retransmits of zero window probes
// to be sent at exponentially inreasing time intervals.
func TestZeroWindowProbeRetransmit(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})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
conn.Connect(t)
diff --git a/test/packetimpact/tests/tcp_zero_window_probe_test.go b/test/packetimpact/tests/tcp_zero_window_probe_test.go
index 44cac42f8..650a569cc 100644
--- a/test/packetimpact/tests/tcp_zero_window_probe_test.go
+++ b/test/packetimpact/tests/tcp_zero_window_probe_test.go
@@ -25,17 +25,16 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
// TestZeroWindowProbe tests few cases of zero window probing over the
// same connection.
func TestZeroWindowProbe(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})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
conn.Connect(t)
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 09a1c653f..079fea68c 100644
--- a/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go
+++ b/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go
@@ -25,17 +25,16 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
// TestZeroWindowProbeUserTimeout sanity tests user timeout when we are
// retransmitting zero window probes.
func TestZeroWindowProbeUserTimeout(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})
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
defer conn.Close(t)
conn.Connect(t)
diff --git a/test/packetimpact/tests/udp_any_addr_recv_unicast_test.go b/test/packetimpact/tests/udp_any_addr_recv_unicast_test.go
index 17f32ef65..f4ae00a81 100644
--- a/test/packetimpact/tests/udp_any_addr_recv_unicast_test.go
+++ b/test/packetimpact/tests/udp_any_addr_recv_unicast_test.go
@@ -26,21 +26,20 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(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})
+ conn := dut.Net.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.IPv4{DstAddr: testbench.Address(tcpip.Address(dut.Net.RemoteIPv4))},
testbench.UDP{},
&testbench.Payload{Bytes: payload},
)
diff --git a/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go b/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go
index 3d2791a6e..52c6f9d91 100644
--- a/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go
+++ b/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go
@@ -30,16 +30,15 @@ import (
var oneSecond = unix.Timeval{Sec: 1, Usec: 0}
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(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))
+ remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, dut.Net.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})
+ conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
defer conn.Close(t)
for _, mcastAddr := range []net.IP{
@@ -66,11 +65,10 @@ func TestDiscardsUDPPacketsWithMcastSourceAddressV4(t *testing.T) {
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))
+ remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, dut.Net.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})
+ conn := dut.Net.NewUDPIPv6(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
defer conn.Close(t)
for _, mcastAddr := range []net.IP{
diff --git a/test/packetimpact/tests/udp_icmp_error_propagation_test.go b/test/packetimpact/tests/udp_icmp_error_propagation_test.go
index df35d16c8..cd4523e88 100644
--- a/test/packetimpact/tests/udp_icmp_error_propagation_test.go
+++ b/test/packetimpact/tests/udp_icmp_error_propagation_test.go
@@ -30,7 +30,7 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
type connectionMode bool
@@ -229,7 +229,6 @@ func TestUDPICMPErrorPropagation(t *testing.T) {
} {
t.Run(fmt.Sprintf("%s/%s/%s", connect, icmpErr, errDetect.name), func(t *testing.T) {
dut := testbench.NewDUT(t)
- defer dut.TearDown()
remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero)
defer dut.Close(t, remoteFD)
@@ -239,7 +238,7 @@ func TestUDPICMPErrorPropagation(t *testing.T) {
cleanFD, cleanPort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero)
defer dut.Close(t, cleanFD)
- conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
+ conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
defer conn.Close(t)
if connect {
@@ -261,7 +260,7 @@ 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 := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
+ connClean := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
defer connClean.Close(t)
errDetectConn = &connClean
@@ -283,7 +282,6 @@ func TestICMPErrorDuringUDPRecv(t *testing.T) {
t.Run(fmt.Sprintf("%s/%s", connect, icmpErr), func(t *testing.T) {
dut := testbench.NewDUT(t)
- defer dut.TearDown()
remoteFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero)
defer dut.Close(t, remoteFD)
@@ -293,7 +291,7 @@ func TestICMPErrorDuringUDPRecv(t *testing.T) {
cleanFD, cleanPort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4zero)
defer dut.Close(t, cleanFD)
- conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
+ conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
defer conn.Close(t)
if connect {
diff --git a/test/packetimpact/tests/udp_recv_mcast_bcast_test.go b/test/packetimpact/tests/udp_recv_mcast_bcast_test.go
index 526173969..b29c07825 100644
--- a/test/packetimpact/tests/udp_recv_mcast_bcast_test.go
+++ b/test/packetimpact/tests/udp_recv_mcast_bcast_test.go
@@ -29,12 +29,12 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
func TestUDPRecvMcastBcast(t *testing.T) {
- subnetBcastAddr := broadcastAddr(net.ParseIP(testbench.RemoteIPv4), net.CIDRMask(testbench.IPv4PrefixLength, 32))
-
+ dut := testbench.NewDUT(t)
+ subnetBcastAddr := broadcastAddr(dut.Net.RemoteIPv4, net.CIDRMask(dut.Net.IPv4PrefixLength, 32))
for _, v := range []struct {
bound, to net.IP
}{
@@ -43,17 +43,22 @@ func TestUDPRecvMcastBcast(t *testing.T) {
{bound: net.IPv4zero, to: net.IPv4allsys},
{bound: subnetBcastAddr, to: subnetBcastAddr},
- {bound: subnetBcastAddr, to: net.IPv4bcast},
+
+ // FIXME(gvisor.dev/issue/4896): Previously by the time subnetBcastAddr is
+ // created, IPv4PrefixLength is still 0 because genPseudoFlags is not called
+ // yet, it was only called in NewDUT, so the test didn't do what the author
+ // original intended to and becomes failing because we process all flags at
+ // the very beginning.
+ //
+ // {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})
+ conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
defer conn.Close(t)
payload := testbench.GenerateRandomPayload(t, 1<<10 /* 1 KiB */)
@@ -73,15 +78,14 @@ func TestUDPRecvMcastBcast(t *testing.T) {
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))
+ boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, dut.Net.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})
+ conn := dut.Net.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)),
+ broadcastAddr(dut.Net.RemoteIPv4, net.CIDRMask(dut.Net.IPv4PrefixLength, 32)),
net.IPv4(255, 255, 255, 255),
net.IPv4(224, 0, 0, 1),
} {
@@ -102,9 +106,10 @@ func TestUDPDoesntRecvMcastBcastOnUnicastAddr(t *testing.T) {
}
func broadcastAddr(ip net.IP, mask net.IPMask) net.IP {
+ result := make(net.IP, net.IPv4len)
ip4 := ip.To4()
for i := range ip4 {
- ip4[i] |= ^mask[i]
+ result[i] = ip4[i] | ^mask[i]
}
- return ip4
+ return result
}
diff --git a/test/packetimpact/tests/udp_send_recv_dgram_test.go b/test/packetimpact/tests/udp_send_recv_dgram_test.go
index 91b967400..7ee2c8014 100644
--- a/test/packetimpact/tests/udp_send_recv_dgram_test.go
+++ b/test/packetimpact/tests/udp_send_recv_dgram_test.go
@@ -26,7 +26,7 @@ import (
)
func init() {
- testbench.RegisterFlags(flag.CommandLine)
+ testbench.Initialize(flag.CommandLine)
}
type udpConn interface {
@@ -38,7 +38,6 @@ type udpConn interface {
func TestUDP(t *testing.T) {
dut := testbench.NewDUT(t)
- defer dut.TearDown()
for _, isIPv4 := range []bool{true, false} {
ipVersionName := "IPv6"
@@ -46,24 +45,24 @@ func TestUDP(t *testing.T) {
ipVersionName = "IPv4"
}
t.Run(ipVersionName, func(t *testing.T) {
- var addr string
+ var addr net.IP
if isIPv4 {
- addr = testbench.RemoteIPv4
+ addr = dut.Net.RemoteIPv4
} else {
- addr = testbench.RemoteIPv6
+ addr = dut.Net.RemoteIPv6
}
- boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP(addr))
+ boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, addr)
defer dut.Close(t, boundFD)
var conn udpConn
var localAddr unix.Sockaddr
if isIPv4 {
- v4Conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
+ v4Conn := dut.Net.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)
+ v6Conn := dut.Net.NewUDPIPv6(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
+ localAddr = v6Conn.LocalAddr(t, dut.Net.RemoteDevID)
conn = &v6Conn
}
defer conn.Close(t)
diff --git a/test/perf/BUILD b/test/perf/BUILD
index b763be50e..e25f090ae 100644
--- a/test/perf/BUILD
+++ b/test/perf/BUILD
@@ -1,3 +1,4 @@
+load("//tools:defs.bzl", "more_shards")
load("//test/runner:defs.bzl", "syscall_test")
package(licenses = ["notice"])
@@ -37,7 +38,7 @@ syscall_test(
syscall_test(
size = "enormous",
debug = False,
- shard_count = 10,
+ shard_count = more_shards,
tags = ["nogotsan"],
test = "//test/perf/linux:getdents_benchmark",
)
diff --git a/test/root/BUILD b/test/root/BUILD
index a9130b34f..8d9fff578 100644
--- a/test/root/BUILD
+++ b/test/root/BUILD
@@ -1,5 +1,4 @@
load("//tools:defs.bzl", "go_library", "go_test")
-load("//tools/vm:defs.bzl", "vm_test")
package(licenses = ["notice"])
@@ -24,12 +23,8 @@ go_test(
],
library = ":root",
tags = [
- # Requires docker and runsc to be configured before the test runs.
- # Also, the test needs to be run as root. Note that below, the
- # root_vm_test relies on the default runtime 'runsc' being installed by
- # the default installer.
- "manual",
"local",
+ "manual",
],
visibility = ["//:sandbox"],
deps = [
@@ -46,10 +41,3 @@ go_test(
"@org_golang_x_sys//unix:go_default_library",
],
)
-
-vm_test(
- name = "root_vm_test",
- size = "large",
- shard_count = 1,
- targets = [":root_test"],
-)
diff --git a/test/root/crictl_test.go b/test/root/crictl_test.go
index 11ac5cb52..df52dd381 100644
--- a/test/root/crictl_test.go
+++ b/test/root/crictl_test.go
@@ -315,7 +315,7 @@ const (
// v1 is the containerd API v1.
v1 string = "v1"
- // v1 is the containerd API v21.
+ // v2 is the containerd API v2.
v2 string = "v2"
)
@@ -480,7 +480,7 @@ func setup(t *testing.T, version string) (*criutil.Crictl, func(), error) {
}
// Wait for containerd to boot.
- if err := testutil.WaitUntilRead(startupR, "Start streaming server", nil, 10*time.Second); err != nil {
+ if err := testutil.WaitUntilRead(startupR, "Start streaming server", 10*time.Second); err != nil {
t.Fatalf("failed to start containerd: %v", err)
}
diff --git a/test/root/oom_score_adj_test.go b/test/root/oom_score_adj_test.go
index 4243eb59e..0dcc0fdea 100644
--- a/test/root/oom_score_adj_test.go
+++ b/test/root/oom_score_adj_test.go
@@ -40,11 +40,7 @@ var (
// TestOOMScoreAdjSingle tests that oom_score_adj is set properly in a
// single container sandbox.
func TestOOMScoreAdjSingle(t *testing.T) {
- ppid, err := specutils.GetParentPid(os.Getpid())
- if err != nil {
- t.Fatalf("getting parent pid: %v", err)
- }
- parentOOMScoreAdj, err := specutils.GetOOMScoreAdj(ppid)
+ parentOOMScoreAdj, err := specutils.GetOOMScoreAdj(os.Getppid())
if err != nil {
t.Fatalf("getting parent oom_score_adj: %v", err)
}
@@ -122,11 +118,7 @@ func TestOOMScoreAdjSingle(t *testing.T) {
// TestOOMScoreAdjMulti tests that oom_score_adj is set properly in a
// multi-container sandbox.
func TestOOMScoreAdjMulti(t *testing.T) {
- ppid, err := specutils.GetParentPid(os.Getpid())
- if err != nil {
- t.Fatalf("getting parent pid: %v", err)
- }
- parentOOMScoreAdj, err := specutils.GetOOMScoreAdj(ppid)
+ parentOOMScoreAdj, err := specutils.GetOOMScoreAdj(os.Getppid())
if err != nil {
t.Fatalf("getting parent oom_score_adj: %v", err)
}
diff --git a/test/runner/defs.bzl b/test/runner/defs.bzl
index 9b5994d59..829247657 100644
--- a/test/runner/defs.bzl
+++ b/test/runner/defs.bzl
@@ -1,6 +1,6 @@
"""Defines a rule for syscall test targets."""
-load("//tools:defs.bzl", "default_platform", "loopback", "platforms")
+load("//tools:defs.bzl", "default_platform", "platforms")
def _runner_test_impl(ctx):
# Generate a runner binary.
@@ -12,7 +12,7 @@ def _runner_test_impl(ctx):
" mkdir -p \"${TEST_UNDECLARED_OUTPUTS_DIR}\"",
" chmod a+rwx \"${TEST_UNDECLARED_OUTPUTS_DIR}\"",
"fi",
- "exec %s %s %s\n" % (
+ "exec %s %s \"$@\" %s\n" % (
ctx.files.runner[0].short_path,
" ".join(ctx.attr.runner_args),
ctx.files.test[0].short_path,
@@ -52,8 +52,6 @@ _runner_test = rule(
def _syscall_test(
test,
- shard_count,
- size,
platform,
use_tmpfs,
tags,
@@ -63,7 +61,8 @@ def _syscall_test(
overlay = False,
add_uds_tree = False,
vfs2 = False,
- fuse = False):
+ fuse = False,
+ **kwargs):
# Prepend "runsc" to non-native platform names.
full_platform = platform if platform == "native" else "runsc_" + platform
@@ -97,10 +96,11 @@ 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")
+ tags.append("requires-net:ipv4")
+ tags.append("block-network")
# gotsan makes sense only if tests are running in gVisor.
if platform == "native":
@@ -125,16 +125,12 @@ def _syscall_test(
name = name,
test = test,
runner_args = runner_args,
- data = [loopback],
- size = size,
tags = tags,
- shard_count = shard_count,
+ **kwargs
)
def syscall_test(
test,
- shard_count = 5,
- size = "small",
use_tmpfs = False,
add_overlay = False,
add_uds_tree = False,
@@ -142,18 +138,21 @@ def syscall_test(
vfs2 = True,
fuse = False,
debug = True,
- tags = None):
+ tags = None,
+ **kwargs):
"""syscall_test is a macro that will create targets for all platforms.
Args:
test: the test target.
- shard_count: shards for defined tests.
- size: the defined test size.
use_tmpfs: use tmpfs in the defined tests.
add_overlay: add an overlay test.
add_uds_tree: add a UDS test.
add_hostinet: add a hostinet test.
+ vfs2: enable VFS2 support.
+ fuse: enable FUSE support.
+ debug: enable debug output.
tags: starting test tags.
+ **kwargs: additional test arguments.
"""
if not tags:
tags = []
@@ -173,8 +172,6 @@ def syscall_test(
_syscall_test(
test = test,
- shard_count = shard_count,
- size = size,
platform = default_platform,
use_tmpfs = use_tmpfs,
add_uds_tree = add_uds_tree,
@@ -182,6 +179,7 @@ def syscall_test(
debug = debug,
vfs2 = True,
fuse = fuse,
+ **kwargs
)
if fuse:
# Only generate *_vfs2_fuse target if fuse parameter is enabled.
@@ -189,38 +187,35 @@ def syscall_test(
_syscall_test(
test = test,
- shard_count = shard_count,
- size = size,
platform = "native",
use_tmpfs = False,
add_uds_tree = add_uds_tree,
tags = list(tags),
debug = debug,
+ **kwargs
)
for (platform, platform_tags) in platforms.items():
_syscall_test(
test = test,
- shard_count = shard_count,
- size = size,
platform = platform,
use_tmpfs = use_tmpfs,
add_uds_tree = add_uds_tree,
tags = platform_tags + tags,
debug = debug,
+ **kwargs
)
if add_overlay:
_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] + tags,
debug = debug,
overlay = True,
+ **kwargs
)
# TODO(gvisor.dev/issue/4407): Remove tags to enable VFS2 overlay tests.
@@ -230,8 +225,6 @@ def syscall_test(
overlay_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,
@@ -239,38 +232,35 @@ def syscall_test(
debug = debug,
overlay = True,
vfs2 = True,
+ **kwargs
)
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,
debug = debug,
+ **kwargs
)
if not use_tmpfs:
# Also test shared gofer access.
_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] + tags,
debug = debug,
file_access = "shared",
+ **kwargs
)
_syscall_test(
test = test,
- shard_count = shard_count,
- size = size,
platform = default_platform,
use_tmpfs = use_tmpfs,
add_uds_tree = add_uds_tree,
@@ -278,4 +268,5 @@ def syscall_test(
debug = debug,
file_access = "shared",
vfs2 = True,
+ **kwargs
)
diff --git a/test/runner/runner.go b/test/runner/runner.go
index 22d535f8d..7ab2c3edf 100644
--- a/test/runner/runner.go
+++ b/test/runner/runner.go
@@ -53,6 +53,9 @@ var (
runscPath = flag.String("runsc", "", "path to runsc binary")
addUDSTree = flag.Bool("add-uds-tree", false, "expose a tree of UDS utilities for use in tests")
+ // TODO(gvisor.dev/issue/4572): properly support leak checking for runsc, and
+ // set to true as the default for the test runner.
+ leakCheck = flag.Bool("leak-check", false, "check for reference leaks")
)
// runTestCaseNative runs the test case directly on the host machine.
@@ -174,6 +177,9 @@ func runRunsc(tc gtest.TestCase, spec *specs.Spec) error {
if *addUDSTree {
args = append(args, "-fsgofer-host-uds")
}
+ if *leakCheck {
+ args = append(args, "-ref-leak-mode=log-names")
+ }
testLogDir := ""
if undeclaredOutputsDir, ok := syscall.Getenv("TEST_UNDECLARED_OUTPUTS_DIR"); ok {
diff --git a/test/runtimes/BUILD b/test/runtimes/BUILD
index 22b526f59..510ffe013 100644
--- a/test/runtimes/BUILD
+++ b/test/runtimes/BUILD
@@ -1,4 +1,4 @@
-load("//tools:defs.bzl", "bzl_library")
+load("//tools:defs.bzl", "bzl_library", "more_shards", "most_shards")
load("//test/runtimes:defs.bzl", "runtime_test")
package(licenses = ["notice"])
@@ -7,7 +7,7 @@ runtime_test(
name = "go1.12",
exclude_file = "exclude/go1.12.csv",
lang = "go",
- shard_count = 8,
+ shard_count = more_shards,
)
runtime_test(
@@ -15,28 +15,28 @@ runtime_test(
batch = 100,
exclude_file = "exclude/java11.csv",
lang = "java",
- shard_count = 16,
+ shard_count = most_shards,
)
runtime_test(
name = "nodejs12.4.0",
exclude_file = "exclude/nodejs12.4.0.csv",
lang = "nodejs",
- shard_count = 8,
+ shard_count = most_shards,
)
runtime_test(
name = "php7.3.6",
exclude_file = "exclude/php7.3.6.csv",
lang = "php",
- shard_count = 8,
+ shard_count = more_shards,
)
runtime_test(
name = "python3.7.3",
exclude_file = "exclude/python3.7.3.csv",
lang = "python",
- shard_count = 8,
+ shard_count = more_shards,
)
bzl_library(
diff --git a/test/runtimes/exclude/java11.csv b/test/runtimes/exclude/java11.csv
index d978baca7..e41441374 100644
--- a/test/runtimes/exclude/java11.csv
+++ b/test/runtimes/exclude/java11.csv
@@ -144,6 +144,7 @@ jdk/jfr/cmd/TestSplit.java,,java.lang.RuntimeException: 'Missing file' missing f
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/oldobject/TestLargeRootSet.java,,Flaky - `main' threw exception: java.lang.RuntimeException: Could not find root object
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,,
diff --git a/test/runtimes/exclude/nodejs12.4.0.csv b/test/runtimes/exclude/nodejs12.4.0.csv
index ba993814f..c4e7917ec 100644
--- a/test/runtimes/exclude/nodejs12.4.0.csv
+++ b/test/runtimes/exclude/nodejs12.4.0.csv
@@ -1,31 +1,22 @@
test name,bug id,comment
async-hooks/test-statwatcher.js,https://github.com/nodejs/node/issues/21425,Check for fix inclusion in nodejs releases after 2020-03-29
-benchmark/test-benchmark-fs.js,,
-benchmark/test-benchmark-napi.js,,
+benchmark/test-benchmark-fs.js,,Broken test
+benchmark/test-benchmark-napi.js,,Broken test
doctool/test-make-doc.js,b/68848110,Expected to fail.
internet/test-dgram-multicast-set-interface-lo.js,b/162798882,
-internet/test-doctool-versions.js,,
-internet/test-uv-threadpool-schedule.js,,
-parallel/test-cluster-dgram-reuse.js,b/64024294,
+internet/test-doctool-versions.js,,Broken test
+internet/test-uv-threadpool-schedule.js,,Broken test
parallel/test-dgram-bind-fd.js,b/132447356,
parallel/test-dgram-socket-buffer-size.js,b/68847921,
parallel/test-dns-channel-timeout.js,b/161893056,
-parallel/test-fs-access.js,,
-parallel/test-fs-watchfile.js,,Flaky - File already exists error
-parallel/test-fs-write-stream.js,b/166819807,Flaky
-parallel/test-fs-write-stream-double-close.js,b/166819807,Flaky
-parallel/test-fs-write-stream-throw-type-error.js,b/166819807,Flaky
-parallel/test-http-writable-true-after-close.js,,Flaky - Mismatched <anonymous> function calls. Expected exactly 1 actual 2
+parallel/test-fs-access.js,,Broken test
+parallel/test-fs-watchfile.js,b/166819807,Flaky - VFS1 only
+parallel/test-fs-write-stream.js,b/166819807,Flaky - VFS1 only
+parallel/test-fs-write-stream-double-close.js,b/166819807,Flaky - VFS1 only
+parallel/test-fs-write-stream-throw-type-error.js,b/166819807,Flaky - VFS1 only
+parallel/test-http-writable-true-after-close.js,b/171301436,Flaky - Mismatched <anonymous> function calls. Expected exactly 1 actual 2
parallel/test-os.js,b/63997097,
-parallel/test-net-server-listen-options.js,,Flaky - EADDRINUSE
-parallel/test-process-uid-gid.js,,
-parallel/test-tls-cli-min-version-1.0.js,,Flaky - EADDRINUSE
-parallel/test-tls-cli-min-version-1.1.js,,Flaky - EADDRINUSE
-parallel/test-tls-cli-min-version-1.2.js,,Flaky - EADDRINUSE
-parallel/test-tls-cli-min-version-1.3.js,,Flaky - EADDRINUSE
-parallel/test-tls-cli-max-version-1.2.js,,Flaky - EADDRINUSE
-parallel/test-tls-cli-max-version-1.3.js,,Flaky - EADDRINUSE
-parallel/test-tls-min-max-version.js,,Flaky - EADDRINUSE
+parallel/test-process-uid-gid.js,,Does not work inside Docker with gid nobody
pseudo-tty/test-assert-colors.js,b/162801321,
pseudo-tty/test-assert-no-color.js,b/162801321,
pseudo-tty/test-assert-position-indicator.js,b/162801321,
@@ -48,11 +39,7 @@ pseudo-tty/test-tty-stdout-resize.js,b/162801321,
pseudo-tty/test-tty-stream-constructors.js,b/162801321,
pseudo-tty/test-tty-window-size.js,b/162801321,
pseudo-tty/test-tty-wrap.js,b/162801321,
-pummel/test-heapdump-http2.js,,Flaky
-pummel/test-net-pingpong.js,,
+pummel/test-net-pingpong.js,,Broken test
pummel/test-vm-memleak.js,b/162799436,
-pummel/test-watch-file.js,,Flaky - Timeout
-sequential/test-child-process-pass-fd.js,b/63926391,Flaky
-sequential/test-https-connect-localport.js,,Flaky - EADDRINUSE
-sequential/test-net-bytes-per-incoming-chunk-overhead.js,,flaky - timeout
-tick-processor/test-tick-processor-builtin.js,,
+pummel/test-watch-file.js,,Flaky - VFS1 only
+tick-processor/test-tick-processor-builtin.js,,Broken test
diff --git a/test/runtimes/exclude/php7.3.6.csv b/test/runtimes/exclude/php7.3.6.csv
index a73f3bcfb..c051fe571 100644
--- a/test/runtimes/exclude/php7.3.6.csv
+++ b/test/runtimes/exclude/php7.3.6.csv
@@ -8,6 +8,7 @@ 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/pcre/tests/cache_limit.phpt,,Broken test - Flaky
ext/session/tests/session_module_name_variation4.phpt,,Flaky
ext/session/tests/session_set_save_handler_class_018.phpt,,
ext/session/tests/session_set_save_handler_iface_003.phpt,,
@@ -26,13 +27,14 @@ ext/standard/tests/file/php_fd_wrapper_01.phpt,,
ext/standard/tests/file/php_fd_wrapper_02.phpt,,
ext/standard/tests/file/php_fd_wrapper_03.phpt,,
ext/standard/tests/file/php_fd_wrapper_04.phpt,,
-ext/standard/tests/file/realpath_bug77484.phpt,b/162894969,
+ext/standard/tests/file/realpath_bug77484.phpt,b/162894969,VFS1 only failure
ext/standard/tests/file/rename_variation.phpt,b/68717309,
ext/standard/tests/file/symlink_link_linkinfo_is_link_variation4.phpt,b/162895341,
-ext/standard/tests/file/symlink_link_linkinfo_is_link_variation8.phpt,b/162896223,
+ext/standard/tests/file/symlink_link_linkinfo_is_link_variation8.phpt,b/162896223,VFS1 only failure
ext/standard/tests/general_functions/escapeshellarg_bug71270.phpt,,
ext/standard/tests/general_functions/escapeshellcmd_bug71270.phpt,,
ext/standard/tests/streams/proc_open_bug60120.phpt,,Flaky until php-src 3852a35fdbcb
+ext/standard/tests/streams/proc_open_bug64438.phpt,,Flaky
ext/standard/tests/streams/proc_open_bug69900.phpt,,Flaky
ext/standard/tests/streams/stream_socket_sendto.phpt,,
ext/standard/tests/strings/007.phpt,,
diff --git a/test/runtimes/exclude/python3.7.3.csv b/test/runtimes/exclude/python3.7.3.csv
index 8760f8951..e9fef03b7 100644
--- a/test/runtimes/exclude/python3.7.3.csv
+++ b/test/runtimes/exclude/python3.7.3.csv
@@ -4,7 +4,6 @@ test_asyncore,b/162973328,
test_epoll,b/162983393,
test_fcntl,b/162978767,fcntl invalid argument -- artificial test to make sure something works in 64 bit mode.
test_httplib,b/163000009,OSError: [Errno 98] Address already in use
-test_imaplib,b/162979661,
test_logging,b/162980079,
test_multiprocessing_fork,,Flaky. Sometimes times out.
test_multiprocessing_forkserver,,Flaky. Sometimes times out.
@@ -18,4 +17,3 @@ test_selectors,b/76116849,OSError not raised with epoll
test_smtplib,b/162980434,unclosed sockets
test_signal,,Flaky - signal: alarm clock
test_socket,b/75983380,
-test_subprocess,b/162980831,
diff --git a/test/runtimes/proctor/main.go b/test/runtimes/proctor/main.go
index e5607ac92..81cb68381 100644
--- a/test/runtimes/proctor/main.go
+++ b/test/runtimes/proctor/main.go
@@ -22,6 +22,7 @@ import (
"log"
"os"
"strings"
+ "syscall"
"gvisor.dev/gvisor/test/runtimes/proctor/lib"
)
@@ -33,6 +34,29 @@ var (
pause = flag.Bool("pause", false, "cause container to pause indefinitely, reaping any zombie children")
)
+// setNumFilesLimit changes the NOFILE soft rlimit if it is too high.
+func setNumFilesLimit() error {
+ // In docker containers, the default value of the NOFILE limit is
+ // 1048576. A few runtime tests (e.g. python:test_subprocess)
+ // enumerates all possible file descriptors and these tests can fail by
+ // timeout if the NOFILE limit is too high. On gVisor, syscalls are
+ // slower so these tests will need even more time to pass.
+ const nofile = 32768
+ rLimit := syscall.Rlimit{}
+ err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
+ if err != nil {
+ return fmt.Errorf("failed to get RLIMIT_NOFILE: %v", err)
+ }
+ if rLimit.Cur > nofile {
+ rLimit.Cur = nofile
+ err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
+ if err != nil {
+ return fmt.Errorf("failed to set RLIMIT_NOFILE: %v", err)
+ }
+ }
+ return nil
+}
+
func main() {
flag.Parse()
@@ -74,6 +98,10 @@ func main() {
tests = strings.Split(*testNames, ",")
}
+ if err := setNumFilesLimit(); err != nil {
+ log.Fatalf("%v", err)
+ }
+
// Run tests.
cmds := tr.TestCmds(tests)
for _, cmd := range cmds {
diff --git a/test/runtimes/runner/lib/lib.go b/test/runtimes/runner/lib/lib.go
index 78285cb0e..9272137ff 100644
--- a/test/runtimes/runner/lib/lib.go
+++ b/test/runtimes/runner/lib/lib.go
@@ -35,7 +35,10 @@ import (
// defered functions before exiting. It returns an exit code that should be
// passed to os.Exit.
func RunTests(lang, image, excludeFile string, batchSize int, timeout time.Duration) int {
- // Get tests to exclude..
+ // TODO(gvisor.dev/issue/1624): Remove those tests from all exclude lists
+ // that only fail with VFS1.
+
+ // Get tests to exclude.
excludes, err := getExcludes(excludeFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting exclude list: %s\n", err.Error())
@@ -82,8 +85,7 @@ func getTests(ctx context.Context, d *dockerutil.Container, lang, image string,
return nil, fmt.Errorf("docker exec failed: %v", err)
}
- // Calculate a subset of tests to run corresponding to the current
- // shard.
+ // Calculate a subset of tests.
tests := strings.Fields(list)
sort.Strings(tests)
indices, err := testutil.TestIndicesForShard(len(tests))
@@ -106,6 +108,10 @@ func getTests(ctx context.Context, d *dockerutil.Container, lang, image string,
}
tcs = append(tcs, tests[tc])
}
+ if len(tcs) == 0 {
+ // No tests to add to this batch.
+ continue
+ }
itests = append(itests, testing.InternalTest{
Name: strings.Join(tcs, ", "),
F: func(t *testing.T) {
@@ -116,8 +122,15 @@ func getTests(ctx context.Context, d *dockerutil.Container, lang, image string,
err error
)
+ state, err := d.Status(ctx)
+ if err != nil {
+ t.Fatalf("Could not find container status: %v", err)
+ }
+ if !state.Running {
+ t.Fatalf("container is not running: state = %s", state.Status)
+ }
+
go func() {
- 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)
}()
@@ -125,12 +138,12 @@ func getTests(ctx context.Context, d *dockerutil.Container, lang, image string,
select {
case <-done:
if err == nil {
- fmt.Printf("PASS: (%v)\n\n", time.Since(now))
+ fmt.Printf("PASS: (%v) %d tests passed\n", time.Since(now), len(tcs))
return
}
- t.Errorf("FAIL: (%v):\n%s\n", time.Since(now), output)
+ t.Errorf("FAIL: (%v):\nBatch:\n%s\nOutput:\n%s\n", time.Since(now), strings.Join(tcs, "\n"), output)
case <-time.After(timeout):
- t.Errorf("TIMEOUT: (%v):\n%s\n", time.Since(now), output)
+ t.Errorf("TIMEOUT: (%v):\nBatch:\n%s\nOutput:\n%s\n", time.Since(now), strings.Join(tcs, "\n"), output)
}
},
})
diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD
index f66a9ceb4..a5b9233f7 100644
--- a/test/syscalls/BUILD
+++ b/test/syscalls/BUILD
@@ -1,3 +1,4 @@
+load("//tools:defs.bzl", "more_shards", "most_shards")
load("//test/runner:defs.bzl", "syscall_test")
package(licenses = ["notice"])
@@ -12,7 +13,7 @@ syscall_test(
syscall_test(
size = "large",
- shard_count = 50,
+ shard_count = most_shards,
test = "//test/syscalls/linux:accept_bind_test",
)
@@ -32,7 +33,7 @@ syscall_test(
syscall_test(
size = "medium",
- shard_count = 5,
+ shard_count = more_shards,
test = "//test/syscalls/linux:alarm_test",
)
@@ -66,7 +67,7 @@ syscall_test(
size = "large",
# Produce too many logs in the debug mode.
debug = False,
- shard_count = 50,
+ shard_count = most_shards,
# Takes too long for TSAN. Since this is kind of a stress test that doesn't
# involve much concurrency, TSAN's usefulness here is limited anyway.
tags = ["nogotsan"],
@@ -211,7 +212,7 @@ syscall_test(
syscall_test(
size = "medium",
- shard_count = 5,
+ shard_count = more_shards,
test = "//test/syscalls/linux:futex_test",
)
@@ -258,7 +259,7 @@ syscall_test(
syscall_test(
size = "large",
- shard_count = 5,
+ shard_count = more_shards,
test = "//test/syscalls/linux:itimer_test",
)
@@ -313,7 +314,7 @@ syscall_test(
syscall_test(
size = "medium",
- shard_count = 5,
+ shard_count = more_shards,
test = "//test/syscalls/linux:mmap_test",
)
@@ -347,6 +348,7 @@ syscall_test(
syscall_test(
add_overlay = True,
+ shard_count = more_shards,
test = "//test/syscalls/linux:open_test",
)
@@ -376,7 +378,7 @@ syscall_test(
syscall_test(
size = "large",
add_overlay = True,
- shard_count = 5,
+ shard_count = more_shards,
test = "//test/syscalls/linux:pipe_test",
)
@@ -448,7 +450,7 @@ syscall_test(
syscall_test(
size = "medium",
- shard_count = 5,
+ shard_count = more_shards,
test = "//test/syscalls/linux:pty_test",
)
@@ -475,6 +477,7 @@ syscall_test(
)
syscall_test(
+ shard_count = more_shards,
test = "//test/syscalls/linux:raw_socket_test",
)
@@ -490,7 +493,7 @@ syscall_test(
syscall_test(
size = "medium",
- shard_count = 5,
+ shard_count = more_shards,
test = "//test/syscalls/linux:readv_socket_test",
)
@@ -539,7 +542,7 @@ syscall_test(
)
syscall_test(
- shard_count = 20,
+ shard_count = more_shards,
test = "//test/syscalls/linux:semaphore_test",
)
@@ -594,7 +597,7 @@ syscall_test(
syscall_test(
size = "large",
- shard_count = 50,
+ shard_count = most_shards,
test = "//test/syscalls/linux:socket_abstract_test",
)
@@ -605,7 +608,7 @@ syscall_test(
syscall_test(
size = "large",
- shard_count = 50,
+ shard_count = most_shards,
test = "//test/syscalls/linux:socket_domain_test",
)
@@ -618,19 +621,19 @@ syscall_test(
syscall_test(
size = "large",
add_overlay = True,
- shard_count = 50,
+ shard_count = most_shards,
test = "//test/syscalls/linux:socket_filesystem_test",
)
syscall_test(
size = "large",
- shard_count = 50,
+ shard_count = most_shards,
test = "//test/syscalls/linux:socket_inet_loopback_test",
)
syscall_test(
size = "large",
- shard_count = 50,
+ shard_count = most_shards,
# Takes too long for TSAN. Creates a lot of TCP sockets.
tags = ["nogotsan"],
test = "//test/syscalls/linux:socket_inet_loopback_nogotsan_test",
@@ -638,35 +641,38 @@ syscall_test(
syscall_test(
size = "large",
- shard_count = 50,
+ shard_count = most_shards,
test = "//test/syscalls/linux:socket_ip_tcp_generic_loopback_test",
)
syscall_test(
size = "medium",
+ add_hostinet = True,
test = "//test/syscalls/linux:socket_ip_tcp_loopback_non_blocking_test",
)
syscall_test(
size = "large",
- shard_count = 50,
+ shard_count = most_shards,
test = "//test/syscalls/linux:socket_ip_tcp_loopback_test",
)
syscall_test(
size = "medium",
- shard_count = 50,
+ add_hostinet = True,
+ shard_count = most_shards,
test = "//test/syscalls/linux:socket_ip_tcp_udp_generic_loopback_test",
)
syscall_test(
size = "medium",
+ add_hostinet = True,
test = "//test/syscalls/linux:socket_ip_udp_loopback_non_blocking_test",
)
syscall_test(
size = "large",
- shard_count = 50,
+ shard_count = most_shards,
test = "//test/syscalls/linux:socket_ip_udp_loopback_test",
)
@@ -677,6 +683,8 @@ syscall_test(
syscall_test(
size = "medium",
+ add_hostinet = True,
+ shard_count = more_shards,
# Takes too long under gotsan to run.
tags = ["nogotsan"],
test = "//test/syscalls/linux:socket_ipv4_udp_unbound_loopback_nogotsan_test",
@@ -691,10 +699,15 @@ syscall_test(
)
syscall_test(
+ shard_count = more_shards,
test = "//test/syscalls/linux:socket_ip_unbound_test",
)
syscall_test(
+ test = "//test/syscalls/linux:socket_ip_unbound_netlink_test",
+)
+
+syscall_test(
test = "//test/syscalls/linux:socket_netdevice_test",
)
@@ -719,6 +732,7 @@ syscall_test(
)
syscall_test(
+ add_hostinet = True,
test = "//test/syscalls/linux:socket_non_stream_blocking_local_test",
)
@@ -749,7 +763,7 @@ syscall_test(
syscall_test(
# NOTE(b/116636318): Large sendmsg may stall a long time.
size = "enormous",
- shard_count = 5,
+ shard_count = more_shards,
test = "//test/syscalls/linux:socket_unix_dgram_local_test",
)
@@ -761,14 +775,14 @@ syscall_test(
syscall_test(
size = "large",
add_overlay = True,
- shard_count = 50,
+ shard_count = most_shards,
test = "//test/syscalls/linux:socket_unix_pair_test",
)
syscall_test(
# NOTE(b/116636318): Large sendmsg may stall a long time.
size = "enormous",
- shard_count = 5,
+ shard_count = more_shards,
test = "//test/syscalls/linux:socket_unix_seqpacket_local_test",
)
@@ -794,13 +808,13 @@ syscall_test(
syscall_test(
size = "medium",
- shard_count = 10,
+ shard_count = more_shards,
test = "//test/syscalls/linux:socket_unix_unbound_seqpacket_test",
)
syscall_test(
size = "large",
- shard_count = 50,
+ shard_count = most_shards,
test = "//test/syscalls/linux:socket_unix_unbound_stream_test",
)
@@ -854,7 +868,7 @@ syscall_test(
syscall_test(
size = "medium",
- shard_count = 10,
+ shard_count = more_shards,
test = "//test/syscalls/linux:tcp_socket_test",
)
@@ -863,6 +877,7 @@ syscall_test(
)
syscall_test(
+ shard_count = more_shards,
test = "//test/syscalls/linux:timerfd_test",
)
@@ -893,13 +908,14 @@ syscall_test(
)
syscall_test(
+ add_hostinet = True,
test = "//test/syscalls/linux:udp_bind_test",
)
syscall_test(
size = "medium",
add_hostinet = True,
- shard_count = 10,
+ shard_count = more_shards,
test = "//test/syscalls/linux:udp_socket_test",
)
@@ -943,7 +959,7 @@ syscall_test(
syscall_test(
size = "medium",
- shard_count = 5,
+ shard_count = more_shards,
test = "//test/syscalls/linux:wait_test",
)
@@ -957,6 +973,7 @@ syscall_test(
)
syscall_test(
+ add_hostinet = True,
test = "//test/syscalls/linux:proc_net_tcp_test",
)
diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD
index 572f39a5d..760456a98 100644
--- a/test/syscalls/linux/BUILD
+++ b/test/syscalls/linux/BUILD
@@ -19,7 +19,6 @@ exports_files(
"socket_ip_udp_loopback_blocking.cc",
"socket_ip_udp_loopback_nonblock.cc",
"socket_ip_unbound.cc",
- "socket_ipv4_tcp_unbound_external_networking_test.cc",
"socket_ipv4_udp_unbound_external_networking_test.cc",
"socket_ipv4_udp_unbound_loopback.cc",
"socket_ipv4_udp_unbound_loopback_nogotsan.cc",
@@ -433,6 +432,9 @@ cc_binary(
testonly = 1,
srcs = ["chown.cc"],
linkstatic = 1,
+ # We require additional UIDs for this test, so don't include the bazel
+ # sandbox as standard.
+ tags = ["no-sandbox"],
deps = [
"//test/util:capability_util",
"//test/util:file_descriptor",
@@ -619,10 +621,7 @@ cc_binary(
cc_binary(
name = "exceptions_test",
testonly = 1,
- srcs = select_arch(
- amd64 = ["exceptions.cc"],
- arm64 = [],
- ),
+ srcs = ["exceptions.cc"],
linkstatic = 1,
deps = [
gtest,
@@ -797,8 +796,8 @@ cc_binary(
deps = [
":socket_test_util",
"//test/util:cleanup",
- "//test/util:epoll_util",
"//test/util:eventfd_util",
+ "//test/util:file_descriptor",
"//test/util:fs_util",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/flags:flag",
@@ -809,6 +808,7 @@ cc_binary(
"//test/util:multiprocess_util",
"//test/util:posix_error",
"//test/util:save_util",
+ "//test/util:signal_util",
"//test/util:temp_path",
"//test/util:test_util",
"//test/util:thread_util",
@@ -1285,6 +1285,7 @@ cc_binary(
"//test/util:mount_util",
"//test/util:multiprocess_util",
"//test/util:posix_error",
+ "//test/util:save_util",
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
@@ -1801,10 +1802,14 @@ cc_binary(
"@com_google_absl//absl/flags:flag",
"@com_google_absl//absl/time",
gtest,
+ "//test/util:file_descriptor",
+ "//test/util:fs_util",
"//test/util:logging",
+ "//test/util:memory_util",
"//test/util:multiprocess_util",
"//test/util:platform_util",
"//test/util:signal_util",
+ "//test/util:temp_path",
"//test/util:test_util",
"//test/util:thread_util",
"//test/util:time_util",
@@ -2101,10 +2106,12 @@ cc_binary(
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
gtest,
+ "//test/util:signal_util",
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
"//test/util:thread_util",
+ "//test/util:timer_util",
],
)
@@ -2124,6 +2131,7 @@ cc_binary(
"//test/util:test_main",
"//test/util:test_util",
"//test/util:thread_util",
+ "//test/util:timer_util",
],
)
@@ -2137,10 +2145,12 @@ cc_binary(
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
gtest,
+ "//test/util:signal_util",
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
"//test/util:thread_util",
+ "//test/util:timer_util",
],
)
@@ -2434,74 +2444,78 @@ cc_library(
"@com_google_absl//absl/memory",
gtest,
"//test/util:posix_error",
+ "//test/util:save_util",
"//test/util:test_util",
],
alwayslink = 1,
)
cc_library(
- name = "socket_ipv4_udp_unbound_netlink_test_cases",
+ name = "socket_ipv6_udp_unbound_test_cases",
testonly = 1,
srcs = [
- "socket_ipv4_udp_unbound_netlink.cc",
+ "socket_ipv6_udp_unbound.cc",
],
hdrs = [
- "socket_ipv4_udp_unbound_netlink.h",
+ "socket_ipv6_udp_unbound.h",
],
deps = [
- ":socket_netlink_route_util",
+ ":ip_socket_test_util",
":socket_test_util",
- "//test/util:capability_util",
- "//test/util:cleanup",
+ "@com_google_absl//absl/memory",
gtest,
+ "//test/util:posix_error",
+ "//test/util:save_util",
+ "//test/util:test_util",
],
alwayslink = 1,
)
cc_library(
- name = "socket_ipv6_udp_unbound_netlink_test_cases",
+ name = "socket_ipv4_udp_unbound_netlink_test_cases",
testonly = 1,
srcs = [
- "socket_ipv6_udp_unbound_netlink.cc",
+ "socket_ipv4_udp_unbound_netlink.cc",
],
hdrs = [
- "socket_ipv6_udp_unbound_netlink.h",
+ "socket_ipv4_udp_unbound_netlink.h",
],
deps = [
":socket_netlink_route_util",
":socket_test_util",
"//test/util:capability_util",
+ "//test/util:cleanup",
gtest,
],
alwayslink = 1,
)
cc_library(
- name = "socket_ipv4_udp_unbound_external_networking_test_cases",
+ name = "socket_ipv6_udp_unbound_netlink_test_cases",
testonly = 1,
srcs = [
- "socket_ipv4_udp_unbound_external_networking.cc",
+ "socket_ipv6_udp_unbound_netlink.cc",
],
hdrs = [
- "socket_ipv4_udp_unbound_external_networking.h",
+ "socket_ipv6_udp_unbound_netlink.h",
],
deps = [
- ":ip_socket_test_util",
+ ":socket_netlink_route_util",
":socket_test_util",
+ "//test/util:capability_util",
gtest,
- "//test/util:test_util",
],
alwayslink = 1,
)
cc_library(
- name = "socket_ipv4_tcp_unbound_external_networking_test_cases",
+ name = "socket_ipv4_udp_unbound_external_networking_test_cases",
testonly = 1,
srcs = [
- "socket_ipv4_tcp_unbound_external_networking.cc",
+ "socket_ipv4_udp_unbound_external_networking.cc",
],
hdrs = [
- "socket_ipv4_tcp_unbound_external_networking.h",
+ "socket_ipv4_udp_unbound_external_networking.h",
],
deps = [
":ip_socket_test_util",
@@ -2707,22 +2721,6 @@ cc_binary(
)
cc_binary(
- name = "socket_ipv4_tcp_unbound_external_networking_test",
- testonly = 1,
- srcs = [
- "socket_ipv4_tcp_unbound_external_networking_test.cc",
- ],
- linkstatic = 1,
- deps = [
- ":ip_socket_test_util",
- ":socket_ipv4_tcp_unbound_external_networking_test_cases",
- ":socket_test_util",
- "//test/util:test_main",
- "//test/util:test_util",
- ],
-)
-
-cc_binary(
name = "socket_bind_to_device_test",
testonly = 1,
srcs = [
@@ -2813,6 +2811,22 @@ cc_binary(
)
cc_binary(
+ name = "socket_ipv6_udp_unbound_loopback_test",
+ testonly = 1,
+ srcs = [
+ "socket_ipv6_udp_unbound_loopback.cc",
+ ],
+ linkstatic = 1,
+ deps = [
+ ":ip_socket_test_util",
+ ":socket_ipv6_udp_unbound_test_cases",
+ ":socket_test_util",
+ "//test/util:test_main",
+ "//test/util:test_util",
+ ],
+)
+
+cc_binary(
name = "socket_ipv4_udp_unbound_loopback_nogotsan_test",
testonly = 1,
srcs = [
@@ -2878,6 +2892,24 @@ cc_binary(
)
cc_binary(
+ name = "socket_ip_unbound_netlink_test",
+ testonly = 1,
+ srcs = [
+ "socket_ip_unbound_netlink.cc",
+ ],
+ linkstatic = 1,
+ deps = [
+ ":ip_socket_test_util",
+ ":socket_netlink_route_util",
+ ":socket_test_util",
+ "//test/util:capability_util",
+ gtest,
+ "//test/util:test_main",
+ "//test/util:test_util",
+ ],
+)
+
+cc_binary(
name = "socket_domain_test",
testonly = 1,
srcs = [
@@ -3441,6 +3473,7 @@ cc_binary(
"@com_google_absl//absl/strings",
gtest,
"//test/util:posix_error",
+ "//test/util:save_util",
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
diff --git a/test/syscalls/linux/chown.cc b/test/syscalls/linux/chown.cc
index 7a28b674d..5530ad18f 100644
--- a/test/syscalls/linux/chown.cc
+++ b/test/syscalls/linux/chown.cc
@@ -75,7 +75,16 @@ TEST_P(ChownParamTest, ChownFileSucceeds) {
if (num_groups > 0) {
std::vector<gid_t> list(num_groups);
EXPECT_THAT(getgroups(list.size(), list.data()), SyscallSucceeds());
- gid = list[0];
+ // Scan the list of groups for a valid gid. Note that if a group is not
+ // defined in this local user namespace, then we will see 65534, and the
+ // group will not chown below as expected. So only change if we find a
+ // valid group in this list.
+ for (const gid_t other_gid : list) {
+ if (other_gid != 65534) {
+ gid = other_gid;
+ break;
+ }
+ }
}
EXPECT_NO_ERRNO(GetParam()(file.path(), geteuid(), gid));
@@ -90,6 +99,7 @@ TEST_P(ChownParamTest, ChownFilePermissionDenied) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));
const auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileMode(0777));
+ EXPECT_THAT(chmod(GetAbsoluteTestTmpdir().c_str(), 0777), 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.
@@ -119,6 +129,7 @@ TEST_P(ChownParamTest, ChownFileSucceedsAsRoot) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_SETUID))));
const std::string filename = NewTempAbsPath();
+ EXPECT_THAT(chmod(GetAbsoluteTestTmpdir().c_str(), 0777), SyscallSucceeds());
absl::Notification fileCreated, fileChowned;
// Change UID only in child thread, or else this parent thread won't be able
diff --git a/test/syscalls/linux/epoll.cc b/test/syscalls/linux/epoll.cc
index 2101e5c9f..8a72ef10a 100644
--- a/test/syscalls/linux/epoll.cc
+++ b/test/syscalls/linux/epoll.cc
@@ -39,11 +39,6 @@ namespace {
constexpr int kFDsPerEpoll = 3;
constexpr uint64_t kMagicConstant = 0x0102030405060708;
-uint64_t ms_elapsed(const struct timespec* begin, const struct timespec* end) {
- return (end->tv_sec - begin->tv_sec) * 1000 +
- (end->tv_nsec - begin->tv_nsec) / 1000000;
-}
-
TEST(EpollTest, AllWritable) {
auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
std::vector<FileDescriptor> eventfds;
@@ -146,7 +141,7 @@ TEST(EpollTest, Timeout_NoRandomSave) {
// Check the lower bound on the timeout. Checking for an upper bound is
// fragile because Linux can overrun the timeout due to scheduling delays.
- EXPECT_GT(ms_elapsed(&begin, &end), kTimeoutMs - 1);
+ EXPECT_GT(ms_elapsed(begin, end), kTimeoutMs - 1);
}
void* writer(void* arg) {
diff --git a/test/syscalls/linux/exceptions.cc b/test/syscalls/linux/exceptions.cc
index 420b9543f..11dc1c651 100644
--- a/test/syscalls/linux/exceptions.cc
+++ b/test/syscalls/linux/exceptions.cc
@@ -23,6 +23,7 @@
namespace gvisor {
namespace testing {
+#if defined(__x86_64__)
// Default value for the x87 FPU control word. See Intel SDM Vol 1, Ch 8.1.5
// "x87 FPU Control Word".
constexpr uint16_t kX87ControlWordDefault = 0x37f;
@@ -93,6 +94,9 @@ void InIOHelper(int width, int value) {
},
::testing::KilledBySignal(SIGSEGV), "");
}
+#elif defined(__aarch64__)
+void inline Halt() { asm("hlt #0\r\n"); }
+#endif
TEST(ExceptionTest, Halt) {
// In order to prevent the regular handler from messing with things (and
@@ -102,9 +106,14 @@ TEST(ExceptionTest, Halt) {
sa.sa_handler = SIG_DFL;
auto const cleanup = ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGSEGV, sa));
+#if defined(__x86_64__)
EXPECT_EXIT(Halt(), ::testing::KilledBySignal(SIGSEGV), "");
+#elif defined(__aarch64__)
+ EXPECT_EXIT(Halt(), ::testing::KilledBySignal(SIGILL), "");
+#endif
}
+#if defined(__x86_64__)
TEST(ExceptionTest, DivideByZero) {
// See above.
struct sigaction sa = {};
@@ -362,6 +371,7 @@ TEST(ExceptionTest, Int3Compact) {
EXPECT_EXIT(Int3Compact(), ::testing::KilledBySignal(SIGTRAP), "");
}
+#endif
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/fcntl.cc b/test/syscalls/linux/fcntl.cc
index 34016d4bd..4b581045b 100644
--- a/test/syscalls/linux/fcntl.cc
+++ b/test/syscalls/linux/fcntl.cc
@@ -14,10 +14,13 @@
#include <fcntl.h>
#include <signal.h>
+#include <sys/epoll.h>
#include <sys/types.h>
#include <syscall.h>
#include <unistd.h>
+#include <atomic>
+#include <deque>
#include <iostream>
#include <list>
#include <string>
@@ -34,25 +37,27 @@
#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/fs_util.h"
#include "test/util/multiprocess_util.h"
#include "test/util/posix_error.h"
#include "test/util/save_util.h"
+#include "test/util/signal_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, "",
+ABSL_FLAG(std::string, child_set_lock_on, "",
"Contains the path to try to set a file lock on.");
-ABSL_FLAG(bool, child_setlock_write, false,
+ABSL_FLAG(bool, child_set_lock_write, false,
"Whether to set a writable lock (otherwise readable)");
ABSL_FLAG(bool, blocking, false,
"Whether to set a blocking lock (otherwise non-blocking).");
ABSL_FLAG(bool, retry_eintr, false,
"Whether to retry in the subprocess on EINTR.");
-ABSL_FLAG(uint64_t, child_setlock_start, 0, "The value of struct flock start");
-ABSL_FLAG(uint64_t, child_setlock_len, 0, "The value of struct flock len");
+ABSL_FLAG(uint64_t, child_set_lock_start, 0, "The value of struct flock start");
+ABSL_FLAG(uint64_t, child_set_lock_len, 0, "The value of struct flock len");
ABSL_FLAG(int32_t, socket_fd, -1,
"A socket to use for communicating more state back "
"to the parent.");
@@ -60,6 +65,11 @@ ABSL_FLAG(int32_t, socket_fd, -1,
namespace gvisor {
namespace testing {
+std::function<void(int, siginfo_t*, void*)> setsig_signal_handle;
+void setsig_signal_handler(int signum, siginfo_t* siginfo, void* ucontext) {
+ setsig_signal_handle(signum, siginfo, ucontext);
+}
+
class FcntlLockTest : public ::testing::Test {
public:
void SetUp() override {
@@ -84,18 +94,93 @@ class FcntlLockTest : public ::testing::Test {
int fds_[2] = {};
};
+struct SignalDelivery {
+ int num;
+ siginfo_t info;
+};
+
+class FcntlSignalTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ int pipe_fds[2];
+ ASSERT_THAT(pipe2(pipe_fds, O_NONBLOCK), SyscallSucceeds());
+ pipe_read_fd_ = pipe_fds[0];
+ pipe_write_fd_ = pipe_fds[1];
+ }
+
+ PosixErrorOr<Cleanup> RegisterSignalHandler(int signum) {
+ struct sigaction handler;
+ handler.sa_sigaction = setsig_signal_handler;
+ setsig_signal_handle = [&](int signum, siginfo_t* siginfo,
+ void* unused_ucontext) {
+ SignalDelivery sig;
+ sig.num = signum;
+ sig.info = *siginfo;
+ signals_received_.push_back(sig);
+ num_signals_received_++;
+ };
+ sigemptyset(&handler.sa_mask);
+ handler.sa_flags = SA_SIGINFO;
+ return ScopedSigaction(signum, handler);
+ }
+
+ void FlushAndCloseFD(int fd) {
+ char buf;
+ int read_bytes;
+ do {
+ read_bytes = read(fd, &buf, 1);
+ } while (read_bytes > 0);
+ // read() can also fail with EWOULDBLOCK since the pipe is open in
+ // non-blocking mode. This is not an error.
+ EXPECT_TRUE(read_bytes == 0 || (read_bytes == -1 && errno == EWOULDBLOCK));
+ EXPECT_THAT(close(fd), SyscallSucceeds());
+ }
+
+ void DupReadFD() {
+ ASSERT_THAT(pipe_read_fd_dup_ = dup(pipe_read_fd_), SyscallSucceeds());
+ max_expected_signals++;
+ }
+
+ void RegisterFD(int fd, int signum) {
+ ASSERT_THAT(fcntl(fd, F_SETOWN, getpid()), SyscallSucceeds());
+ ASSERT_THAT(fcntl(fd, F_SETSIG, signum), SyscallSucceeds());
+ int old_flags;
+ ASSERT_THAT(old_flags = fcntl(fd, F_GETFL), SyscallSucceeds());
+ ASSERT_THAT(fcntl(fd, F_SETFL, old_flags | O_ASYNC), SyscallSucceeds());
+ }
+
+ void GenerateIOEvent() {
+ ASSERT_THAT(write(pipe_write_fd_, "test", 4), SyscallSucceedsWithValue(4));
+ }
+
+ void WaitForSignalDelivery(absl::Duration timeout) {
+ absl::Time wait_start = absl::Now();
+ while (num_signals_received_ < max_expected_signals &&
+ absl::Now() - wait_start < timeout) {
+ absl::SleepFor(absl::Milliseconds(10));
+ }
+ }
+
+ int pipe_read_fd_ = -1;
+ int pipe_read_fd_dup_ = -1;
+ int pipe_write_fd_ = -1;
+ int max_expected_signals = 1;
+ std::deque<SignalDelivery> signals_received_;
+ std::atomic<int> num_signals_received_ = 0;
+};
+
namespace {
PosixErrorOr<Cleanup> SubprocessLock(std::string const& path, bool for_write,
bool blocking, bool retry_eintr, int fd,
off_t start, off_t length, pid_t* child) {
std::vector<std::string> args = {
- "/proc/self/exe", "--child_setlock_on", path,
- "--child_setlock_start", absl::StrCat(start), "--child_setlock_len",
- absl::StrCat(length), "--socket_fd", absl::StrCat(fd)};
+ "/proc/self/exe", "--child_set_lock_on", path,
+ "--child_set_lock_start", absl::StrCat(start), "--child_set_lock_len",
+ absl::StrCat(length), "--socket_fd", absl::StrCat(fd)};
if (for_write) {
- args.push_back("--child_setlock_write");
+ args.push_back("--child_set_lock_write");
}
if (blocking) {
@@ -965,7 +1050,6 @@ TEST(FcntlTest, GetOwnNone) {
// into F_{GET,SET}OWN_EX.
EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN),
SyscallSucceedsWithValue(0));
- MaybeSave();
}
TEST(FcntlTest, GetOwnExNone) {
@@ -1009,7 +1093,6 @@ TEST(FcntlTest, SetOwnPid) {
EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN),
SyscallSucceedsWithValue(pid));
- MaybeSave();
}
TEST(FcntlTest, SetOwnPgrp) {
@@ -1030,7 +1113,6 @@ TEST(FcntlTest, SetOwnPgrp) {
SyscallSucceedsWithValue(0));
EXPECT_EQ(got_owner.type, F_OWNER_PGRP);
EXPECT_EQ(got_owner.pid, pgid);
- MaybeSave();
}
TEST(FcntlTest, SetOwnUnset) {
@@ -1058,7 +1140,6 @@ TEST(FcntlTest, SetOwnUnset) {
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
@@ -1130,7 +1211,6 @@ TEST(FcntlTest, SetOwnExTid) {
EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN),
SyscallSucceedsWithValue(owner.pid));
- MaybeSave();
}
TEST(FcntlTest, SetOwnExPid) {
@@ -1146,7 +1226,6 @@ TEST(FcntlTest, SetOwnExPid) {
EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN),
SyscallSucceedsWithValue(owner.pid));
- MaybeSave();
}
TEST(FcntlTest, SetOwnExPgrp) {
@@ -1168,7 +1247,6 @@ TEST(FcntlTest, SetOwnExPgrp) {
SyscallSucceedsWithValue(0));
EXPECT_EQ(got_owner.type, set_owner.type);
EXPECT_EQ(got_owner.pid, set_owner.pid);
- MaybeSave();
}
TEST(FcntlTest, SetOwnExUnset) {
@@ -1201,7 +1279,6 @@ TEST(FcntlTest, SetOwnExUnset) {
EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETOWN),
SyscallSucceedsWithValue(0));
- MaybeSave();
}
TEST(FcntlTest, GetOwnExTid) {
@@ -1258,9 +1335,269 @@ TEST(FcntlTest, GetOwnExPgrp) {
EXPECT_EQ(got_owner.pid, set_owner.pid);
}
+TEST(FcntlTest, SetSig) {
+ FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
+
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGUSR1),
+ SyscallSucceedsWithValue(0));
+ EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG),
+ SyscallSucceedsWithValue(SIGUSR1));
+}
+
+TEST(FcntlTest, SetSigDefaultsToZero) {
+ FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
+
+ // Defaults to returning the zero value, indicating default behavior (SIGIO).
+ EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG),
+ SyscallSucceedsWithValue(0));
+}
+
+TEST(FcntlTest, SetSigToDefault) {
+ FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
+
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGIO),
+ SyscallSucceedsWithValue(0));
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG),
+ SyscallSucceedsWithValue(SIGIO));
+
+ // Can be reset to the default behavior.
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, 0),
+ SyscallSucceedsWithValue(0));
+ EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG),
+ SyscallSucceedsWithValue(0));
+}
+
+TEST(FcntlTest, SetSigInvalid) {
+ FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
+
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGRTMAX + 1),
+ SyscallFailsWithErrno(EINVAL));
+ EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG),
+ SyscallSucceedsWithValue(0));
+}
+
+TEST(FcntlTest, SetSigInvalidDoesNotResetPreviousChoice) {
+ FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
+
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGUSR1),
+ SyscallSucceedsWithValue(0));
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGRTMAX + 1),
+ SyscallFailsWithErrno(EINVAL));
+ EXPECT_THAT(syscall(__NR_fcntl, s.get(), F_GETSIG),
+ SyscallSucceedsWithValue(SIGUSR1));
+}
+
+TEST_F(FcntlSignalTest, SetSigDefault) {
+ const auto signal_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGIO));
+ RegisterFD(pipe_read_fd_, 0); // Zero = default behavior
+ GenerateIOEvent();
+ WaitForSignalDelivery(absl::Seconds(1));
+ ASSERT_EQ(num_signals_received_, 1);
+ SignalDelivery sig = signals_received_.front();
+ signals_received_.pop_front();
+ EXPECT_EQ(sig.num, SIGIO);
+ EXPECT_EQ(sig.info.si_signo, SIGIO);
+ // siginfo contents is undefined in this case.
+}
+
+TEST_F(FcntlSignalTest, SetSigCustom) {
+ const auto signal_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1));
+ RegisterFD(pipe_read_fd_, SIGUSR1);
+ GenerateIOEvent();
+ WaitForSignalDelivery(absl::Seconds(1));
+ ASSERT_EQ(num_signals_received_, 1);
+ SignalDelivery sig = signals_received_.front();
+ signals_received_.pop_front();
+ EXPECT_EQ(sig.num, SIGUSR1);
+ EXPECT_EQ(sig.info.si_signo, SIGUSR1);
+ EXPECT_EQ(sig.info.si_fd, pipe_read_fd_);
+ EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM);
+}
+
+TEST_F(FcntlSignalTest, SetSigUnregisterStillGetsSigio) {
+ const auto sigio_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGIO));
+ const auto sigusr1_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1));
+ RegisterFD(pipe_read_fd_, SIGUSR1);
+ RegisterFD(pipe_read_fd_, 0);
+ GenerateIOEvent();
+ WaitForSignalDelivery(absl::Seconds(1));
+ ASSERT_EQ(num_signals_received_, 1);
+ SignalDelivery sig = signals_received_.front();
+ signals_received_.pop_front();
+ EXPECT_EQ(sig.num, SIGIO);
+ // siginfo contents is undefined in this case.
+}
+
+TEST_F(FcntlSignalTest, SetSigWithSigioStillGetsSiginfo) {
+ const auto signal_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGIO));
+ RegisterFD(pipe_read_fd_, SIGIO);
+ GenerateIOEvent();
+ WaitForSignalDelivery(absl::Seconds(1));
+ ASSERT_EQ(num_signals_received_, 1);
+ SignalDelivery sig = signals_received_.front();
+ EXPECT_EQ(sig.num, SIGIO);
+ EXPECT_EQ(sig.info.si_signo, SIGIO);
+ EXPECT_EQ(sig.info.si_fd, pipe_read_fd_);
+ EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM);
+}
+
+TEST_F(FcntlSignalTest, SetSigDupThenCloseOld) {
+ const auto sigusr1_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1));
+ RegisterFD(pipe_read_fd_, SIGUSR1);
+ DupReadFD();
+ FlushAndCloseFD(pipe_read_fd_);
+ GenerateIOEvent();
+ WaitForSignalDelivery(absl::Seconds(1));
+ ASSERT_EQ(num_signals_received_, 1);
+ SignalDelivery sig = signals_received_.front();
+ // We get a signal with the **old** FD (even though it is closed).
+ EXPECT_EQ(sig.num, SIGUSR1);
+ EXPECT_EQ(sig.info.si_signo, SIGUSR1);
+ EXPECT_EQ(sig.info.si_fd, pipe_read_fd_);
+ EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM);
+}
+
+TEST_F(FcntlSignalTest, SetSigDupThenCloseNew) {
+ const auto sigusr1_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1));
+ RegisterFD(pipe_read_fd_, SIGUSR1);
+ DupReadFD();
+ FlushAndCloseFD(pipe_read_fd_dup_);
+ GenerateIOEvent();
+ WaitForSignalDelivery(absl::Seconds(1));
+ ASSERT_EQ(num_signals_received_, 1);
+ SignalDelivery sig = signals_received_.front();
+ // We get a signal with the old FD.
+ EXPECT_EQ(sig.num, SIGUSR1);
+ EXPECT_EQ(sig.info.si_signo, SIGUSR1);
+ EXPECT_EQ(sig.info.si_fd, pipe_read_fd_);
+ EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM);
+}
+
+TEST_F(FcntlSignalTest, SetSigDupOldRegistered) {
+ const auto sigusr1_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1));
+ RegisterFD(pipe_read_fd_, SIGUSR1);
+ DupReadFD();
+ GenerateIOEvent();
+ WaitForSignalDelivery(absl::Seconds(1));
+ ASSERT_EQ(num_signals_received_, 1);
+ SignalDelivery sig = signals_received_.front();
+ // We get a signal with the old FD.
+ EXPECT_EQ(sig.num, SIGUSR1);
+ EXPECT_EQ(sig.info.si_signo, SIGUSR1);
+ EXPECT_EQ(sig.info.si_fd, pipe_read_fd_);
+ EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM);
+}
+
+TEST_F(FcntlSignalTest, SetSigDupNewRegistered) {
+ const auto sigusr2_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR2));
+ DupReadFD();
+ RegisterFD(pipe_read_fd_dup_, SIGUSR2);
+ GenerateIOEvent();
+ WaitForSignalDelivery(absl::Seconds(1));
+ ASSERT_EQ(num_signals_received_, 1);
+ SignalDelivery sig = signals_received_.front();
+ // We get a signal with the new FD.
+ EXPECT_EQ(sig.num, SIGUSR2);
+ EXPECT_EQ(sig.info.si_signo, SIGUSR2);
+ EXPECT_EQ(sig.info.si_fd, pipe_read_fd_dup_);
+ EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM);
+}
+
+TEST_F(FcntlSignalTest, SetSigDupBothRegistered) {
+ const auto sigusr1_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1));
+ const auto sigusr2_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR2));
+ RegisterFD(pipe_read_fd_, SIGUSR1);
+ DupReadFD();
+ RegisterFD(pipe_read_fd_dup_, SIGUSR2);
+ GenerateIOEvent();
+ WaitForSignalDelivery(absl::Seconds(1));
+ ASSERT_EQ(num_signals_received_, 1);
+ SignalDelivery sig = signals_received_.front();
+ // We get a signal with the **new** signal number, but the **old** FD.
+ EXPECT_EQ(sig.num, SIGUSR2);
+ EXPECT_EQ(sig.info.si_signo, SIGUSR2);
+ EXPECT_EQ(sig.info.si_fd, pipe_read_fd_);
+ EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM);
+}
+
+TEST_F(FcntlSignalTest, SetSigDupBothRegisteredAfterDup) {
+ const auto sigusr1_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1));
+ const auto sigusr2_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR2));
+ DupReadFD();
+ RegisterFD(pipe_read_fd_, SIGUSR1);
+ RegisterFD(pipe_read_fd_dup_, SIGUSR2);
+ GenerateIOEvent();
+ WaitForSignalDelivery(absl::Seconds(1));
+ ASSERT_EQ(num_signals_received_, 1);
+ SignalDelivery sig = signals_received_.front();
+ // We get a signal with the **new** signal number, but the **old** FD.
+ EXPECT_EQ(sig.num, SIGUSR2);
+ EXPECT_EQ(sig.info.si_signo, SIGUSR2);
+ EXPECT_EQ(sig.info.si_fd, pipe_read_fd_);
+ EXPECT_EQ(sig.info.si_band, EPOLLIN | EPOLLRDNORM);
+}
+
+TEST_F(FcntlSignalTest, SetSigDupUnregisterOld) {
+ const auto sigio_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGIO));
+ const auto sigusr1_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1));
+ const auto sigusr2_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR2));
+ RegisterFD(pipe_read_fd_, SIGUSR1);
+ DupReadFD();
+ RegisterFD(pipe_read_fd_dup_, SIGUSR2);
+ RegisterFD(pipe_read_fd_, 0); // Should go back to SIGIO behavior.
+ GenerateIOEvent();
+ WaitForSignalDelivery(absl::Seconds(1));
+ ASSERT_EQ(num_signals_received_, 1);
+ SignalDelivery sig = signals_received_.front();
+ // We get a signal with SIGIO.
+ EXPECT_EQ(sig.num, SIGIO);
+ // siginfo is undefined in this case.
+}
+
+TEST_F(FcntlSignalTest, SetSigDupUnregisterNew) {
+ const auto sigio_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGIO));
+ const auto sigusr1_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR1));
+ const auto sigusr2_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGUSR2));
+ RegisterFD(pipe_read_fd_, SIGUSR1);
+ DupReadFD();
+ RegisterFD(pipe_read_fd_dup_, SIGUSR2);
+ RegisterFD(pipe_read_fd_dup_, 0); // Should go back to SIGIO behavior.
+ GenerateIOEvent();
+ WaitForSignalDelivery(absl::Seconds(1));
+ ASSERT_EQ(num_signals_received_, 1);
+ SignalDelivery sig = signals_received_.front();
+ // We get a signal with SIGIO.
+ EXPECT_EQ(sig.num, SIGIO);
+ // siginfo is undefined in this case.
+}
+
// Make sure that making multiple concurrent changes to async signal generation
// does not cause any race issues.
-TEST(FcntlTest, SetFlSetOwnDoNotRace) {
+TEST(FcntlTest, SetFlSetOwnSetSigDoNotRace) {
FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
Socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
@@ -1268,32 +1605,40 @@ TEST(FcntlTest, SetFlSetOwnDoNotRace) {
EXPECT_THAT(pid = getpid(), SyscallSucceeds());
constexpr absl::Duration runtime = absl::Milliseconds(300);
- auto setAsync = [&s, &runtime] {
+ auto set_async = [&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] {
+ auto reset_async = [&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] {
+ auto set_own = [&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();
}
};
+ auto set_sig = [&s, &runtime] {
+ for (auto start = absl::Now(); absl::Now() - start < runtime;) {
+ ASSERT_THAT(syscall(__NR_fcntl, s.get(), F_SETSIG, SIGUSR1),
+ 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);
+ threads.emplace_back(set_async);
+ threads.emplace_back(reset_async);
+ threads.emplace_back(set_own);
+ threads.emplace_back(set_sig);
}
}
@@ -1302,57 +1647,60 @@ TEST(FcntlTest, SetFlSetOwnDoNotRace) {
} // namespace testing
} // namespace gvisor
-int main(int argc, char** argv) {
- gvisor::testing::TestInit(&argc, &argv);
-
- const std::string setlock_on = absl::GetFlag(FLAGS_child_setlock_on);
- if (!setlock_on.empty()) {
- int socket_fd = absl::GetFlag(FLAGS_socket_fd);
- int fd = open(setlock_on.c_str(), O_RDWR, 0666);
- if (fd == -1 && errno != 0) {
- int err = errno;
- std::cerr << "CHILD open " << setlock_on << " failed " << err
- << std::endl;
- exit(err);
- }
+int set_lock() {
+ const std::string set_lock_on = absl::GetFlag(FLAGS_child_set_lock_on);
+ int socket_fd = absl::GetFlag(FLAGS_socket_fd);
+ int fd = open(set_lock_on.c_str(), O_RDWR, 0666);
+ if (fd == -1 && errno != 0) {
+ int err = errno;
+ std::cerr << "CHILD open " << set_lock_on << " failed: " << err
+ << std::endl;
+ return err;
+ }
- struct flock fl;
- if (absl::GetFlag(FLAGS_child_setlock_write)) {
- fl.l_type = F_WRLCK;
- } else {
- fl.l_type = F_RDLCK;
- }
- fl.l_whence = SEEK_SET;
- fl.l_start = absl::GetFlag(FLAGS_child_setlock_start);
- fl.l_len = absl::GetFlag(FLAGS_child_setlock_len);
+ struct flock fl;
+ if (absl::GetFlag(FLAGS_child_set_lock_write)) {
+ fl.l_type = F_WRLCK;
+ } else {
+ fl.l_type = F_RDLCK;
+ }
+ fl.l_whence = SEEK_SET;
+ fl.l_start = absl::GetFlag(FLAGS_child_set_lock_start);
+ fl.l_len = absl::GetFlag(FLAGS_child_set_lock_len);
+
+ // Test the fcntl.
+ int err = 0;
+ int ret = 0;
+
+ gvisor::testing::MonotonicTimer timer;
+ timer.Start();
+ do {
+ ret = fcntl(fd, absl::GetFlag(FLAGS_blocking) ? F_SETLKW : F_SETLK, &fl);
+ } while (absl::GetFlag(FLAGS_retry_eintr) && ret == -1 && errno == EINTR);
+ auto usec = absl::ToInt64Microseconds(timer.Duration());
+
+ if (ret == -1 && errno != 0) {
+ err = errno;
+ std::cerr << "CHILD lock " << set_lock_on << " failed " << err << std::endl;
+ }
- // Test the fcntl.
- int err = 0;
- int ret = 0;
+ // If there is a socket fd let's send back the time in microseconds it took
+ // to execute this syscall.
+ if (socket_fd != -1) {
+ gvisor::testing::WriteFd(socket_fd, reinterpret_cast<void*>(&usec),
+ sizeof(usec));
+ close(socket_fd);
+ }
- gvisor::testing::MonotonicTimer timer;
- timer.Start();
- do {
- ret = fcntl(fd, absl::GetFlag(FLAGS_blocking) ? F_SETLKW : F_SETLK, &fl);
- } while (absl::GetFlag(FLAGS_retry_eintr) && ret == -1 && errno == EINTR);
- auto usec = absl::ToInt64Microseconds(timer.Duration());
-
- if (ret == -1 && errno != 0) {
- err = errno;
- std::cerr << "CHILD lock " << setlock_on << " failed " << err
- << std::endl;
- }
+ close(fd);
+ return err;
+}
- // If there is a socket fd let's send back the time in microseconds it took
- // to execute this syscall.
- if (socket_fd != -1) {
- gvisor::testing::WriteFd(socket_fd, reinterpret_cast<void*>(&usec),
- sizeof(usec));
- close(socket_fd);
- }
+int main(int argc, char** argv) {
+ gvisor::testing::TestInit(&argc, &argv);
- close(fd);
- exit(err);
+ if (!absl::GetFlag(FLAGS_child_set_lock_on).empty()) {
+ exit(set_lock());
}
return gvisor::testing::RunAllTests();
diff --git a/test/syscalls/linux/kill.cc b/test/syscalls/linux/kill.cc
index db29bd59c..5d1735853 100644
--- a/test/syscalls/linux/kill.cc
+++ b/test/syscalls/linux/kill.cc
@@ -58,6 +58,12 @@ void SigHandler(int sig, siginfo_t* info, void* context) { _exit(0); }
// If pid equals -1, then sig is sent to every process for which the calling
// process has permission to send signals, except for process 1 (init).
TEST(KillTest, CanKillAllPIDs) {
+ // If we're not running inside the sandbox, then we skip this test
+ // as our namespace may contain may more processes that cannot tolerate
+ // the signal below. We also cannot reliably create a new pid namespace
+ // for ourselves and test the same functionality.
+ SKIP_IF(!IsRunningOnGvisor());
+
int pipe_fds[2];
ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds());
FileDescriptor read_fd(pipe_fds[0]);
diff --git a/test/syscalls/linux/mknod.cc b/test/syscalls/linux/mknod.cc
index b96907b30..1635c6d0c 100644
--- a/test/syscalls/linux/mknod.cc
+++ b/test/syscalls/linux/mknod.cc
@@ -125,6 +125,16 @@ TEST(MknodTest, Socket) {
ASSERT_THAT(unlink(filename.c_str()), SyscallSucceeds());
}
+PosixErrorOr<FileDescriptor> OpenRetryEINTR(std::string const& path, int flags,
+ mode_t mode = 0) {
+ while (true) {
+ auto maybe_fd = Open(path, flags, mode);
+ if (maybe_fd.ok() || maybe_fd.error().errno_value() != EINTR) {
+ return maybe_fd;
+ }
+ }
+}
+
TEST(MknodTest, Fifo) {
const std::string fifo = NewTempAbsPath();
ASSERT_THAT(mknod(fifo.c_str(), S_IFIFO | S_IRUSR | S_IWUSR, 0),
@@ -139,14 +149,16 @@ TEST(MknodTest, Fifo) {
// Read-end of the pipe.
ScopedThread t([&fifo, &buf, &msg]() {
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_RDONLY));
+ FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(OpenRetryEINTR(fifo.c_str(), O_RDONLY));
EXPECT_THAT(ReadFd(fd.get(), buf.data(), buf.size()),
SyscallSucceedsWithValue(msg.length()));
EXPECT_EQ(msg, std::string(buf.data()));
});
// Write-end of the pipe.
- FileDescriptor wfd = ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_WRONLY));
+ FileDescriptor wfd =
+ ASSERT_NO_ERRNO_AND_VALUE(OpenRetryEINTR(fifo.c_str(), O_WRONLY));
EXPECT_THAT(WriteFd(wfd.get(), msg.c_str(), msg.length()),
SyscallSucceedsWithValue(msg.length()));
}
@@ -164,15 +176,16 @@ TEST(MknodTest, FifoOtrunc) {
std::vector<char> buf(512);
// Read-end of the pipe.
ScopedThread t([&fifo, &buf, &msg]() {
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_RDONLY));
+ FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(OpenRetryEINTR(fifo.c_str(), O_RDONLY));
EXPECT_THAT(ReadFd(fd.get(), buf.data(), buf.size()),
SyscallSucceedsWithValue(msg.length()));
EXPECT_EQ(msg, std::string(buf.data()));
});
// Write-end of the pipe.
- FileDescriptor wfd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_WRONLY | O_TRUNC));
+ FileDescriptor wfd = ASSERT_NO_ERRNO_AND_VALUE(
+ OpenRetryEINTR(fifo.c_str(), O_WRONLY | O_TRUNC));
EXPECT_THAT(WriteFd(wfd.get(), msg.c_str(), msg.length()),
SyscallSucceedsWithValue(msg.length()));
}
@@ -192,14 +205,15 @@ TEST(MknodTest, FifoTruncNoOp) {
std::vector<char> buf(512);
// Read-end of the pipe.
ScopedThread t([&fifo, &buf, &msg]() {
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_RDONLY));
+ FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(OpenRetryEINTR(fifo.c_str(), O_RDONLY));
EXPECT_THAT(ReadFd(fd.get(), buf.data(), buf.size()),
SyscallSucceedsWithValue(msg.length()));
EXPECT_EQ(msg, std::string(buf.data()));
});
- FileDescriptor wfd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_WRONLY | O_TRUNC));
+ FileDescriptor wfd = ASSERT_NO_ERRNO_AND_VALUE(
+ OpenRetryEINTR(fifo.c_str(), O_WRONLY | O_TRUNC));
EXPECT_THAT(ftruncate(wfd.get(), 0), SyscallFailsWithErrno(EINVAL));
EXPECT_THAT(WriteFd(wfd.get(), msg.c_str(), msg.length()),
SyscallSucceedsWithValue(msg.length()));
diff --git a/test/syscalls/linux/mmap.cc b/test/syscalls/linux/mmap.cc
index e52c9cbcb..83546830d 100644
--- a/test/syscalls/linux/mmap.cc
+++ b/test/syscalls/linux/mmap.cc
@@ -592,6 +592,12 @@ TEST_F(MMapTest, ProtExec) {
memcpy(reinterpret_cast<void*>(addr), machine_code, sizeof(machine_code));
+#if defined(__aarch64__)
+ // We use this as a memory barrier for Arm64.
+ ASSERT_THAT(Protect(addr, kPageSize, PROT_READ | PROT_EXEC),
+ SyscallSucceeds());
+#endif
+
func = reinterpret_cast<uint32_t (*)(void)>(addr);
EXPECT_EQ(42, func());
diff --git a/test/syscalls/linux/mount.cc b/test/syscalls/linux/mount.cc
index 3aab25b23..d65b7d031 100644
--- a/test/syscalls/linux/mount.cc
+++ b/test/syscalls/linux/mount.cc
@@ -34,6 +34,7 @@
#include "test/util/mount_util.h"
#include "test/util/multiprocess_util.h"
#include "test/util/posix_error.h"
+#include "test/util/save_util.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
#include "test/util/thread_util.h"
@@ -131,7 +132,9 @@ TEST(MountTest, UmountDetach) {
ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "tmpfs", 0, "mode=0700",
/* umountflags= */ MNT_DETACH));
const struct stat after = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
- EXPECT_NE(before.st_ino, after.st_ino);
+ EXPECT_FALSE(before.st_dev == after.st_dev && before.st_ino == after.st_ino)
+ << "mount point has device number " << before.st_dev
+ << " and inode number " << before.st_ino << " before and after mount";
// Create files in the new mount.
constexpr char kContents[] = "no no no";
@@ -147,12 +150,14 @@ TEST(MountTest, UmountDetach) {
// Unmount the tmpfs.
mount.Release()();
- // Only check for inode number equality if the directory is not in overlayfs.
- // If xino option is not enabled and if all overlayfs layers do not belong to
- // the same filesystem then "the value of st_ino for directory objects may not
- // be persistent and could change even while the overlay filesystem is
- // mounted." -- Documentation/filesystems/overlayfs.txt
- if (!ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(dir.path()))) {
+ // Inode numbers for gofer-accessed files may change across save/restore.
+ //
+ // For overlayfs, if xino option is not enabled and if all overlayfs layers do
+ // not belong to the same filesystem then "the value of st_ino for directory
+ // objects may not be persistent and could change even while the overlay
+ // filesystem is mounted." -- Documentation/filesystems/overlayfs.txt
+ if (!IsRunningWithSaveRestore() &&
+ !ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(dir.path()))) {
const struct stat after2 = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
EXPECT_EQ(before.st_ino, after2.st_ino);
}
@@ -214,18 +219,23 @@ TEST(MountTest, MountTmpfs) {
const struct stat s = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
EXPECT_EQ(s.st_mode, S_IFDIR | 0700);
- EXPECT_NE(s.st_ino, before.st_ino);
+ EXPECT_FALSE(before.st_dev == s.st_dev && before.st_ino == s.st_ino)
+ << "mount point has device number " << before.st_dev
+ << " and inode number " << before.st_ino << " before and after mount";
EXPECT_NO_ERRNO(Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777));
}
// Now that dir is unmounted again, we should have the old inode back.
- // Only check for inode number equality if the directory is not in overlayfs.
- // If xino option is not enabled and if all overlayfs layers do not belong to
- // the same filesystem then "the value of st_ino for directory objects may not
- // be persistent and could change even while the overlay filesystem is
- // mounted." -- Documentation/filesystems/overlayfs.txt
- if (!ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(dir.path()))) {
+ //
+ // Inode numbers for gofer-accessed files may change across save/restore.
+ //
+ // For overlayfs, if xino option is not enabled and if all overlayfs layers do
+ // not belong to the same filesystem then "the value of st_ino for directory
+ // objects may not be persistent and could change even while the overlay
+ // filesystem is mounted." -- Documentation/filesystems/overlayfs.txt
+ if (!IsRunningWithSaveRestore() &&
+ !ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(dir.path()))) {
const struct stat after = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
EXPECT_EQ(before.st_ino, after.st_ino);
}
diff --git a/test/syscalls/linux/open_create.cc b/test/syscalls/linux/open_create.cc
index 78c36f98f..9d63782fb 100644
--- a/test/syscalls/linux/open_create.cc
+++ b/test/syscalls/linux/open_create.cc
@@ -112,14 +112,6 @@ TEST(CreateTest, CreatFileWithOTruncAndReadOnly) {
ASSERT_THAT(close(dirfd), SyscallSucceeds());
}
-TEST(CreateTest, CreateFailsOnUnpermittedDir) {
- // Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to
- // always override directory permissions.
- ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
- ASSERT_THAT(open("/foo", O_CREAT | O_RDWR, 0644),
- SyscallFailsWithErrno(EACCES));
-}
-
TEST(CreateTest, CreateFailsOnDirWithoutWritePerms) {
// Make sure we don't have CAP_DAC_OVERRIDE, since that allows the user to
// always override directory permissions.
diff --git a/test/syscalls/linux/packet_socket_raw.cc b/test/syscalls/linux/packet_socket_raw.cc
index b558e3a01..a7c46adbf 100644
--- a/test/syscalls/linux/packet_socket_raw.cc
+++ b/test/syscalls/linux/packet_socket_raw.cc
@@ -664,6 +664,17 @@ TEST_P(RawPacketTest, SetAndGetSocketLinger) {
EXPECT_EQ(0, memcmp(&sl, &got_linger, length));
}
+TEST_P(RawPacketTest, GetSocketAcceptConn) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ int got = -1;
+ socklen_t length = sizeof(got);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_ACCEPTCONN, &got, &length),
+ SyscallSucceedsWithValue(0));
+
+ ASSERT_EQ(length, sizeof(got));
+ EXPECT_EQ(got, 0);
+}
INSTANTIATE_TEST_SUITE_P(AllInetTests, RawPacketTest,
::testing::Values(ETH_P_IP, ETH_P_ALL));
diff --git a/test/syscalls/linux/partial_bad_buffer.cc b/test/syscalls/linux/partial_bad_buffer.cc
index df7129acc..13afa0eaf 100644
--- a/test/syscalls/linux/partial_bad_buffer.cc
+++ b/test/syscalls/linux/partial_bad_buffer.cc
@@ -320,7 +320,10 @@ PosixErrorOr<sockaddr_storage> InetLoopbackAddr(int family) {
// EFAULT. It also verifies that passing a buffer which is made up of 2
// pages one valid and one guard page succeeds as long as the write is
// for exactly the size of 1 page.
-TEST_F(PartialBadBufferTest, SendMsgTCP) {
+TEST_F(PartialBadBufferTest, SendMsgTCP_NoRandomSave) {
+ // FIXME(b/171436815): Netstack save/restore is broken.
+ const DisableSave ds;
+
auto listen_socket =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc
index e8fcc4439..575be014c 100644
--- a/test/syscalls/linux/proc.cc
+++ b/test/syscalls/linux/proc.cc
@@ -17,6 +17,7 @@
#include <fcntl.h>
#include <limits.h>
#include <linux/magic.h>
+#include <linux/sem.h>
#include <sched.h>
#include <signal.h>
#include <stddef.h>
@@ -26,6 +27,7 @@
#include <string.h>
#include <sys/mman.h>
#include <sys/prctl.h>
+#include <sys/ptrace.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <sys/utsname.h>
@@ -512,6 +514,414 @@ TEST(ProcSelfAuxv, EntryValues) {
EXPECT_EQ(i, proc_auxv.size());
}
+// Just open and read a part of /proc/self/mem, check that we can read an item.
+TEST(ProcPidMem, Read) {
+ auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY));
+ char input[] = "hello-world";
+ char output[sizeof(input)];
+ ASSERT_THAT(pread(memfd.get(), output, sizeof(output),
+ reinterpret_cast<off_t>(input)),
+ SyscallSucceedsWithValue(sizeof(input)));
+ ASSERT_STREQ(input, output);
+}
+
+// Perform read on an unmapped region.
+TEST(ProcPidMem, Unmapped) {
+ // Strategy: map then unmap, so we have a guaranteed unmapped region
+ auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY));
+ Mapping mapping = ASSERT_NO_ERRNO_AND_VALUE(
+ MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
+ // Fill it with things
+ memset(mapping.ptr(), 'x', mapping.len());
+ char expected = 'x', output;
+ ASSERT_THAT(pread(memfd.get(), &output, sizeof(output),
+ reinterpret_cast<off_t>(mapping.ptr())),
+ SyscallSucceedsWithValue(sizeof(output)));
+ ASSERT_EQ(expected, output);
+
+ // Unmap region again
+ ASSERT_THAT(munmap(mapping.ptr(), mapping.len()), SyscallSucceeds());
+
+ // Now we want EIO error
+ ASSERT_THAT(pread(memfd.get(), &output, sizeof(output),
+ reinterpret_cast<off_t>(mapping.ptr())),
+ SyscallFailsWithErrno(EIO));
+}
+
+// Perform read repeatedly to verify offset change.
+TEST(ProcPidMem, RepeatedRead) {
+ auto const num_reads = 3;
+ char expected[] = "01234567890abcdefghijkl";
+ char output[sizeof(expected) / num_reads];
+
+ auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY));
+ ASSERT_THAT(lseek(memfd.get(), reinterpret_cast<off_t>(&expected), SEEK_SET),
+ SyscallSucceedsWithValue(reinterpret_cast<off_t>(&expected)));
+ for (auto i = 0; i < num_reads; i++) {
+ ASSERT_THAT(read(memfd.get(), &output, sizeof(output)),
+ SyscallSucceedsWithValue(sizeof(output)));
+ ASSERT_EQ(strncmp(&expected[i * sizeof(output)], output, sizeof(output)),
+ 0);
+ }
+}
+
+// Perform seek operations repeatedly.
+TEST(ProcPidMem, RepeatedSeek) {
+ auto const num_reads = 3;
+ char expected[] = "01234567890abcdefghijkl";
+ char output[sizeof(expected) / num_reads];
+
+ auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY));
+ ASSERT_THAT(lseek(memfd.get(), reinterpret_cast<off_t>(&expected), SEEK_SET),
+ SyscallSucceedsWithValue(reinterpret_cast<off_t>(&expected)));
+ // Read from start
+ ASSERT_THAT(read(memfd.get(), &output, sizeof(output)),
+ SyscallSucceedsWithValue(sizeof(output)));
+ ASSERT_EQ(strncmp(&expected[0 * sizeof(output)], output, sizeof(output)), 0);
+ // Skip ahead one read
+ ASSERT_THAT(lseek(memfd.get(), sizeof(output), SEEK_CUR),
+ SyscallSucceedsWithValue(reinterpret_cast<off_t>(&expected) +
+ sizeof(output) * 2));
+ // Do read again
+ ASSERT_THAT(read(memfd.get(), &output, sizeof(output)),
+ SyscallSucceedsWithValue(sizeof(output)));
+ ASSERT_EQ(strncmp(&expected[2 * sizeof(output)], output, sizeof(output)), 0);
+ // Skip back three reads
+ ASSERT_THAT(lseek(memfd.get(), -3 * sizeof(output), SEEK_CUR),
+ SyscallSucceedsWithValue(reinterpret_cast<off_t>(&expected)));
+ // Do read again
+ ASSERT_THAT(read(memfd.get(), &output, sizeof(output)),
+ SyscallSucceedsWithValue(sizeof(output)));
+ ASSERT_EQ(strncmp(&expected[0 * sizeof(output)], output, sizeof(output)), 0);
+ // Check that SEEK_END does not work
+ ASSERT_THAT(lseek(memfd.get(), 0, SEEK_END), SyscallFailsWithErrno(EINVAL));
+}
+
+// Perform read past an allocated memory region.
+TEST(ProcPidMem, PartialRead) {
+ // Strategy: map large region, then do unmap and remap smaller region
+ auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY));
+
+ Mapping mapping = ASSERT_NO_ERRNO_AND_VALUE(
+ MmapAnon(2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
+ ASSERT_THAT(munmap(mapping.ptr(), mapping.len()), SyscallSucceeds());
+ Mapping smaller_mapping = ASSERT_NO_ERRNO_AND_VALUE(
+ Mmap(mapping.ptr(), kPageSize, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
+
+ // Fill it with things
+ memset(smaller_mapping.ptr(), 'x', smaller_mapping.len());
+
+ // Now we want no error
+ char expected[] = {'x'};
+ std::unique_ptr<char[]> output(new char[kPageSize]);
+ off_t read_offset =
+ reinterpret_cast<off_t>(smaller_mapping.ptr()) + kPageSize - 1;
+ ASSERT_THAT(
+ pread(memfd.get(), output.get(), sizeof(output.get()), read_offset),
+ SyscallSucceedsWithValue(sizeof(expected)));
+ // Since output is larger, than expected we have to do manual compare
+ ASSERT_EQ(expected[0], (output).get()[0]);
+}
+
+// Perform read on /proc/[pid]/mem after exit.
+TEST(ProcPidMem, AfterExit) {
+ int pfd1[2] = {};
+ int pfd2[2] = {};
+
+ char expected[] = "hello-world";
+
+ ASSERT_THAT(pipe(pfd1), SyscallSucceeds());
+ ASSERT_THAT(pipe(pfd2), SyscallSucceeds());
+
+ // Create child process
+ pid_t const child_pid = fork();
+ if (child_pid == 0) {
+ // Close reading end of first pipe
+ close(pfd1[0]);
+
+ // Tell parent about location of input
+ char ok = 1;
+ TEST_CHECK(WriteFd(pfd1[1], &ok, sizeof(ok)) == sizeof(ok));
+ TEST_PCHECK(close(pfd1[1]) == 0);
+
+ // Close writing end of second pipe
+ TEST_PCHECK(close(pfd2[1]) == 0);
+
+ // Await parent OK to die
+ ok = 0;
+ TEST_CHECK(ReadFd(pfd2[0], &ok, sizeof(ok)) == sizeof(ok));
+
+ // Close rest pipes
+ TEST_PCHECK(close(pfd2[0]) == 0);
+ _exit(0);
+ }
+
+ // In parent process.
+ ASSERT_THAT(child_pid, SyscallSucceeds());
+
+ // Close writing end of first pipe
+ EXPECT_THAT(close(pfd1[1]), SyscallSucceeds());
+
+ // Wait for child to be alive and well
+ char ok = 0;
+ EXPECT_THAT(ReadFd(pfd1[0], &ok, sizeof(ok)),
+ SyscallSucceedsWithValue(sizeof(ok)));
+ // Close reading end of first pipe
+ EXPECT_THAT(close(pfd1[0]), SyscallSucceeds());
+
+ // Open /proc/pid/mem fd
+ std::string mempath = absl::StrCat("/proc/", child_pid, "/mem");
+ auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open(mempath, O_RDONLY));
+
+ // Expect that we can read
+ char output[sizeof(expected)];
+ EXPECT_THAT(pread(memfd.get(), &output, sizeof(output),
+ reinterpret_cast<off_t>(&expected)),
+ SyscallSucceedsWithValue(sizeof(output)));
+ EXPECT_STREQ(expected, output);
+
+ // Tell proc its ok to go
+ EXPECT_THAT(close(pfd2[0]), SyscallSucceeds());
+ ok = 1;
+ EXPECT_THAT(WriteFd(pfd2[1], &ok, sizeof(ok)),
+ SyscallSucceedsWithValue(sizeof(ok)));
+ EXPECT_THAT(close(pfd2[1]), SyscallSucceeds());
+
+ // Expect termination
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds());
+
+ // Expect that we can't read anymore
+ EXPECT_THAT(pread(memfd.get(), &output, sizeof(output),
+ reinterpret_cast<off_t>(&expected)),
+ SyscallSucceedsWithValue(0));
+}
+
+// Read from /proc/[pid]/mem with different UID/GID and attached state.
+TEST(ProcPidMem, DifferentUserAttached) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_DAC_OVERRIDE)));
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_PTRACE)));
+
+ int pfd1[2] = {};
+ int pfd2[2] = {};
+
+ ASSERT_THAT(pipe(pfd1), SyscallSucceeds());
+ ASSERT_THAT(pipe(pfd2), SyscallSucceeds());
+
+ // Create child process
+ pid_t const child_pid = fork();
+ if (child_pid == 0) {
+ // Close reading end of first pipe
+ close(pfd1[0]);
+
+ // Tell parent about location of input
+ char input[] = "hello-world";
+ off_t input_location = reinterpret_cast<off_t>(input);
+ TEST_CHECK(WriteFd(pfd1[1], &input_location, sizeof(input_location)) ==
+ sizeof(input_location));
+ TEST_PCHECK(close(pfd1[1]) == 0);
+
+ // Close writing end of second pipe
+ TEST_PCHECK(close(pfd2[1]) == 0);
+
+ // Await parent OK to die
+ char ok = 0;
+ TEST_CHECK(ReadFd(pfd2[0], &ok, sizeof(ok)) == sizeof(ok));
+
+ // Close rest pipes
+ TEST_PCHECK(close(pfd2[0]) == 0);
+ _exit(0);
+ }
+
+ // In parent process.
+ ASSERT_THAT(child_pid, SyscallSucceeds());
+
+ // Close writing end of first pipe
+ EXPECT_THAT(close(pfd1[1]), SyscallSucceeds());
+
+ // Read target location from child
+ off_t target_location;
+ EXPECT_THAT(ReadFd(pfd1[0], &target_location, sizeof(target_location)),
+ SyscallSucceedsWithValue(sizeof(target_location)));
+ // Close reading end of first pipe
+ EXPECT_THAT(close(pfd1[0]), SyscallSucceeds());
+
+ ScopedThread([&] {
+ // Attach to child subprocess without stopping it
+ EXPECT_THAT(ptrace(PTRACE_SEIZE, child_pid, NULL, NULL), SyscallSucceeds());
+
+ // Keep capabilities after setuid
+ EXPECT_THAT(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0), SyscallSucceeds());
+ constexpr int kNobody = 65534;
+ EXPECT_THAT(syscall(SYS_setuid, kNobody), SyscallSucceeds());
+
+ // Only restore CAP_SYS_PTRACE and CAP_DAC_OVERRIDE
+ EXPECT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, true));
+ EXPECT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, true));
+
+ // Open /proc/pid/mem fd
+ std::string mempath = absl::StrCat("/proc/", child_pid, "/mem");
+ auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open(mempath, O_RDONLY));
+ char expected[] = "hello-world";
+ char output[sizeof(expected)];
+ EXPECT_THAT(pread(memfd.get(), output, sizeof(output),
+ reinterpret_cast<off_t>(target_location)),
+ SyscallSucceedsWithValue(sizeof(output)));
+ EXPECT_STREQ(expected, output);
+
+ // Tell proc its ok to go
+ EXPECT_THAT(close(pfd2[0]), SyscallSucceeds());
+ char ok = 1;
+ EXPECT_THAT(WriteFd(pfd2[1], &ok, sizeof(ok)),
+ SyscallSucceedsWithValue(sizeof(ok)));
+ EXPECT_THAT(close(pfd2[1]), SyscallSucceeds());
+
+ // Expect termination
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds());
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
+ << " status " << status;
+ });
+}
+
+// Attempt to read from /proc/[pid]/mem with different UID/GID.
+TEST(ProcPidMem, DifferentUser) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));
+
+ int pfd1[2] = {};
+ int pfd2[2] = {};
+
+ ASSERT_THAT(pipe(pfd1), SyscallSucceeds());
+ ASSERT_THAT(pipe(pfd2), SyscallSucceeds());
+
+ // Create child process
+ pid_t const child_pid = fork();
+ if (child_pid == 0) {
+ // Close reading end of first pipe
+ close(pfd1[0]);
+
+ // Tell parent about location of input
+ char input[] = "hello-world";
+ off_t input_location = reinterpret_cast<off_t>(input);
+ TEST_CHECK(WriteFd(pfd1[1], &input_location, sizeof(input_location)) ==
+ sizeof(input_location));
+ TEST_PCHECK(close(pfd1[1]) == 0);
+
+ // Close writing end of second pipe
+ TEST_PCHECK(close(pfd2[1]) == 0);
+
+ // Await parent OK to die
+ char ok = 0;
+ TEST_CHECK(ReadFd(pfd2[0], &ok, sizeof(ok)) == sizeof(ok));
+
+ // Close rest pipes
+ TEST_PCHECK(close(pfd2[0]) == 0);
+ _exit(0);
+ }
+
+ // In parent process.
+ ASSERT_THAT(child_pid, SyscallSucceeds());
+
+ // Close writing end of first pipe
+ EXPECT_THAT(close(pfd1[1]), SyscallSucceeds());
+
+ // Read target location from child
+ off_t target_location;
+ EXPECT_THAT(ReadFd(pfd1[0], &target_location, sizeof(target_location)),
+ SyscallSucceedsWithValue(sizeof(target_location)));
+ // Close reading end of first pipe
+ EXPECT_THAT(close(pfd1[0]), SyscallSucceeds());
+
+ ScopedThread([&] {
+ constexpr int kNobody = 65534;
+ EXPECT_THAT(syscall(SYS_setuid, kNobody), SyscallSucceeds());
+
+ // Attempt to open /proc/[child_pid]/mem
+ std::string mempath = absl::StrCat("/proc/", child_pid, "/mem");
+ EXPECT_THAT(open(mempath.c_str(), O_RDONLY), SyscallFailsWithErrno(EACCES));
+
+ // Tell proc its ok to go
+ EXPECT_THAT(close(pfd2[0]), SyscallSucceeds());
+ char ok = 1;
+ EXPECT_THAT(WriteFd(pfd2[1], &ok, sizeof(ok)),
+ SyscallSucceedsWithValue(sizeof(ok)));
+ EXPECT_THAT(close(pfd2[1]), SyscallSucceeds());
+
+ // Expect termination
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds());
+ });
+}
+
+// Perform read on /proc/[pid]/mem with same UID/GID.
+TEST(ProcPidMem, SameUser) {
+ int pfd1[2] = {};
+ int pfd2[2] = {};
+
+ ASSERT_THAT(pipe(pfd1), SyscallSucceeds());
+ ASSERT_THAT(pipe(pfd2), SyscallSucceeds());
+
+ // Create child process
+ pid_t const child_pid = fork();
+ if (child_pid == 0) {
+ // Close reading end of first pipe
+ close(pfd1[0]);
+
+ // Tell parent about location of input
+ char input[] = "hello-world";
+ off_t input_location = reinterpret_cast<off_t>(input);
+ TEST_CHECK(WriteFd(pfd1[1], &input_location, sizeof(input_location)) ==
+ sizeof(input_location));
+ TEST_PCHECK(close(pfd1[1]) == 0);
+
+ // Close writing end of second pipe
+ TEST_PCHECK(close(pfd2[1]) == 0);
+
+ // Await parent OK to die
+ char ok = 0;
+ TEST_CHECK(ReadFd(pfd2[0], &ok, sizeof(ok)) == sizeof(ok));
+
+ // Close rest pipes
+ TEST_PCHECK(close(pfd2[0]) == 0);
+ _exit(0);
+ }
+ // In parent process.
+ ASSERT_THAT(child_pid, SyscallSucceeds());
+
+ // Close writing end of first pipe
+ EXPECT_THAT(close(pfd1[1]), SyscallSucceeds());
+
+ // Read target location from child
+ off_t target_location;
+ EXPECT_THAT(ReadFd(pfd1[0], &target_location, sizeof(target_location)),
+ SyscallSucceedsWithValue(sizeof(target_location)));
+ // Close reading end of first pipe
+ EXPECT_THAT(close(pfd1[0]), SyscallSucceeds());
+
+ // Open /proc/pid/mem fd
+ std::string mempath = absl::StrCat("/proc/", child_pid, "/mem");
+ auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open(mempath, O_RDONLY));
+ char expected[] = "hello-world";
+ char output[sizeof(expected)];
+ EXPECT_THAT(pread(memfd.get(), output, sizeof(output),
+ reinterpret_cast<off_t>(target_location)),
+ SyscallSucceedsWithValue(sizeof(output)));
+ EXPECT_STREQ(expected, output);
+
+ // Tell proc its ok to go
+ EXPECT_THAT(close(pfd2[0]), SyscallSucceeds());
+ char ok = 1;
+ EXPECT_THAT(WriteFd(pfd2[1], &ok, sizeof(ok)),
+ SyscallSucceedsWithValue(sizeof(ok)));
+ EXPECT_THAT(close(pfd2[1]), SyscallSucceeds());
+
+ // Expect termination
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds());
+}
+
// Just open and read /proc/self/maps, check that we can find [stack]
TEST(ProcSelfMaps, Basic) {
auto proc_self_maps =
@@ -2000,6 +2410,28 @@ TEST(ProcFilesystems, PresenceOfShmMaxMniAll) {
ASSERT_LE(shmall, ULONG_MAX - (1UL << 24));
}
+TEST(ProcFilesystems, PresenceOfSem) {
+ uint32_t semmsl = 0;
+ uint32_t semmns = 0;
+ uint32_t semopm = 0;
+ uint32_t semmni = 0;
+ std::string proc_file;
+ proc_file = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/sem"));
+ ASSERT_FALSE(proc_file.empty());
+ std::vector<absl::string_view> sem_limits =
+ absl::StrSplit(proc_file, absl::ByAnyChar("\t"), absl::SkipWhitespace());
+ ASSERT_EQ(sem_limits.size(), 4);
+ ASSERT_TRUE(absl::SimpleAtoi(sem_limits[0], &semmsl));
+ ASSERT_TRUE(absl::SimpleAtoi(sem_limits[1], &semmns));
+ ASSERT_TRUE(absl::SimpleAtoi(sem_limits[2], &semopm));
+ ASSERT_TRUE(absl::SimpleAtoi(sem_limits[3], &semmni));
+
+ ASSERT_EQ(semmsl, SEMMSL);
+ ASSERT_EQ(semmns, SEMMNS);
+ ASSERT_EQ(semopm, SEMOPM);
+ ASSERT_EQ(semmni, SEMMNI);
+}
+
// Check that /proc/mounts is a symlink to self/mounts.
TEST(ProcMounts, IsSymlink) {
auto link = ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/mounts"));
@@ -2050,7 +2482,7 @@ void CheckDuplicatesRecursively(std::string path) {
return;
}
auto dir_closer = Cleanup([&dir]() { closedir(dir); });
- std::unordered_set<std::string> children;
+ absl::node_hash_set<std::string> children;
while (true) {
// Readdir(3): If the end of the directory stream is reached, NULL is
// returned and errno is not changed. If an error occurs, NULL is
@@ -2069,6 +2501,10 @@ void CheckDuplicatesRecursively(std::string path) {
absl::EndsWith(path, "/net")) {
break;
}
+ // We may also see permission failures traversing some files.
+ if (errno == EACCES && absl::StartsWith(path, "/proc/")) {
+ break;
+ }
// Otherwise, no errors are allowed.
ASSERT_EQ(errno, 0) << path;
diff --git a/test/syscalls/linux/proc_pid_smaps.cc b/test/syscalls/linux/proc_pid_smaps.cc
index 9fb1b3a2c..738923822 100644
--- a/test/syscalls/linux/proc_pid_smaps.cc
+++ b/test/syscalls/linux/proc_pid_smaps.cc
@@ -191,7 +191,7 @@ PosixErrorOr<std::vector<ProcPidSmapsEntry>> ParseProcPidSmaps(
// amount of whitespace).
if (!entry) {
std::cerr << "smaps line not considered a maps line: "
- << maybe_maps_entry.error_message() << std::endl;
+ << maybe_maps_entry.error().message() << std::endl;
return PosixError(
EINVAL,
absl::StrCat("smaps field line without preceding maps line: ", l));
diff --git a/test/syscalls/linux/ptrace.cc b/test/syscalls/linux/ptrace.cc
index 926690eb8..13c19d4a8 100644
--- a/test/syscalls/linux/ptrace.cc
+++ b/test/syscalls/linux/ptrace.cc
@@ -30,10 +30,13 @@
#include "absl/flags/flag.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
+#include "test/util/fs_util.h"
#include "test/util/logging.h"
+#include "test/util/memory_util.h"
#include "test/util/multiprocess_util.h"
#include "test/util/platform_util.h"
#include "test/util/signal_util.h"
+#include "test/util/temp_path.h"
#include "test/util/test_util.h"
#include "test/util/thread_util.h"
#include "test/util/time_util.h"
@@ -113,10 +116,21 @@ TEST(PtraceTest, AttachParent_PeekData_PokeData_SignalSuppression) {
// except disabled.
SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) > 0);
- constexpr long kBeforePokeDataValue = 10;
- constexpr long kAfterPokeDataValue = 20;
+ // Test PTRACE_POKE/PEEKDATA on both anonymous and file mappings.
+ const auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ ASSERT_NO_ERRNO(Truncate(file.path(), kPageSize));
+ const FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
+ const auto file_mapping = ASSERT_NO_ERRNO_AND_VALUE(Mmap(
+ nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0));
- volatile long word = kBeforePokeDataValue;
+ constexpr long kBeforePokeDataAnonValue = 10;
+ constexpr long kAfterPokeDataAnonValue = 20;
+ constexpr long kBeforePokeDataFileValue = 0; // implicit, due to truncate()
+ constexpr long kAfterPokeDataFileValue = 30;
+
+ volatile long anon_word = kBeforePokeDataAnonValue;
+ auto* file_word_ptr = static_cast<volatile long*>(file_mapping.ptr());
pid_t const child_pid = fork();
if (child_pid == 0) {
@@ -134,12 +148,22 @@ TEST(PtraceTest, AttachParent_PeekData_PokeData_SignalSuppression) {
MaybeSave();
TEST_CHECK(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP);
- // Replace the value of word in the parent process with kAfterPokeDataValue.
- long const parent_word = ptrace(PTRACE_PEEKDATA, parent_pid, &word, 0);
+ // Replace the value of anon_word in the parent process with
+ // kAfterPokeDataAnonValue.
+ long parent_word = ptrace(PTRACE_PEEKDATA, parent_pid, &anon_word, 0);
+ MaybeSave();
+ TEST_CHECK(parent_word == kBeforePokeDataAnonValue);
+ TEST_PCHECK(ptrace(PTRACE_POKEDATA, parent_pid, &anon_word,
+ kAfterPokeDataAnonValue) == 0);
+ MaybeSave();
+
+ // Replace the value pointed to by file_word_ptr in the mapped file with
+ // kAfterPokeDataFileValue, via the parent process' mapping.
+ parent_word = ptrace(PTRACE_PEEKDATA, parent_pid, file_word_ptr, 0);
MaybeSave();
- TEST_CHECK(parent_word == kBeforePokeDataValue);
- TEST_PCHECK(
- ptrace(PTRACE_POKEDATA, parent_pid, &word, kAfterPokeDataValue) == 0);
+ TEST_CHECK(parent_word == kBeforePokeDataFileValue);
+ TEST_PCHECK(ptrace(PTRACE_POKEDATA, parent_pid, file_word_ptr,
+ kAfterPokeDataFileValue) == 0);
MaybeSave();
// Detach from the parent and suppress the SIGSTOP. If the SIGSTOP is not
@@ -160,7 +184,8 @@ TEST(PtraceTest, AttachParent_PeekData_PokeData_SignalSuppression) {
<< " status " << status;
// Check that the child's PTRACE_POKEDATA was effective.
- EXPECT_EQ(kAfterPokeDataValue, word);
+ EXPECT_EQ(kAfterPokeDataAnonValue, anon_word);
+ EXPECT_EQ(kAfterPokeDataFileValue, *file_word_ptr);
}
TEST(PtraceTest, GetSigMask) {
diff --git a/test/syscalls/linux/raw_socket.cc b/test/syscalls/linux/raw_socket.cc
index 54709371c..955bcee4b 100644
--- a/test/syscalls/linux/raw_socket.cc
+++ b/test/syscalls/linux/raw_socket.cc
@@ -852,6 +852,51 @@ TEST(RawSocketTest, IPv6ProtoRaw) {
SyscallFailsWithErrno(EINVAL));
}
+TEST(RawSocketTest, IPv6SendMsg) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ int sock;
+ ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_TCP),
+ SyscallSucceeds());
+
+ char kBuf[] = "hello";
+ struct iovec iov = {};
+ iov.iov_base = static_cast<void*>(const_cast<char*>(kBuf));
+ iov.iov_len = static_cast<size_t>(sizeof(kBuf));
+
+ struct sockaddr_storage addr = {};
+ struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(&addr);
+ sin->sin_family = AF_INET;
+ sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ struct msghdr msg = {};
+ msg.msg_name = static_cast<void*>(&addr);
+ msg.msg_namelen = sizeof(sockaddr_in);
+ 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), SyscallFailsWithErrno(EINVAL));
+}
+
+TEST_P(RawSocketTest, ConnectOnIPv6Socket) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ int sock;
+ ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_TCP),
+ SyscallSucceeds());
+
+ struct sockaddr_storage addr = {};
+ struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(&addr);
+ sin->sin_family = AF_INET;
+ sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ ASSERT_THAT(connect(sock, reinterpret_cast<struct sockaddr*>(&addr),
+ sizeof(sockaddr_in6)),
+ SyscallFailsWithErrno(EAFNOSUPPORT));
+}
+
INSTANTIATE_TEST_SUITE_P(
AllInetTests, RawSocketTest,
::testing::Combine(::testing::Values(IPPROTO_TCP, IPPROTO_UDP),
diff --git a/test/syscalls/linux/raw_socket_icmp.cc b/test/syscalls/linux/raw_socket_icmp.cc
index 1b9dbc584..bd779da92 100644
--- a/test/syscalls/linux/raw_socket_icmp.cc
+++ b/test/syscalls/linux/raw_socket_icmp.cc
@@ -438,6 +438,19 @@ TEST_F(RawSocketICMPTest, SetAndGetSocketLinger) {
EXPECT_EQ(0, memcmp(&sl, &got_linger, length));
}
+// Test getsockopt for SO_ACCEPTCONN.
+TEST_F(RawSocketICMPTest, GetSocketAcceptConn) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ int got = -1;
+ socklen_t length = sizeof(got);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_ACCEPTCONN, &got, &length),
+ SyscallSucceedsWithValue(0));
+
+ ASSERT_EQ(length, sizeof(got));
+ EXPECT_EQ(got, 0);
+}
+
void RawSocketICMPTest::ExpectICMPSuccess(const struct icmphdr& icmp) {
// We're going to receive both the echo request and reply, but the order is
// indeterminate.
diff --git a/test/syscalls/linux/semaphore.cc b/test/syscalls/linux/semaphore.cc
index e9b131ca9..fb4695e72 100644
--- a/test/syscalls/linux/semaphore.cc
+++ b/test/syscalls/linux/semaphore.cc
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include <signal.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>
@@ -19,6 +20,7 @@
#include <atomic>
#include <cerrno>
#include <ctime>
+#include <stack>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
@@ -486,6 +488,331 @@ TEST(SemaphoreTest, SemIpcSet) {
ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallFailsWithErrno(EACCES));
}
+TEST(SemaphoreTest, SemCtlIpcStat) {
+ // Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions.
+ ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false));
+ const uid_t kUid = getuid();
+ const gid_t kGid = getgid();
+ time_t start_time = time(nullptr);
+
+ AutoSem sem(semget(IPC_PRIVATE, 10, 0600 | IPC_CREAT));
+ ASSERT_THAT(sem.get(), SyscallSucceeds());
+
+ struct semid_ds ds;
+ EXPECT_THAT(semctl(sem.get(), 0, IPC_STAT, &ds), SyscallSucceeds());
+
+ EXPECT_EQ(ds.sem_perm.__key, IPC_PRIVATE);
+ EXPECT_EQ(ds.sem_perm.uid, kUid);
+ EXPECT_EQ(ds.sem_perm.gid, kGid);
+ EXPECT_EQ(ds.sem_perm.cuid, kUid);
+ EXPECT_EQ(ds.sem_perm.cgid, kGid);
+ EXPECT_EQ(ds.sem_perm.mode, 0600);
+ // Last semop time is not set on creation.
+ EXPECT_EQ(ds.sem_otime, 0);
+ EXPECT_GE(ds.sem_ctime, start_time);
+ EXPECT_EQ(ds.sem_nsems, 10);
+
+ // The timestamps only have a resolution of seconds; slow down so we actually
+ // see the timestamps change.
+ absl::SleepFor(absl::Seconds(1));
+
+ // Set semid_ds structure of the set.
+ auto last_ctime = ds.sem_ctime;
+ start_time = time(nullptr);
+ struct semid_ds semid_to_set = {};
+ semid_to_set.sem_perm.uid = kUid;
+ semid_to_set.sem_perm.gid = kGid;
+ semid_to_set.sem_perm.mode = 0666;
+ ASSERT_THAT(semctl(sem.get(), 0, IPC_SET, &semid_to_set), SyscallSucceeds());
+ struct sembuf buf = {};
+ buf.sem_op = 1;
+ ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallSucceeds());
+
+ EXPECT_THAT(semctl(sem.get(), 0, IPC_STAT, &ds), SyscallSucceeds());
+ EXPECT_EQ(ds.sem_perm.mode, 0666);
+ EXPECT_GE(ds.sem_otime, start_time);
+ EXPECT_GT(ds.sem_ctime, last_ctime);
+
+ // An invalid semid fails the syscall with errno EINVAL.
+ EXPECT_THAT(semctl(sem.get() + 1, 0, IPC_STAT, &ds),
+ SyscallFailsWithErrno(EINVAL));
+
+ // Make semaphore not readable and check the signal fails.
+ semid_to_set.sem_perm.mode = 0200;
+ ASSERT_THAT(semctl(sem.get(), 0, IPC_SET, &semid_to_set), SyscallSucceeds());
+ EXPECT_THAT(semctl(sem.get(), 0, IPC_STAT, &ds),
+ SyscallFailsWithErrno(EACCES));
+}
+
+// Calls semctl(semid, 0, cmd) until the returned value is >= target, an
+// internal timeout expires, or semctl returns an error.
+PosixErrorOr<int> WaitSemctl(int semid, int target, int cmd) {
+ constexpr absl::Duration timeout = absl::Seconds(10);
+ const auto deadline = absl::Now() + timeout;
+ int semcnt = 0;
+ while (absl::Now() < deadline) {
+ semcnt = semctl(semid, 0, cmd);
+ if (semcnt < 0) {
+ return PosixError(errno, "semctl(GETZCNT) failed");
+ }
+ if (semcnt >= target) {
+ break;
+ }
+ absl::SleepFor(absl::Milliseconds(10));
+ }
+ return semcnt;
+}
+
+TEST(SemaphoreTest, SemopGetzcnt) {
+ // Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions.
+ ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false));
+ // Create a write only semaphore set.
+ AutoSem sem(semget(IPC_PRIVATE, 1, 0200 | IPC_CREAT));
+ ASSERT_THAT(sem.get(), SyscallSucceeds());
+
+ // No read permission to retrieve semzcnt.
+ EXPECT_THAT(semctl(sem.get(), 0, GETZCNT), SyscallFailsWithErrno(EACCES));
+
+ // Remove the calling thread's read permission.
+ struct semid_ds ds = {};
+ ds.sem_perm.uid = getuid();
+ ds.sem_perm.gid = getgid();
+ ds.sem_perm.mode = 0600;
+ ASSERT_THAT(semctl(sem.get(), 0, IPC_SET, &ds), SyscallSucceeds());
+
+ std::vector<pid_t> children;
+ ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 1), SyscallSucceeds());
+
+ struct sembuf buf = {};
+ buf.sem_num = 0;
+ buf.sem_op = 0;
+ constexpr size_t kLoops = 10;
+ for (auto i = 0; i < kLoops; i++) {
+ auto child_pid = fork();
+ if (child_pid == 0) {
+ TEST_PCHECK(RetryEINTR(semop)(sem.get(), &buf, 1) == 0);
+ _exit(0);
+ }
+ children.push_back(child_pid);
+ }
+
+ EXPECT_THAT(WaitSemctl(sem.get(), kLoops, GETZCNT),
+ IsPosixErrorOkAndHolds(kLoops));
+ // Set semval to 0, which wakes up children that sleep on the semop.
+ ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 0), SyscallSucceeds());
+ for (const auto& child_pid : children) {
+ int status;
+ ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+ }
+ EXPECT_EQ(semctl(sem.get(), 0, GETZCNT), 0);
+}
+
+TEST(SemaphoreTest, SemopGetzcntOnSetRemoval) {
+ auto semid = semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT);
+ ASSERT_THAT(semid, SyscallSucceeds());
+ ASSERT_THAT(semctl(semid, 0, SETVAL, 1), SyscallSucceeds());
+ ASSERT_EQ(semctl(semid, 0, GETZCNT), 0);
+
+ auto child_pid = fork();
+ if (child_pid == 0) {
+ struct sembuf buf = {};
+ buf.sem_num = 0;
+ buf.sem_op = 0;
+
+ // Ensure that wait will only unblock when the semaphore is removed. On
+ // EINTR retry it may race with deletion and return EINVAL.
+ TEST_PCHECK(RetryEINTR(semop)(semid, &buf, 1) < 0 &&
+ (errno == EIDRM || errno == EINVAL));
+ _exit(0);
+ }
+
+ EXPECT_THAT(WaitSemctl(semid, 1, GETZCNT), IsPosixErrorOkAndHolds(1));
+ // Remove the semaphore set, which fails the sleep semop.
+ ASSERT_THAT(semctl(semid, 0, IPC_RMID), SyscallSucceeds());
+ int status;
+ ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+ EXPECT_THAT(semctl(semid, 0, GETZCNT), SyscallFailsWithErrno(EINVAL));
+}
+
+TEST(SemaphoreTest, SemopGetzcntOnSignal_NoRandomSave) {
+ AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT));
+ ASSERT_THAT(sem.get(), SyscallSucceeds());
+ ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 1), SyscallSucceeds());
+ ASSERT_EQ(semctl(sem.get(), 0, GETZCNT), 0);
+
+ // Saving will cause semop() to be spuriously interrupted.
+ DisableSave ds;
+
+ auto child_pid = fork();
+ if (child_pid == 0) {
+ TEST_PCHECK(signal(SIGHUP, [](int sig) -> void {}) != SIG_ERR);
+ struct sembuf buf = {};
+ buf.sem_num = 0;
+ buf.sem_op = 0;
+
+ TEST_PCHECK(semop(sem.get(), &buf, 1) < 0 && errno == EINTR);
+ _exit(0);
+ }
+
+ EXPECT_THAT(WaitSemctl(sem.get(), 1, GETZCNT), IsPosixErrorOkAndHolds(1));
+ // Send a signal to the child, which fails the sleep semop.
+ ASSERT_EQ(kill(child_pid, SIGHUP), 0);
+
+ ds.reset();
+
+ int status;
+ ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+ EXPECT_EQ(semctl(sem.get(), 0, GETZCNT), 0);
+}
+
+TEST(SemaphoreTest, SemopGetncnt) {
+ // Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions.
+ ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false));
+ // Create a write only semaphore set.
+ AutoSem sem(semget(IPC_PRIVATE, 1, 0200 | IPC_CREAT));
+ ASSERT_THAT(sem.get(), SyscallSucceeds());
+
+ // No read permission to retrieve semzcnt.
+ EXPECT_THAT(semctl(sem.get(), 0, GETNCNT), SyscallFailsWithErrno(EACCES));
+
+ // Remove the calling thread's read permission.
+ struct semid_ds ds = {};
+ ds.sem_perm.uid = getuid();
+ ds.sem_perm.gid = getgid();
+ ds.sem_perm.mode = 0600;
+ ASSERT_THAT(semctl(sem.get(), 0, IPC_SET, &ds), SyscallSucceeds());
+
+ std::vector<pid_t> children;
+
+ struct sembuf buf = {};
+ buf.sem_num = 0;
+ buf.sem_op = -1;
+ constexpr size_t kLoops = 10;
+ for (auto i = 0; i < kLoops; i++) {
+ auto child_pid = fork();
+ if (child_pid == 0) {
+ TEST_PCHECK(RetryEINTR(semop)(sem.get(), &buf, 1) == 0);
+ _exit(0);
+ }
+ children.push_back(child_pid);
+ }
+ EXPECT_THAT(WaitSemctl(sem.get(), kLoops, GETNCNT),
+ IsPosixErrorOkAndHolds(kLoops));
+ // Set semval to 1, which wakes up children that sleep on the semop.
+ ASSERT_THAT(semctl(sem.get(), 0, SETVAL, kLoops), SyscallSucceeds());
+ for (const auto& child_pid : children) {
+ int status;
+ ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+ }
+ EXPECT_EQ(semctl(sem.get(), 0, GETNCNT), 0);
+}
+
+TEST(SemaphoreTest, SemopGetncntOnSetRemoval) {
+ auto semid = semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT);
+ ASSERT_THAT(semid, SyscallSucceeds());
+ ASSERT_EQ(semctl(semid, 0, GETNCNT), 0);
+
+ auto child_pid = fork();
+ if (child_pid == 0) {
+ struct sembuf buf = {};
+ buf.sem_num = 0;
+ buf.sem_op = -1;
+
+ // Ensure that wait will only unblock when the semaphore is removed. On
+ // EINTR retry it may race with deletion and return EINVAL
+ TEST_PCHECK(RetryEINTR(semop)(semid, &buf, 1) < 0 &&
+ (errno == EIDRM || errno == EINVAL));
+ _exit(0);
+ }
+
+ EXPECT_THAT(WaitSemctl(semid, 1, GETNCNT), IsPosixErrorOkAndHolds(1));
+ // Remove the semaphore set, which fails the sleep semop.
+ ASSERT_THAT(semctl(semid, 0, IPC_RMID), SyscallSucceeds());
+ int status;
+ ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+ EXPECT_THAT(semctl(semid, 0, GETNCNT), SyscallFailsWithErrno(EINVAL));
+}
+
+TEST(SemaphoreTest, SemopGetncntOnSignal_NoRandomSave) {
+ AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT));
+ ASSERT_THAT(sem.get(), SyscallSucceeds());
+ ASSERT_EQ(semctl(sem.get(), 0, GETNCNT), 0);
+
+ // Saving will cause semop() to be spuriously interrupted.
+ DisableSave ds;
+
+ auto child_pid = fork();
+ if (child_pid == 0) {
+ TEST_PCHECK(signal(SIGHUP, [](int sig) -> void {}) != SIG_ERR);
+ struct sembuf buf = {};
+ buf.sem_num = 0;
+ buf.sem_op = -1;
+
+ TEST_PCHECK(semop(sem.get(), &buf, 1) < 0 && errno == EINTR);
+ _exit(0);
+ }
+ EXPECT_THAT(WaitSemctl(sem.get(), 1, GETNCNT), IsPosixErrorOkAndHolds(1));
+ // Send a signal to the child, which fails the sleep semop.
+ ASSERT_EQ(kill(child_pid, SIGHUP), 0);
+
+ ds.reset();
+
+ int status;
+ ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+ EXPECT_EQ(semctl(sem.get(), 0, GETNCNT), 0);
+}
+
+TEST(SemaphoreTest, IpcInfo) {
+ std::stack<int> sem_ids;
+ std::stack<int> max_used_indexes;
+ struct seminfo info;
+ for (int i = 0; i < 3; i++) {
+ int sem_id = 0;
+ ASSERT_THAT(sem_id = semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT),
+ SyscallSucceeds());
+ sem_ids.push(sem_id);
+ int max_used_index = 0;
+ EXPECT_THAT(max_used_index = semctl(0, 0, IPC_INFO, &info),
+ SyscallSucceeds());
+ if (!max_used_indexes.empty()) {
+ EXPECT_GT(max_used_index, max_used_indexes.top());
+ }
+ max_used_indexes.push(max_used_index);
+ }
+ while (!sem_ids.empty()) {
+ int sem_id = sem_ids.top();
+ sem_ids.pop();
+ ASSERT_THAT(semctl(sem_id, 0, IPC_RMID), SyscallSucceeds());
+ int max_index = max_used_indexes.top();
+ EXPECT_THAT(max_index = semctl(0, 0, IPC_INFO, &info), SyscallSucceeds());
+ EXPECT_GE(max_used_indexes.top(), max_index);
+ max_used_indexes.pop();
+ }
+ ASSERT_THAT(semctl(0, 0, IPC_INFO, &info), SyscallSucceedsWithValue(0));
+
+ EXPECT_EQ(info.semmap, 1024000000);
+ EXPECT_EQ(info.semmni, 32000);
+ EXPECT_EQ(info.semmns, 1024000000);
+ EXPECT_EQ(info.semmnu, 1024000000);
+ EXPECT_EQ(info.semmsl, 32000);
+ EXPECT_EQ(info.semopm, 500);
+ EXPECT_EQ(info.semume, 500);
+ EXPECT_EQ(info.semvmx, 32767);
+ EXPECT_EQ(info.semaem, 32767);
+}
+
} // namespace
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/sendfile.cc b/test/syscalls/linux/sendfile.cc
index a8bfb01f1..3924e0001 100644
--- a/test/syscalls/linux/sendfile.cc
+++ b/test/syscalls/linux/sendfile.cc
@@ -25,9 +25,11 @@
#include "absl/time/time.h"
#include "test/util/eventfd_util.h"
#include "test/util/file_descriptor.h"
+#include "test/util/signal_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"
namespace gvisor {
namespace testing {
@@ -629,6 +631,75 @@ TEST(SendFileTest, SendFileToPipe) {
SyscallSucceedsWithValue(kDataSize));
}
+TEST(SendFileTest, SendFileToSelf_NoRandomSave) {
+ int rawfd;
+ ASSERT_THAT(rawfd = memfd_create("memfd", 0), SyscallSucceeds());
+ const FileDescriptor fd(rawfd);
+
+ char c = 0x01;
+ ASSERT_THAT(WriteFd(fd.get(), &c, 1), SyscallSucceedsWithValue(1));
+
+ // Arbitrarily chosen to make sendfile() take long enough that the sentry
+ // watchdog usually fires unless it's reset by sendfile() between iterations
+ // of the buffered copy. See b/172076632.
+ constexpr size_t kSendfileSize = 0xa00000;
+
+ off_t offset = 0;
+ ASSERT_THAT(sendfile(fd.get(), fd.get(), &offset, kSendfileSize),
+ SyscallSucceedsWithValue(kSendfileSize));
+}
+
+static volatile int signaled = 0;
+void SigUsr1Handler(int sig, siginfo_t* info, void* context) { signaled = 1; }
+
+TEST(SendFileTest, ToEventFDDoesNotSpin_NoRandomSave) {
+ FileDescriptor efd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0));
+
+ // Write the maximum value of an eventfd to a file.
+ const uint64_t kMaxEventfdValue = 0xfffffffffffffffe;
+ const auto tempfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ const auto tempfd = ASSERT_NO_ERRNO_AND_VALUE(Open(tempfile.path(), O_RDWR));
+ ASSERT_THAT(
+ pwrite(tempfd.get(), &kMaxEventfdValue, sizeof(kMaxEventfdValue), 0),
+ SyscallSucceedsWithValue(sizeof(kMaxEventfdValue)));
+
+ // Set the eventfd's value to 1.
+ const uint64_t kOne = 1;
+ ASSERT_THAT(write(efd.get(), &kOne, sizeof(kOne)),
+ SyscallSucceedsWithValue(sizeof(kOne)));
+
+ // Set up signal handler.
+ struct sigaction sa = {};
+ sa.sa_sigaction = SigUsr1Handler;
+ sa.sa_flags = SA_SIGINFO;
+ const auto cleanup_sigact =
+ ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGUSR1, sa));
+
+ // Send SIGUSR1 to this thread in 1 second.
+ struct sigevent sev = {};
+ sev.sigev_notify = SIGEV_THREAD_ID;
+ sev.sigev_signo = SIGUSR1;
+ sev.sigev_notify_thread_id = gettid();
+ auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev));
+ struct itimerspec its = {};
+ its.it_value = absl::ToTimespec(absl::Seconds(1));
+ DisableSave ds; // Asserting an EINTR.
+ ASSERT_NO_ERRNO(timer.Set(0, its));
+
+ // Sendfile from tempfd to the eventfd. Since the eventfd is not already at
+ // its maximum value, the eventfd is "ready for writing"; however, since the
+ // eventfd's existing value plus the new value would exceed the maximum, the
+ // write should internally fail with EWOULDBLOCK. In this case, sendfile()
+ // should block instead of spinning, and eventually be interrupted by our
+ // timer. See b/172075629.
+ EXPECT_THAT(
+ sendfile(efd.get(), tempfd.get(), nullptr, sizeof(kMaxEventfdValue)),
+ SyscallFailsWithErrno(EINTR));
+
+ // Signal should have been handled.
+ EXPECT_EQ(signaled, 1);
+}
+
} // namespace
} // namespace testing
diff --git a/test/syscalls/linux/signalfd.cc b/test/syscalls/linux/signalfd.cc
index 389e5fca2..c86cd2755 100644
--- a/test/syscalls/linux/signalfd.cc
+++ b/test/syscalls/linux/signalfd.cc
@@ -126,7 +126,7 @@ TEST_P(SignalfdTest, Blocking) {
// Shared tid variable.
absl::Mutex mu;
- bool has_tid;
+ bool has_tid = false;
pid_t tid;
// Start a thread reading.
diff --git a/test/syscalls/linux/socket_generic.cc b/test/syscalls/linux/socket_generic.cc
index 5d39e6fbd..a28ee2233 100644
--- a/test/syscalls/linux/socket_generic.cc
+++ b/test/syscalls/linux/socket_generic.cc
@@ -818,5 +818,56 @@ TEST_P(AllSocketPairTest, GetSockoptProtocol) {
}
}
+TEST_P(AllSocketPairTest, SetAndGetBooleanSocketOptions) {
+ int sock_opts[] = {SO_BROADCAST, SO_PASSCRED, SO_NO_CHECK,
+ SO_REUSEADDR, SO_REUSEPORT, SO_KEEPALIVE};
+ for (int sock_opt : sock_opts) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+ int enable = -1;
+ socklen_t enableLen = sizeof(enable);
+
+ // Test that the option is initially set to false.
+ ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, sock_opt, &enable,
+ &enableLen),
+ SyscallSucceeds());
+ ASSERT_EQ(enableLen, sizeof(enable));
+ EXPECT_EQ(enable, 0) << absl::StrFormat(
+ "getsockopt(fd, SOL_SOCKET, %d, &enable, &enableLen) => enable=%d",
+ sock_opt, enable);
+
+ // Test that setting the option to true is reflected in the subsequent
+ // call to getsockopt(2).
+ enable = 1;
+ ASSERT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, sock_opt, &enable,
+ sizeof(enable)),
+ SyscallSucceeds());
+ enable = -1;
+ enableLen = sizeof(enable);
+ ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, sock_opt, &enable,
+ &enableLen),
+ SyscallSucceeds());
+ ASSERT_EQ(enableLen, sizeof(enable));
+ EXPECT_EQ(enable, 1) << absl::StrFormat(
+ "getsockopt(fd, SOL_SOCKET, %d, &enable, &enableLen) => enable=%d",
+ sock_opt, enable);
+ }
+}
+
+TEST_P(AllSocketPairTest, GetSocketOutOfBandInlineOption) {
+ // We do not support disabling this option. It is always enabled.
+ SKIP_IF(!IsRunningOnGvisor());
+
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+ int enable = -1;
+ socklen_t enableLen = sizeof(enable);
+
+ int want = 1;
+ ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_OOBINLINE, &enable,
+ &enableLen),
+ SyscallSucceeds());
+ ASSERT_EQ(enableLen, sizeof(enable));
+ EXPECT_EQ(enable, want);
+}
+
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/socket_inet_loopback.cc b/test/syscalls/linux/socket_inet_loopback.cc
index 11fcec443..51b77ad85 100644
--- a/test/syscalls/linux/socket_inet_loopback.cc
+++ b/test/syscalls/linux/socket_inet_loopback.cc
@@ -350,6 +350,10 @@ TEST_P(SocketInetLoopbackTest, TCPListenShutdownListen) {
sockaddr_storage conn_addr = connector.addr;
ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
+ // TODO(b/157236388): Remove Disable save after bug is fixed. S/R test can
+ // fail because the last socket may not be delivered to the accept queue
+ // by the time connect returns.
+ DisableSave ds;
for (int i = 0; i < kBacklog; i++) {
auto client = ASSERT_NO_ERRNO_AND_VALUE(
Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
@@ -554,7 +558,11 @@ TEST_P(SocketInetLoopbackTest, TCPListenShutdownWhileConnect) {
});
}
-TEST_P(SocketInetLoopbackTest, TCPbacklog) {
+// TODO(b/157236388): Remove _NoRandomSave once bug is fixed. Test fails w/
+// random save as established connections which can't be delivered to the accept
+// queue because the queue is full are not correctly delivered after restore
+// causing the last accept to timeout on the restore.
+TEST_P(SocketInetLoopbackTest, TCPbacklog_NoRandomSave) {
auto const& param = GetParam();
TestAddress const& listener = param.listener;
@@ -567,7 +575,8 @@ TEST_P(SocketInetLoopbackTest, TCPbacklog) {
ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr),
listener.addr_len),
SyscallSucceeds());
- ASSERT_THAT(listen(listen_fd.get(), 2), SyscallSucceeds());
+ constexpr int kBacklogSize = 2;
+ ASSERT_THAT(listen(listen_fd.get(), kBacklogSize), SyscallSucceeds());
// Get the port bound by the listening socket.
socklen_t addrlen = listener.addr_len;
@@ -931,7 +940,7 @@ void setupTimeWaitClose(const TestAddress* listener,
}
// shutdown to trigger TIME_WAIT.
- ASSERT_THAT(shutdown(active_closefd.get(), SHUT_RDWR), SyscallSucceeds());
+ ASSERT_THAT(shutdown(active_closefd.get(), SHUT_WR), SyscallSucceeds());
{
const int kTimeout = 10000;
struct pollfd pfd = {
@@ -941,7 +950,8 @@ void setupTimeWaitClose(const TestAddress* listener,
ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1));
ASSERT_EQ(pfd.revents, POLLIN);
}
- ScopedThread t([&]() {
+ ASSERT_THAT(shutdown(passive_closefd.get(), SHUT_WR), SyscallSucceeds());
+ {
constexpr int kTimeout = 10000;
constexpr int16_t want_events = POLLHUP;
struct pollfd pfd = {
@@ -949,11 +959,8 @@ void setupTimeWaitClose(const TestAddress* listener,
.events = want_events,
};
ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1));
- });
+ }
- passive_closefd.reset();
- t.Join();
- active_closefd.reset();
// This sleep is needed to reduce flake to ensure that the passive-close
// ensures the state transitions to CLOSE from LAST_ACK.
absl::SleepFor(absl::Seconds(1));
@@ -1143,6 +1150,9 @@ TEST_P(SocketInetLoopbackTest, TCPAcceptAfterReset) {
sockaddr_storage conn_addr = connector.addr;
ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
+
+ // TODO(b/157236388): Reenable Cooperative S/R once bug is fixed.
+ DisableSave ds;
ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(),
reinterpret_cast<sockaddr*>(&conn_addr),
connector.addr_len),
@@ -1175,19 +1185,44 @@ TEST_P(SocketInetLoopbackTest, TCPAcceptAfterReset) {
listen_fd.get(), reinterpret_cast<sockaddr*>(&accept_addr), &addrlen));
ASSERT_EQ(addrlen, listener.addr_len);
- // TODO(gvisor.dev/issue/3812): Remove after SO_ERROR is fixed.
- if (IsRunningOnGvisor()) {
- char buf[10];
- ASSERT_THAT(ReadFd(accept_fd.get(), buf, sizeof(buf)),
- SyscallFailsWithErrno(ECONNRESET));
- } else {
+ // Wait for accept_fd to process the RST.
+ const int kTimeout = 10000;
+ struct pollfd pfd = {
+ .fd = accept_fd.get(),
+ .events = POLLIN,
+ };
+ ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1));
+ ASSERT_EQ(pfd.revents, POLLIN | POLLHUP | POLLERR);
+
+ {
int err;
socklen_t optlen = sizeof(err);
ASSERT_THAT(
getsockopt(accept_fd.get(), SOL_SOCKET, SO_ERROR, &err, &optlen),
SyscallSucceeds());
+ // This should return ECONNRESET as the socket just received a RST packet
+ // from the peer.
+ ASSERT_EQ(optlen, sizeof(err));
ASSERT_EQ(err, ECONNRESET);
+ }
+ {
+ int err;
+ socklen_t optlen = sizeof(err);
+ ASSERT_THAT(
+ getsockopt(accept_fd.get(), SOL_SOCKET, SO_ERROR, &err, &optlen),
+ SyscallSucceeds());
+ // This should return no error as the previous getsockopt call would have
+ // cleared the socket error.
ASSERT_EQ(optlen, sizeof(err));
+ ASSERT_EQ(err, 0);
+ }
+ {
+ sockaddr_storage peer_addr;
+ socklen_t addrlen = sizeof(peer_addr);
+ // The socket is not connected anymore and should return ENOTCONN.
+ ASSERT_THAT(getpeername(accept_fd.get(),
+ reinterpret_cast<sockaddr*>(&peer_addr), &addrlen),
+ SyscallFailsWithErrno(ENOTCONN));
}
}
@@ -2795,5 +2830,28 @@ INSTANTIATE_TEST_SUITE_P(
} // namespace
+// Check that loopback receives connections from any address in the range:
+// 127.0.0.1 to 127.254.255.255. This behavior is exclusive to IPv4.
+TEST_F(SocketInetLoopbackTest, LoopbackAddressRangeConnect) {
+ TestAddress const& listener = V4Any();
+
+ in_addr_t addresses[] = {
+ INADDR_LOOPBACK,
+ INADDR_LOOPBACK + 1, // 127.0.0.2
+ (in_addr_t)0x7f000101, // 127.0.1.1
+ (in_addr_t)0x7f010101, // 127.1.1.1
+ (in_addr_t)0x7ffeffff, // 127.254.255.255
+ };
+ for (const auto& address : addresses) {
+ TestAddress connector("V4Loopback");
+ connector.addr.ss_family = AF_INET;
+ connector.addr_len = sizeof(sockaddr_in);
+ reinterpret_cast<sockaddr_in*>(&connector.addr)->sin_addr.s_addr =
+ htonl(address);
+
+ tcpSimpleConnectTest(listener, connector, true);
+ }
+}
+
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ip_udp_generic.cc b/test/syscalls/linux/socket_ip_udp_generic.cc
index 3f2c0fdf2..2fcd08112 100644
--- a/test/syscalls/linux/socket_ip_udp_generic.cc
+++ b/test/syscalls/linux/socket_ip_udp_generic.cc
@@ -15,6 +15,9 @@
#include "test/syscalls/linux/socket_ip_udp_generic.h"
#include <errno.h>
+#ifdef __linux__
+#include <linux/in6.h>
+#endif // __linux__
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <poll.h>
@@ -356,6 +359,58 @@ TEST_P(UDPSocketPairTest, SetAndGetIPPKTINFO) {
EXPECT_EQ(get_len, sizeof(get));
}
+// Test getsockopt for a socket which is not set with IP_RECVORIGDSTADDR option.
+TEST_P(UDPSocketPairTest, ReceiveOrigDstAddrDefault) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+
+ int get = -1;
+ socklen_t get_len = sizeof(get);
+ int level = SOL_IP;
+ int type = IP_RECVORIGDSTADDR;
+ if (sockets->first_addr()->sa_family == AF_INET6) {
+ level = SOL_IPV6;
+ type = IPV6_RECVORIGDSTADDR;
+ }
+ ASSERT_THAT(getsockopt(sockets->first_fd(), level, type, &get, &get_len),
+ SyscallSucceedsWithValue(0));
+ EXPECT_EQ(get_len, sizeof(get));
+ EXPECT_EQ(get, kSockOptOff);
+}
+
+// Test setsockopt and getsockopt for a socket with IP_RECVORIGDSTADDR option.
+TEST_P(UDPSocketPairTest, SetAndGetReceiveOrigDstAddr) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+
+ int level = SOL_IP;
+ int type = IP_RECVORIGDSTADDR;
+ if (sockets->first_addr()->sa_family == AF_INET6) {
+ level = SOL_IPV6;
+ type = IPV6_RECVORIGDSTADDR;
+ }
+
+ // Check getsockopt before IP_PKTINFO is set.
+ int get = -1;
+ socklen_t get_len = sizeof(get);
+
+ ASSERT_THAT(setsockopt(sockets->first_fd(), level, type, &kSockOptOn,
+ sizeof(kSockOptOn)),
+ SyscallSucceedsWithValue(0));
+
+ ASSERT_THAT(getsockopt(sockets->first_fd(), level, type, &get, &get_len),
+ SyscallSucceedsWithValue(0));
+ EXPECT_EQ(get, kSockOptOn);
+ EXPECT_EQ(get_len, sizeof(get));
+
+ ASSERT_THAT(setsockopt(sockets->first_fd(), level, type, &kSockOptOff,
+ sizeof(kSockOptOff)),
+ SyscallSucceedsWithValue(0));
+
+ ASSERT_THAT(getsockopt(sockets->first_fd(), level, type, &get, &get_len),
+ SyscallSucceedsWithValue(0));
+ EXPECT_EQ(get, kSockOptOff);
+ EXPECT_EQ(get_len, sizeof(get));
+}
+
// Holds TOS or TClass information for IPv4 or IPv6 respectively.
struct RecvTosOption {
int level;
@@ -472,5 +527,19 @@ TEST_P(UDPSocketPairTest, SetAndGetSocketLinger) {
EXPECT_EQ(0, memcmp(&sl, &got_linger, length));
}
+// Test getsockopt for SO_ACCEPTCONN on udp socket.
+TEST_P(UDPSocketPairTest, GetSocketAcceptConn) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+
+ int got = -1;
+ socklen_t length = sizeof(got);
+ ASSERT_THAT(
+ getsockopt(sockets->first_fd(), SOL_SOCKET, SO_ACCEPTCONN, &got, &length),
+ SyscallSucceedsWithValue(0));
+
+ ASSERT_EQ(length, sizeof(got));
+ EXPECT_EQ(got, 0);
+}
+
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ip_unbound.cc b/test/syscalls/linux/socket_ip_unbound.cc
index 8f7ccc868..029f1e872 100644
--- a/test/syscalls/linux/socket_ip_unbound.cc
+++ b/test/syscalls/linux/socket_ip_unbound.cc
@@ -454,23 +454,15 @@ TEST_P(IPUnboundSocketTest, SetReuseAddr) {
INSTANTIATE_TEST_SUITE_P(
IPUnboundSockets, IPUnboundSocketTest,
- ::testing::ValuesIn(VecCat<SocketKind>(VecCat<SocketKind>(
+ ::testing::ValuesIn(VecCat<SocketKind>(
ApplyVec<SocketKind>(IPv4UDPUnboundSocket,
- AllBitwiseCombinations(List<int>{SOCK_DGRAM},
- List<int>{0,
- SOCK_NONBLOCK})),
+ std::vector<int>{0, SOCK_NONBLOCK}),
ApplyVec<SocketKind>(IPv6UDPUnboundSocket,
- AllBitwiseCombinations(List<int>{SOCK_DGRAM},
- List<int>{0,
- SOCK_NONBLOCK})),
+ std::vector<int>{0, SOCK_NONBLOCK}),
ApplyVec<SocketKind>(IPv4TCPUnboundSocket,
- AllBitwiseCombinations(List<int>{SOCK_STREAM},
- List<int>{0,
- SOCK_NONBLOCK})),
+ std::vector{0, SOCK_NONBLOCK}),
ApplyVec<SocketKind>(IPv6TCPUnboundSocket,
- AllBitwiseCombinations(List<int>{SOCK_STREAM},
- List<int>{
- 0, SOCK_NONBLOCK}))))));
+ std::vector{0, SOCK_NONBLOCK}))));
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ip_unbound_netlink.cc b/test/syscalls/linux/socket_ip_unbound_netlink.cc
new file mode 100644
index 000000000..7fb1c0faf
--- /dev/null
+++ b/test/syscalls/linux/socket_ip_unbound_netlink.cc
@@ -0,0 +1,104 @@
+// 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 <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <cstdio>
+#include <cstring>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "test/syscalls/linux/ip_socket_test_util.h"
+#include "test/syscalls/linux/socket_netlink_route_util.h"
+#include "test/syscalls/linux/socket_test_util.h"
+#include "test/util/capability_util.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+// Test fixture for tests that apply to pairs of IP sockets.
+using IPv6UnboundSocketTest = SimpleSocketTest;
+
+TEST_P(IPv6UnboundSocketTest, ConnectToBadLocalAddress_NoRandomSave) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)));
+
+ // TODO(gvisor.dev/issue/4595): Addresses on net devices are not saved
+ // across save/restore.
+ DisableSave ds;
+
+ // Delete the loopback address from the loopback interface.
+ Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink());
+ EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET6,
+ /*prefixlen=*/128, &in6addr_loopback,
+ sizeof(in6addr_loopback)));
+ Cleanup defer_addr_removal =
+ Cleanup([loopback_link = std::move(loopback_link)] {
+ EXPECT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET6,
+ /*prefixlen=*/128, &in6addr_loopback,
+ sizeof(in6addr_loopback)));
+ });
+
+ TestAddress addr = V6Loopback();
+ reinterpret_cast<sockaddr_in6*>(&addr.addr)->sin6_port = 65535;
+ auto sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
+ EXPECT_THAT(connect(sock->get(), reinterpret_cast<sockaddr*>(&addr.addr),
+ addr.addr_len),
+ SyscallFailsWithErrno(EADDRNOTAVAIL));
+}
+
+INSTANTIATE_TEST_SUITE_P(IPUnboundSockets, IPv6UnboundSocketTest,
+ ::testing::ValuesIn(std::vector<SocketKind>{
+ IPv6UDPUnboundSocket(0),
+ IPv6TCPUnboundSocket(0)}));
+
+using IPv4UnboundSocketTest = SimpleSocketTest;
+
+TEST_P(IPv4UnboundSocketTest, ConnectToBadLocalAddress_NoRandomSave) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)));
+
+ // TODO(gvisor.dev/issue/4595): Addresses on net devices are not saved
+ // across save/restore.
+ DisableSave ds;
+
+ // Delete the loopback address from the loopback interface.
+ Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink());
+ struct in_addr laddr;
+ laddr.s_addr = htonl(INADDR_LOOPBACK);
+ EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET,
+ /*prefixlen=*/8, &laddr, sizeof(laddr)));
+ Cleanup defer_addr_removal = Cleanup(
+ [loopback_link = std::move(loopback_link), addr = std::move(laddr)] {
+ EXPECT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET,
+ /*prefixlen=*/8, &addr, sizeof(addr)));
+ });
+ TestAddress addr = V4Loopback();
+ reinterpret_cast<sockaddr_in*>(&addr.addr)->sin_port = 65535;
+ auto sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
+ EXPECT_THAT(connect(sock->get(), reinterpret_cast<sockaddr*>(&addr.addr),
+ addr.addr_len),
+ SyscallFailsWithErrno(ENETUNREACH));
+}
+
+INSTANTIATE_TEST_SUITE_P(IPUnboundSockets, IPv4UnboundSocketTest,
+ ::testing::ValuesIn(std::vector<SocketKind>{
+ IPv4UDPUnboundSocket(0),
+ IPv4TCPUnboundSocket(0)}));
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.cc b/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.cc
deleted file mode 100644
index 80f12b0a9..000000000
--- a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.cc
+++ /dev/null
@@ -1,66 +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 "test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h"
-
-#include <netinet/in.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/un.h>
-
-#include <cstdio>
-#include <cstring>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "test/syscalls/linux/socket_test_util.h"
-#include "test/util/test_util.h"
-
-namespace gvisor {
-namespace testing {
-
-// Verifies that a newly instantiated TCP socket does not have the
-// broadcast socket option enabled.
-TEST_P(IPv4TCPUnboundExternalNetworkingSocketTest, TCPBroadcastDefault) {
- auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- int get = -1;
- socklen_t get_sz = sizeof(get);
- EXPECT_THAT(
- getsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &get, &get_sz),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get, kSockOptOff);
- EXPECT_EQ(get_sz, sizeof(get));
-}
-
-// Verifies that a newly instantiated TCP socket returns true after enabling
-// the broadcast socket option.
-TEST_P(IPv4TCPUnboundExternalNetworkingSocketTest, SetTCPBroadcast) {
- auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- EXPECT_THAT(setsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn,
- sizeof(kSockOptOn)),
- SyscallSucceedsWithValue(0));
-
- int get = -1;
- socklen_t get_sz = sizeof(get);
- EXPECT_THAT(
- getsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &get, &get_sz),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get, kSockOptOn);
- EXPECT_EQ(get_sz, sizeof(get));
-}
-
-} // namespace testing
-} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound.cc b/test/syscalls/linux/socket_ipv4_udp_unbound.cc
index a72c76c97..e557572a7 100644
--- a/test/syscalls/linux/socket_ipv4_udp_unbound.cc
+++ b/test/syscalls/linux/socket_ipv4_udp_unbound.cc
@@ -28,6 +28,7 @@
#include "test/syscalls/linux/ip_socket_test_util.h"
#include "test/syscalls/linux/socket_test_util.h"
#include "test/util/posix_error.h"
+#include "test/util/save_util.h"
#include "test/util/test_util.h"
namespace gvisor {
@@ -75,7 +76,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackNoGroup) {
// Check that we did not receive the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
EXPECT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
PosixErrorIs(EAGAIN, ::testing::_));
}
@@ -209,7 +210,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackAddr) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
@@ -265,7 +266,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackNic) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
@@ -321,7 +322,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddr) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
@@ -377,7 +378,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNic) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
@@ -437,7 +438,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddrConnect) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
@@ -497,7 +498,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicConnect) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
@@ -553,7 +554,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddrSelf) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
@@ -609,7 +610,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelf) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
@@ -669,7 +670,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddrSelfConnect) {
// Check that we did not receive the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
EXPECT_THAT(
- RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
PosixErrorIs(EAGAIN, ::testing::_));
}
@@ -727,7 +728,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelfConnect) {
// Check that we did not receive the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
EXPECT_THAT(
- RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
PosixErrorIs(EAGAIN, ::testing::_));
}
@@ -785,7 +786,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddrSelfNoLoop) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
@@ -845,7 +846,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelfNoLoop) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
@@ -919,7 +920,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastDropAddr) {
// Check that we did not receive the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
EXPECT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
PosixErrorIs(EAGAIN, ::testing::_));
}
@@ -977,7 +978,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastDropNic) {
// Check that we did not receive the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
EXPECT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
PosixErrorIs(EAGAIN, ::testing::_));
}
@@ -1330,8 +1331,8 @@ TEST_P(IPv4UDPUnboundSocketTest, TestMcastReceptionOnTwoSockets) {
// Check that we received the multicast packet on both sockets.
for (auto& sockets : socket_pairs) {
char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(RecvMsgTimeout(sockets->second_fd(), recv_buf,
- sizeof(recv_buf), 1 /*timeout*/),
+ ASSERT_THAT(RecvTimeout(sockets->second_fd(), recv_buf, sizeof(recv_buf),
+ 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
@@ -1409,8 +1410,8 @@ TEST_P(IPv4UDPUnboundSocketTest, TestMcastReceptionWhenDroppingMemberships) {
// Check that we received the multicast packet on both sockets.
for (auto& sockets : socket_pairs) {
char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(RecvMsgTimeout(sockets->second_fd(), recv_buf,
- sizeof(recv_buf), 1 /*timeout*/),
+ ASSERT_THAT(RecvTimeout(sockets->second_fd(), recv_buf, sizeof(recv_buf),
+ 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
@@ -1432,8 +1433,8 @@ TEST_P(IPv4UDPUnboundSocketTest, TestMcastReceptionWhenDroppingMemberships) {
char recv_buf[sizeof(send_buf)] = {};
for (auto& sockets : socket_pairs) {
- ASSERT_THAT(RecvMsgTimeout(sockets->second_fd(), recv_buf,
- sizeof(recv_buf), 1 /*timeout*/),
+ ASSERT_THAT(RecvTimeout(sockets->second_fd(), recv_buf, sizeof(recv_buf),
+ 1 /*timeout*/),
PosixErrorIs(EAGAIN, ::testing::_));
}
}
@@ -1486,7 +1487,7 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToMcastThenJoinThenReceive) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
@@ -1530,7 +1531,7 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToMcastThenNoJoinThenNoReceive) {
// Check that we don't receive the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
PosixErrorIs(EAGAIN, ::testing::_));
}
@@ -1580,7 +1581,7 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToMcastThenSend) {
// Check that we received the packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
@@ -1627,7 +1628,7 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToBcastThenReceive) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
@@ -1678,7 +1679,7 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToBcastThenSend) {
// Check that we received the packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
@@ -1737,7 +1738,7 @@ TEST_P(IPv4UDPUnboundSocketTest, ReuseAddrDistribution_NoRandomSave) {
// of the other sockets to have received it, but we will check that later.
char recv_buf[sizeof(send_buf)] = {};
EXPECT_THAT(
- RecvMsgTimeout(last->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(last->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(send_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
@@ -1745,9 +1746,9 @@ TEST_P(IPv4UDPUnboundSocketTest, ReuseAddrDistribution_NoRandomSave) {
// Verify that no other messages were received.
for (auto& socket : sockets) {
char recv_buf[kMessageSize] = {};
- EXPECT_THAT(RecvMsgTimeout(socket->get(), recv_buf, sizeof(recv_buf),
- 1 /*timeout*/),
- PosixErrorIs(EAGAIN, ::testing::_));
+ EXPECT_THAT(
+ RecvTimeout(socket->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ PosixErrorIs(EAGAIN, ::testing::_));
}
}
@@ -2108,6 +2109,9 @@ TEST_P(IPv4UDPUnboundSocketTest, ReuseAddrReusePortDistribution) {
constexpr int kMessageSize = 10;
+ // Saving during each iteration of the following loop is too expensive.
+ DisableSave ds;
+
for (int i = 0; i < 100; ++i) {
// Send a new message to the REUSEADDR/REUSEPORT group. We use a new socket
// each time so that a new ephemerial port will be used each time. This
@@ -2120,16 +2124,18 @@ TEST_P(IPv4UDPUnboundSocketTest, ReuseAddrReusePortDistribution) {
SyscallSucceedsWithValue(sizeof(send_buf)));
}
+ ds.reset();
+
// Check that both receivers got messages. This checks that we are using load
// balancing (REUSEPORT) instead of the most recently bound socket
// (REUSEADDR).
char recv_buf[kMessageSize] = {};
- EXPECT_THAT(RecvMsgTimeout(receiver1->get(), recv_buf, sizeof(recv_buf),
- 1 /*timeout*/),
- IsPosixErrorOkAndHolds(kMessageSize));
- EXPECT_THAT(RecvMsgTimeout(receiver2->get(), recv_buf, sizeof(recv_buf),
- 1 /*timeout*/),
- IsPosixErrorOkAndHolds(kMessageSize));
+ EXPECT_THAT(
+ RecvTimeout(receiver1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ IsPosixErrorOkAndHolds(kMessageSize));
+ EXPECT_THAT(
+ RecvTimeout(receiver2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ IsPosixErrorOkAndHolds(kMessageSize));
}
// Test that socket will receive packet info control message.
@@ -2193,8 +2199,8 @@ TEST_P(IPv4UDPUnboundSocketTest, SetAndReceiveIPPKTINFO) {
received_msg.msg_controllen = CMSG_LEN(cmsg_data_len);
received_msg.msg_control = received_cmsg_buf;
- ASSERT_THAT(RetryEINTR(recvmsg)(receiver->get(), &received_msg, 0),
- SyscallSucceedsWithValue(kDataLength));
+ ASSERT_THAT(RecvMsgTimeout(receiver->get(), &received_msg, 1 /*timeout*/),
+ IsPosixErrorOkAndHolds(kDataLength));
cmsghdr* cmsg = CMSG_FIRSTHDR(&received_msg);
ASSERT_NE(cmsg, nullptr);
@@ -2216,6 +2222,90 @@ TEST_P(IPv4UDPUnboundSocketTest, SetAndReceiveIPPKTINFO) {
EXPECT_EQ(received_pktinfo.ipi_addr.s_addr, htonl(INADDR_LOOPBACK));
}
+// Test that socket will receive IP_RECVORIGDSTADDR control message.
+TEST_P(IPv4UDPUnboundSocketTest, SetAndReceiveIPReceiveOrigDstAddr) {
+ auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
+ auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
+ auto receiver_addr = V4Loopback();
+ int level = SOL_IP;
+ int type = IP_RECVORIGDSTADDR;
+
+ ASSERT_THAT(
+ bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr),
+ receiver_addr.addr_len),
+ SyscallSucceeds());
+
+ // Retrieve the port bound by the receiver.
+ socklen_t receiver_addr_len = receiver_addr.addr_len;
+ ASSERT_THAT(getsockname(receiver->get(),
+ reinterpret_cast<sockaddr*>(&receiver_addr.addr),
+ &receiver_addr_len),
+ SyscallSucceeds());
+ EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
+
+ ASSERT_THAT(
+ connect(sender->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr),
+ receiver_addr.addr_len),
+ SyscallSucceeds());
+
+ // Get address and port bound by the sender.
+ sockaddr_storage sender_addr_storage;
+ socklen_t sender_addr_len = sizeof(sender_addr_storage);
+ ASSERT_THAT(getsockname(sender->get(),
+ reinterpret_cast<sockaddr*>(&sender_addr_storage),
+ &sender_addr_len),
+ SyscallSucceeds());
+ ASSERT_EQ(sender_addr_len, sizeof(struct sockaddr_in));
+
+ // Enable IP_RECVORIGDSTADDR on socket so that we get the original destination
+ // address of the datagram as auxiliary information in the control message.
+ ASSERT_THAT(
+ setsockopt(receiver->get(), level, type, &kSockOptOn, sizeof(kSockOptOn)),
+ SyscallSucceeds());
+
+ // Prepare message to send.
+ constexpr size_t kDataLength = 1024;
+ msghdr sent_msg = {};
+ iovec sent_iov = {};
+ char sent_data[kDataLength];
+ sent_iov.iov_base = sent_data;
+ sent_iov.iov_len = kDataLength;
+ sent_msg.msg_iov = &sent_iov;
+ sent_msg.msg_iovlen = 1;
+ sent_msg.msg_flags = 0;
+
+ ASSERT_THAT(RetryEINTR(sendmsg)(sender->get(), &sent_msg, 0),
+ SyscallSucceedsWithValue(kDataLength));
+
+ msghdr received_msg = {};
+ iovec received_iov = {};
+ char received_data[kDataLength];
+ char received_cmsg_buf[CMSG_SPACE(sizeof(sockaddr_in))] = {};
+ size_t cmsg_data_len = sizeof(sockaddr_in);
+ received_iov.iov_base = received_data;
+ received_iov.iov_len = kDataLength;
+ received_msg.msg_iov = &received_iov;
+ received_msg.msg_iovlen = 1;
+ received_msg.msg_controllen = CMSG_LEN(cmsg_data_len);
+ received_msg.msg_control = received_cmsg_buf;
+
+ ASSERT_THAT(RecvMsgTimeout(receiver->get(), &received_msg, 1 /*timeout*/),
+ IsPosixErrorOkAndHolds(kDataLength));
+
+ cmsghdr* cmsg = CMSG_FIRSTHDR(&received_msg);
+ ASSERT_NE(cmsg, nullptr);
+ EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(cmsg_data_len));
+ EXPECT_EQ(cmsg->cmsg_level, level);
+ EXPECT_EQ(cmsg->cmsg_type, type);
+
+ // Check the data
+ sockaddr_in received_addr = {};
+ memcpy(&received_addr, CMSG_DATA(cmsg), sizeof(received_addr));
+ auto orig_receiver_addr = reinterpret_cast<sockaddr_in*>(&receiver_addr.addr);
+ EXPECT_EQ(received_addr.sin_addr.s_addr, orig_receiver_addr->sin_addr.s_addr);
+ EXPECT_EQ(received_addr.sin_port, orig_receiver_addr->sin_port);
+}
+
// Check that setting SO_RCVBUF below min is clamped to the minimum
// receive buffer size.
TEST_P(IPv4UDPUnboundSocketTest, SetSocketRecvBufBelowMin) {
diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc
index b206137eb..2eecb0866 100644
--- a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc
+++ b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc
@@ -76,38 +76,6 @@ void IPv4UDPUnboundExternalNetworkingSocketTest::SetUp() {
found_net_interfaces_ = true;
}
-// Verifies that a newly instantiated UDP socket does not have the
-// broadcast socket option enabled.
-TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, UDPBroadcastDefault) {
- auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- int get = -1;
- socklen_t get_sz = sizeof(get);
- EXPECT_THAT(
- getsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &get, &get_sz),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get, kSockOptOff);
- EXPECT_EQ(get_sz, sizeof(get));
-}
-
-// Verifies that a newly instantiated UDP socket returns true after enabling
-// the broadcast socket option.
-TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, SetUDPBroadcast) {
- auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
-
- EXPECT_THAT(setsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn,
- sizeof(kSockOptOn)),
- SyscallSucceedsWithValue(0));
-
- int get = -1;
- socklen_t get_sz = sizeof(get);
- EXPECT_THAT(
- getsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &get, &get_sz),
- SyscallSucceedsWithValue(0));
- EXPECT_EQ(get, kSockOptOn);
- EXPECT_EQ(get_sz, sizeof(get));
-}
-
// Verifies that a broadcast UDP packet will arrive at all UDP sockets with
// the destination port number.
TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest,
diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc
index 49a0f06d9..875016812 100644
--- a/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc
+++ b/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc
@@ -40,17 +40,9 @@ TEST_P(IPv4UDPUnboundSocketNetlinkTest, JoinSubnet) {
/*prefixlen=*/24, &addr, sizeof(addr)));
Cleanup defer_addr_removal = Cleanup(
[loopback_link = std::move(loopback_link), addr = std::move(addr)] {
- if (IsRunningOnGvisor()) {
- // TODO(gvisor.dev/issue/3921): Remove this once deleting addresses
- // via netlink is supported.
- EXPECT_THAT(LinkDelLocalAddr(loopback_link.index, AF_INET,
- /*prefixlen=*/24, &addr, sizeof(addr)),
- PosixErrorIs(EOPNOTSUPP, ::testing::_));
- } else {
- EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET,
- /*prefixlen=*/24, &addr,
- sizeof(addr)));
- }
+ EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET,
+ /*prefixlen=*/24, &addr,
+ sizeof(addr)));
});
auto snd_sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
@@ -124,17 +116,9 @@ TEST_P(IPv4UDPUnboundSocketNetlinkTest, ReuseAddrSubnetDirectedBroadcast) {
24 /* prefixlen */, &addr, sizeof(addr)));
Cleanup defer_addr_removal = Cleanup(
[loopback_link = std::move(loopback_link), addr = std::move(addr)] {
- if (IsRunningOnGvisor()) {
- // TODO(gvisor.dev/issue/3921): Remove this once deleting addresses
- // via netlink is supported.
- EXPECT_THAT(LinkDelLocalAddr(loopback_link.index, AF_INET,
- /*prefixlen=*/24, &addr, sizeof(addr)),
- PosixErrorIs(EOPNOTSUPP, ::testing::_));
- } else {
- EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET,
- /*prefixlen=*/24, &addr,
- sizeof(addr)));
- }
+ EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET,
+ /*prefixlen=*/24, &addr,
+ sizeof(addr)));
});
TestAddress broadcast_address("SubnetBroadcastAddress");
diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound.cc b/test/syscalls/linux/socket_ipv6_udp_unbound.cc
new file mode 100644
index 000000000..08526468e
--- /dev/null
+++ b/test/syscalls/linux/socket_ipv6_udp_unbound.cc
@@ -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.
+
+#include "test/syscalls/linux/socket_ipv6_udp_unbound.h"
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#ifdef __linux__
+#include <linux/in6.h>
+#endif // __linux__
+#include <net/if.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <cstdio>
+#include <cstring>
+
+#include "gtest/gtest.h"
+#include "absl/memory/memory.h"
+#include "test/syscalls/linux/ip_socket_test_util.h"
+#include "test/syscalls/linux/socket_test_util.h"
+#include "test/util/posix_error.h"
+#include "test/util/save_util.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+// Test that socket will receive IP_RECVORIGDSTADDR control message.
+TEST_P(IPv6UDPUnboundSocketTest, SetAndReceiveIPReceiveOrigDstAddr) {
+ auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
+ auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
+ auto receiver_addr = V6Loopback();
+ int level = SOL_IPV6;
+ int type = IPV6_RECVORIGDSTADDR;
+
+ ASSERT_THAT(
+ bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr),
+ receiver_addr.addr_len),
+ SyscallSucceeds());
+
+ // Retrieve the port bound by the receiver.
+ socklen_t receiver_addr_len = receiver_addr.addr_len;
+ ASSERT_THAT(getsockname(receiver->get(),
+ reinterpret_cast<sockaddr*>(&receiver_addr.addr),
+ &receiver_addr_len),
+ SyscallSucceeds());
+ EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
+
+ ASSERT_THAT(
+ connect(sender->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr),
+ receiver_addr.addr_len),
+ SyscallSucceeds());
+
+ // Get address and port bound by the sender.
+ sockaddr_storage sender_addr_storage;
+ socklen_t sender_addr_len = sizeof(sender_addr_storage);
+ ASSERT_THAT(getsockname(sender->get(),
+ reinterpret_cast<sockaddr*>(&sender_addr_storage),
+ &sender_addr_len),
+ SyscallSucceeds());
+ ASSERT_EQ(sender_addr_len, sizeof(struct sockaddr_in6));
+
+ // Enable IP_RECVORIGDSTADDR on socket so that we get the original destination
+ // address of the datagram as auxiliary information in the control message.
+ ASSERT_THAT(
+ setsockopt(receiver->get(), level, type, &kSockOptOn, sizeof(kSockOptOn)),
+ SyscallSucceeds());
+
+ // Prepare message to send.
+ constexpr size_t kDataLength = 1024;
+ msghdr sent_msg = {};
+ iovec sent_iov = {};
+ char sent_data[kDataLength];
+ sent_iov.iov_base = sent_data;
+ sent_iov.iov_len = kDataLength;
+ sent_msg.msg_iov = &sent_iov;
+ sent_msg.msg_iovlen = 1;
+ sent_msg.msg_flags = 0;
+
+ ASSERT_THAT(RetryEINTR(sendmsg)(sender->get(), &sent_msg, 0),
+ SyscallSucceedsWithValue(kDataLength));
+
+ msghdr received_msg = {};
+ iovec received_iov = {};
+ char received_data[kDataLength];
+ char received_cmsg_buf[CMSG_SPACE(sizeof(sockaddr_in6))] = {};
+ size_t cmsg_data_len = sizeof(sockaddr_in6);
+ received_iov.iov_base = received_data;
+ received_iov.iov_len = kDataLength;
+ received_msg.msg_iov = &received_iov;
+ received_msg.msg_iovlen = 1;
+ received_msg.msg_controllen = CMSG_LEN(cmsg_data_len);
+ received_msg.msg_control = received_cmsg_buf;
+
+ ASSERT_THAT(RecvMsgTimeout(receiver->get(), &received_msg, 1 /*timeout*/),
+ IsPosixErrorOkAndHolds(kDataLength));
+
+ cmsghdr* cmsg = CMSG_FIRSTHDR(&received_msg);
+ ASSERT_NE(cmsg, nullptr);
+ EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(cmsg_data_len));
+ EXPECT_EQ(cmsg->cmsg_level, level);
+ EXPECT_EQ(cmsg->cmsg_type, type);
+
+ // Check that the received address in the control message matches the expected
+ // receiver's address.
+ sockaddr_in6 received_addr = {};
+ memcpy(&received_addr, CMSG_DATA(cmsg), sizeof(received_addr));
+ auto orig_receiver_addr =
+ reinterpret_cast<sockaddr_in6*>(&receiver_addr.addr);
+ EXPECT_EQ(memcmp(&received_addr.sin6_addr, &orig_receiver_addr->sin6_addr,
+ sizeof(in6_addr)),
+ 0);
+ EXPECT_EQ(received_addr.sin6_port, orig_receiver_addr->sin6_port);
+}
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h b/test/syscalls/linux/socket_ipv6_udp_unbound.h
index fb582b224..71e160f73 100644
--- a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h
+++ b/test/syscalls/linux/socket_ipv6_udp_unbound.h
@@ -1,4 +1,4 @@
-// Copyright 2019 The gVisor Authors.
+// 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.
@@ -12,19 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_TCP_UNBOUND_EXTERNAL_NETWORKING_H_
-#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_TCP_UNBOUND_EXTERNAL_NETWORKING_H_
+#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_H_
+#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_H_
#include "test/syscalls/linux/socket_test_util.h"
namespace gvisor {
namespace testing {
-// Test fixture for tests that apply to unbound IPv4 TCP sockets in a sandbox
-// with external networking support.
-using IPv4TCPUnboundExternalNetworkingSocketTest = SimpleSocketTest;
+// Test fixture for tests that apply to IPv6 UDP sockets.
+using IPv6UDPUnboundSocketTest = SimpleSocketTest;
} // namespace testing
} // namespace gvisor
-#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_TCP_UNBOUND_EXTERNAL_NETWORKING_H_
+#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_H_
diff --git a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking_test.cc b/test/syscalls/linux/socket_ipv6_udp_unbound_loopback.cc
index 797c4174e..058336ecc 100644
--- a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking_test.cc
+++ b/test/syscalls/linux/socket_ipv6_udp_unbound_loopback.cc
@@ -1,4 +1,4 @@
-// Copyright 2019 The gVisor Authors.
+// 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.
@@ -12,28 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h"
-
#include <vector>
#include "test/syscalls/linux/ip_socket_test_util.h"
+#include "test/syscalls/linux/socket_ipv6_udp_unbound.h"
#include "test/syscalls/linux/socket_test_util.h"
#include "test/util/test_util.h"
namespace gvisor {
namespace testing {
-namespace {
-
-std::vector<SocketKind> GetSockets() {
- return ApplyVec<SocketKind>(
- IPv4TCPUnboundSocket,
- AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK}));
-}
-INSTANTIATE_TEST_SUITE_P(IPv4TCPUnboundSockets,
- IPv4TCPUnboundExternalNetworkingSocketTest,
- ::testing::ValuesIn(GetSockets()));
+INSTANTIATE_TEST_SUITE_P(
+ IPv6UDPSockets, IPv6UDPUnboundSocketTest,
+ ::testing::ValuesIn(ApplyVec<SocketKind>(IPv6UDPUnboundSocket,
+ AllBitwiseCombinations(List<int>{
+ 0, SOCK_NONBLOCK}))));
-} // namespace
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/socket_netlink_route.cc b/test/syscalls/linux/socket_netlink_route.cc
index 241ddad74..ee3c08770 100644
--- a/test/syscalls/linux/socket_netlink_route.cc
+++ b/test/syscalls/linux/socket_netlink_route.cc
@@ -511,53 +511,42 @@ TEST(NetlinkRouteTest, LookupAll) {
ASSERT_GT(count, 0);
}
-TEST(NetlinkRouteTest, AddAddr) {
+TEST(NetlinkRouteTest, AddAndRemoveAddr) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)));
+ // Don't do cooperative save/restore because netstack state is not restored.
+ // TODO(gvisor.dev/issue/4595): enable cooperative save tests.
+ const DisableSave ds;
Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink());
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
-
- struct request {
- struct nlmsghdr hdr;
- struct ifaddrmsg ifa;
- struct rtattr rtattr;
- struct in_addr addr;
- char pad[NLMSG_ALIGNTO + RTA_ALIGNTO];
- };
-
- struct request req = {};
- req.hdr.nlmsg_type = RTM_NEWADDR;
- req.hdr.nlmsg_seq = kSeq;
- req.ifa.ifa_family = AF_INET;
- req.ifa.ifa_prefixlen = 24;
- req.ifa.ifa_flags = 0;
- req.ifa.ifa_scope = 0;
- req.ifa.ifa_index = loopback_link.index;
- req.rtattr.rta_type = IFA_LOCAL;
- req.rtattr.rta_len = RTA_LENGTH(sizeof(req.addr));
- inet_pton(AF_INET, "10.0.0.1", &req.addr);
- req.hdr.nlmsg_len =
- NLMSG_LENGTH(sizeof(req.ifa)) + NLMSG_ALIGN(req.rtattr.rta_len);
+ struct in_addr addr;
+ ASSERT_EQ(inet_pton(AF_INET, "10.0.0.1", &addr), 1);
// Create should succeed, as no such address in kernel.
- req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK;
- EXPECT_NO_ERRNO(
- NetlinkRequestAckOrError(fd, req.hdr.nlmsg_seq, &req, req.hdr.nlmsg_len));
+ ASSERT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET,
+ /*prefixlen=*/24, &addr, sizeof(addr)));
+
+ Cleanup defer_addr_removal = Cleanup(
+ [loopback_link = std::move(loopback_link), addr = std::move(addr)] {
+ // First delete should succeed, as address exists.
+ EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET,
+ /*prefixlen=*/24, &addr,
+ sizeof(addr)));
+
+ // Second delete should fail, as address no longer exists.
+ EXPECT_THAT(LinkDelLocalAddr(loopback_link.index, AF_INET,
+ /*prefixlen=*/24, &addr, sizeof(addr)),
+ PosixErrorIs(EADDRNOTAVAIL, ::testing::_));
+ });
// Replace an existing address should succeed.
- req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_ACK;
- req.hdr.nlmsg_seq++;
- EXPECT_NO_ERRNO(
- NetlinkRequestAckOrError(fd, req.hdr.nlmsg_seq, &req, req.hdr.nlmsg_len));
+ ASSERT_NO_ERRNO(LinkReplaceLocalAddr(loopback_link.index, AF_INET,
+ /*prefixlen=*/24, &addr, sizeof(addr)));
// Create exclusive should fail, as we created the address above.
- req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK;
- req.hdr.nlmsg_seq++;
- EXPECT_THAT(
- NetlinkRequestAckOrError(fd, req.hdr.nlmsg_seq, &req, req.hdr.nlmsg_len),
- PosixErrorIs(EEXIST, ::testing::_));
+ EXPECT_THAT(LinkAddExclusiveLocalAddr(loopback_link.index, AF_INET,
+ /*prefixlen=*/24, &addr, sizeof(addr)),
+ PosixErrorIs(EEXIST, ::testing::_));
}
// GetRouteDump tests a RTM_GETROUTE + NLM_F_DUMP request.
diff --git a/test/syscalls/linux/socket_netlink_route_util.cc b/test/syscalls/linux/socket_netlink_route_util.cc
index 7a0bad4cb..46f749c7c 100644
--- a/test/syscalls/linux/socket_netlink_route_util.cc
+++ b/test/syscalls/linux/socket_netlink_route_util.cc
@@ -29,6 +29,8 @@ constexpr uint32_t kSeq = 12345;
// Types of address modifications that may be performed on an interface.
enum class LinkAddrModification {
kAdd,
+ kAddExclusive,
+ kReplace,
kDelete,
};
@@ -40,6 +42,14 @@ PosixError PopulateNlmsghdr(LinkAddrModification modification,
hdr->nlmsg_type = RTM_NEWADDR;
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
return NoError();
+ case LinkAddrModification::kAddExclusive:
+ hdr->nlmsg_type = RTM_NEWADDR;
+ hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_ACK;
+ return NoError();
+ case LinkAddrModification::kReplace:
+ hdr->nlmsg_type = RTM_NEWADDR;
+ hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_ACK;
+ return NoError();
case LinkAddrModification::kDelete:
hdr->nlmsg_type = RTM_DELADDR;
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
@@ -144,6 +154,18 @@ PosixError LinkAddLocalAddr(int index, int family, int prefixlen,
LinkAddrModification::kAdd);
}
+PosixError LinkAddExclusiveLocalAddr(int index, int family, int prefixlen,
+ const void* addr, int addrlen) {
+ return LinkModifyLocalAddr(index, family, prefixlen, addr, addrlen,
+ LinkAddrModification::kAddExclusive);
+}
+
+PosixError LinkReplaceLocalAddr(int index, int family, int prefixlen,
+ const void* addr, int addrlen) {
+ return LinkModifyLocalAddr(index, family, prefixlen, addr, addrlen,
+ LinkAddrModification::kReplace);
+}
+
PosixError LinkDelLocalAddr(int index, int family, int prefixlen,
const void* addr, int addrlen) {
return LinkModifyLocalAddr(index, family, prefixlen, addr, addrlen,
diff --git a/test/syscalls/linux/socket_netlink_route_util.h b/test/syscalls/linux/socket_netlink_route_util.h
index e5badca70..eaa91ad79 100644
--- a/test/syscalls/linux/socket_netlink_route_util.h
+++ b/test/syscalls/linux/socket_netlink_route_util.h
@@ -39,10 +39,19 @@ PosixErrorOr<std::vector<Link>> DumpLinks();
// Returns the loopback link on the system. ENOENT if not found.
PosixErrorOr<Link> LoopbackLink();
-// LinkAddLocalAddr sets IFA_LOCAL attribute on the interface.
+// LinkAddLocalAddr adds a new IFA_LOCAL address to the interface.
PosixError LinkAddLocalAddr(int index, int family, int prefixlen,
const void* addr, int addrlen);
+// LinkAddExclusiveLocalAddr adds a new IFA_LOCAL address with NLM_F_EXCL flag
+// to the interface.
+PosixError LinkAddExclusiveLocalAddr(int index, int family, int prefixlen,
+ const void* addr, int addrlen);
+
+// LinkReplaceLocalAddr replaces an IFA_LOCAL address on the interface.
+PosixError LinkReplaceLocalAddr(int index, int family, int prefixlen,
+ const void* addr, int addrlen);
+
// LinkDelLocalAddr removes IFA_LOCAL attribute on the interface.
PosixError LinkDelLocalAddr(int index, int family, int prefixlen,
const void* addr, int addrlen);
diff --git a/test/syscalls/linux/socket_test_util.cc b/test/syscalls/linux/socket_test_util.cc
index e11792309..a760581b5 100644
--- a/test/syscalls/linux/socket_test_util.cc
+++ b/test/syscalls/linux/socket_test_util.cc
@@ -753,8 +753,7 @@ PosixErrorOr<int> SendMsg(int sock, msghdr* msg, char buf[], int buf_size) {
return ret;
}
-PosixErrorOr<int> RecvMsgTimeout(int sock, char buf[], int buf_size,
- int timeout) {
+PosixErrorOr<int> RecvTimeout(int sock, char buf[], int buf_size, int timeout) {
fd_set rfd;
struct timeval to = {.tv_sec = timeout, .tv_usec = 0};
FD_ZERO(&rfd);
@@ -767,6 +766,19 @@ PosixErrorOr<int> RecvMsgTimeout(int sock, char buf[], int buf_size,
return ret;
}
+PosixErrorOr<int> RecvMsgTimeout(int sock, struct msghdr* msg, int timeout) {
+ fd_set rfd;
+ struct timeval to = {.tv_sec = timeout, .tv_usec = 0};
+ FD_ZERO(&rfd);
+ FD_SET(sock, &rfd);
+
+ int ret;
+ RETURN_ERROR_IF_SYSCALL_FAIL(ret = select(1, &rfd, NULL, NULL, &to));
+ RETURN_ERROR_IF_SYSCALL_FAIL(
+ ret = RetryEINTR(recvmsg)(sock, msg, MSG_DONTWAIT));
+ return ret;
+}
+
void RecvNoData(int sock) {
char data = 0;
struct iovec iov;
diff --git a/test/syscalls/linux/socket_test_util.h b/test/syscalls/linux/socket_test_util.h
index 468bc96e0..5e205339f 100644
--- a/test/syscalls/linux/socket_test_util.h
+++ b/test/syscalls/linux/socket_test_util.h
@@ -467,9 +467,12 @@ PosixError FreeAvailablePort(int port);
// SendMsg converts a buffer to an iovec and adds it to msg before sending it.
PosixErrorOr<int> SendMsg(int sock, msghdr* msg, char buf[], int buf_size);
-// RecvMsgTimeout calls select on sock with timeout and then calls recv on sock.
-PosixErrorOr<int> RecvMsgTimeout(int sock, char buf[], int buf_size,
- int timeout);
+// RecvTimeout calls select on sock with timeout and then calls recv on sock.
+PosixErrorOr<int> RecvTimeout(int sock, char buf[], int buf_size, int timeout);
+
+// RecvMsgTimeout calls select on sock with timeout and then calls recvmsg on
+// sock.
+PosixErrorOr<int> RecvMsgTimeout(int sock, msghdr* msg, int timeout);
// RecvNoData checks that no data is receivable on sock.
void RecvNoData(int sock);
diff --git a/test/syscalls/linux/socket_unix_stream.cc b/test/syscalls/linux/socket_unix_stream.cc
index 1edcb15a7..ad9c4bf37 100644
--- a/test/syscalls/linux/socket_unix_stream.cc
+++ b/test/syscalls/linux/socket_unix_stream.cc
@@ -121,6 +121,19 @@ TEST_P(StreamUnixSocketPairTest, SetAndGetSocketLinger) {
EXPECT_EQ(0, memcmp(&got_linger, &sl, length));
}
+TEST_P(StreamUnixSocketPairTest, GetSocketAcceptConn) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+
+ int got = -1;
+ socklen_t length = sizeof(got);
+ ASSERT_THAT(
+ getsockopt(sockets->first_fd(), SOL_SOCKET, SO_ACCEPTCONN, &got, &length),
+ SyscallSucceedsWithValue(0));
+
+ ASSERT_EQ(length, sizeof(got));
+ EXPECT_EQ(got, 0);
+}
+
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 a1d2b9b11..c2369db54 100644
--- a/test/syscalls/linux/splice.cc
+++ b/test/syscalls/linux/splice.cc
@@ -26,9 +26,11 @@
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "test/util/file_descriptor.h"
+#include "test/util/signal_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"
namespace gvisor {
namespace testing {
@@ -772,6 +774,59 @@ TEST(SpliceTest, FromPipeToDevZero) {
SyscallSucceedsWithValue(0));
}
+static volatile int signaled = 0;
+void SigUsr1Handler(int sig, siginfo_t* info, void* context) { signaled = 1; }
+
+TEST(SpliceTest, ToPipeWithSmallCapacityDoesNotSpin_NoRandomSave) {
+ // Writes to a pipe that are less than PIPE_BUF must be atomic. This test
+ // creates a pipe with only 128 bytes of capacity (< PIPE_BUF) and checks that
+ // splicing to the pipe does not spin. See b/170743336.
+
+ // Create a file with one page of data.
+ std::vector<char> buf(kPageSize);
+ RandomizeBuffer(buf.data(), buf.size());
+ auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
+ GetAbsoluteTestTmpdir(), absl::string_view(buf.data(), buf.size()),
+ TempPath::kDefaultFileMode));
+ auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));
+
+ // Create a pipe with size 4096, and fill all but 128 bytes of it.
+ int p[2];
+ ASSERT_THAT(pipe(p), SyscallSucceeds());
+ ASSERT_THAT(fcntl(p[1], F_SETPIPE_SZ, kPageSize), SyscallSucceeds());
+ const int kWriteSize = kPageSize - 128;
+ std::vector<char> writeBuf(kWriteSize);
+ RandomizeBuffer(writeBuf.data(), writeBuf.size());
+ ASSERT_THAT(write(p[1], writeBuf.data(), writeBuf.size()),
+ SyscallSucceedsWithValue(kWriteSize));
+
+ // Set up signal handler.
+ struct sigaction sa = {};
+ sa.sa_sigaction = SigUsr1Handler;
+ sa.sa_flags = SA_SIGINFO;
+ const auto cleanup_sigact =
+ ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGUSR1, sa));
+
+ // Send SIGUSR1 to this thread in 1 second.
+ struct sigevent sev = {};
+ sev.sigev_notify = SIGEV_THREAD_ID;
+ sev.sigev_signo = SIGUSR1;
+ sev.sigev_notify_thread_id = gettid();
+ auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev));
+ struct itimerspec its = {};
+ its.it_value = absl::ToTimespec(absl::Seconds(1));
+ DisableSave ds; // Asserting an EINTR.
+ ASSERT_NO_ERRNO(timer.Set(0, its));
+
+ // Now splice the file to the pipe. This should block, but not spin, and
+ // should return EINTR because it is interrupted by the signal.
+ EXPECT_THAT(splice(fd.get(), nullptr, p[1], nullptr, kPageSize, 0),
+ SyscallFailsWithErrno(EINTR));
+
+ // Alarm should have been handled.
+ EXPECT_EQ(signaled, 1);
+}
+
} // namespace
} // namespace testing
diff --git a/test/syscalls/linux/stat.cc b/test/syscalls/linux/stat.cc
index 92260b1e1..6e7142a42 100644
--- a/test/syscalls/linux/stat.cc
+++ b/test/syscalls/linux/stat.cc
@@ -31,6 +31,7 @@
#include "test/util/cleanup.h"
#include "test/util/file_descriptor.h"
#include "test/util/fs_util.h"
+#include "test/util/save_util.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
@@ -328,7 +329,10 @@ TEST_F(StatTest, LeadingDoubleSlash) {
ASSERT_THAT(lstat(double_slash_path.c_str(), &double_slash_st),
SyscallSucceeds());
EXPECT_EQ(st.st_dev, double_slash_st.st_dev);
- EXPECT_EQ(st.st_ino, double_slash_st.st_ino);
+ // Inode numbers for gofer-accessed files may change across save/restore.
+ if (!IsRunningWithSaveRestore()) {
+ EXPECT_EQ(st.st_ino, double_slash_st.st_ino);
+ }
}
// Test that a rename doesn't change the underlying file.
@@ -346,8 +350,14 @@ TEST_F(StatTest, StatDoesntChangeAfterRename) {
EXPECT_EQ(st_old.st_nlink, st_new.st_nlink);
EXPECT_EQ(st_old.st_dev, st_new.st_dev);
+ // Inode numbers for gofer-accessed files on which no reference is held may
+ // change across save/restore because the information that the gofer client
+ // uses to track file identity (9P QID path) is inconsistent between gofer
+ // processes, which are restarted across save/restore.
+ //
// Overlay filesystems may synthesize directory inode numbers on the fly.
- if (!ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(GetAbsoluteTestTmpdir()))) {
+ if (!IsRunningWithSaveRestore() &&
+ !ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(GetAbsoluteTestTmpdir()))) {
EXPECT_EQ(st_old.st_ino, st_new.st_ino);
}
EXPECT_EQ(st_old.st_mode, st_new.st_mode);
@@ -541,6 +551,26 @@ TEST_F(StatTest, LstatELOOPPath) {
ASSERT_THAT(lstat(path.c_str(), &s), SyscallFailsWithErrno(ELOOP));
}
+TEST(SimpleStatTest, DifferentFilesHaveDifferentDeviceInodeNumberPairs) {
+ // TODO(gvisor.dev/issue/1624): This test case fails in VFS1 save/restore
+ // tests because VFS1 gofer inode number assignment restarts after
+ // save/restore, such that the inodes for file1 and file2 (which are
+ // unreferenced and therefore not retained in sentry checkpoints before the
+ // calls to lstat()) are assigned the same inode number.
+ SKIP_IF(IsRunningWithVFS1() && IsRunningWithSaveRestore());
+
+ TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ TempPath file2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+
+ MaybeSave();
+ struct stat st1 = ASSERT_NO_ERRNO_AND_VALUE(Lstat(file1.path()));
+ MaybeSave();
+ struct stat st2 = ASSERT_NO_ERRNO_AND_VALUE(Lstat(file2.path()));
+ EXPECT_FALSE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino)
+ << "both files have device number " << st1.st_dev << " and inode number "
+ << st1.st_ino;
+}
+
// Ensure that inode allocation for anonymous devices work correctly across
// save/restore. In particular, inode numbers should be unique across S/R.
TEST(SimpleStatTest, AnonDeviceAllocatesUniqueInodesAcrossSaveRestore) {
diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc
index 9f522f833..714848b8e 100644
--- a/test/syscalls/linux/tcp_socket.cc
+++ b/test/syscalls/linux/tcp_socket.cc
@@ -60,6 +60,48 @@ PosixErrorOr<sockaddr_storage> InetLoopbackAddr(int family) {
return addr;
}
+static void FillSocketBuffers(int sender, int receiver) {
+ // Set the FD to O_NONBLOCK.
+ int opts;
+ int orig_opts;
+ ASSERT_THAT(opts = fcntl(sender, F_GETFL), SyscallSucceeds());
+ orig_opts = opts;
+ opts |= O_NONBLOCK;
+ ASSERT_THAT(fcntl(sender, F_SETFL, opts), SyscallSucceeds());
+
+ // Set TCP_NODELAY, which will cause linux to fill the receive buffer from the
+ // send buffer as quickly as possibly. This way we can fill up both buffers
+ // faster.
+ constexpr int tcp_nodelay_flag = 1;
+ ASSERT_THAT(setsockopt(sender, IPPROTO_TCP, TCP_NODELAY, &tcp_nodelay_flag,
+ sizeof(tcp_nodelay_flag)),
+ SyscallSucceeds());
+
+ // Set a 256KB send/receive buffer.
+ int buf_sz = 1 << 18;
+ EXPECT_THAT(
+ setsockopt(receiver, SOL_SOCKET, SO_RCVBUF, &buf_sz, sizeof(buf_sz)),
+ SyscallSucceedsWithValue(0));
+ EXPECT_THAT(
+ setsockopt(sender, SOL_SOCKET, SO_SNDBUF, &buf_sz, sizeof(buf_sz)),
+ SyscallSucceedsWithValue(0));
+
+ // Create a large buffer that will be used for sending.
+ std::vector<char> buf(1 << 16);
+
+ // Write until we receive an error.
+ while (RetryEINTR(send)(sender, buf.data(), buf.size(), 0) != -1) {
+ // Sleep to give linux a chance to move data from the send buffer to the
+ // receive buffer.
+ usleep(10000); // 10ms.
+ }
+ // The last error should have been EWOULDBLOCK.
+ ASSERT_EQ(errno, EWOULDBLOCK);
+
+ // Restore the fcntl opts
+ ASSERT_THAT(fcntl(sender, F_SETFL, orig_opts), SyscallSucceeds());
+}
+
// Fixture for tests parameterized by the address family to use (AF_INET and
// AF_INET6) when creating sockets.
class TcpSocketTest : public ::testing::TestWithParam<int> {
@@ -75,10 +117,10 @@ class TcpSocketTest : public ::testing::TestWithParam<int> {
int listener_ = -1;
// Socket connected via connect().
- int s_ = -1;
+ int first_fd = -1;
// Socket connected via accept().
- int t_ = -1;
+ int second_fd = -1;
// Initial size of the send buffer.
int sendbuf_size_ = -1;
@@ -88,7 +130,7 @@ void TcpSocketTest::SetUp() {
ASSERT_THAT(listener_ = socket(GetParam(), SOCK_STREAM, IPPROTO_TCP),
SyscallSucceeds());
- ASSERT_THAT(s_ = socket(GetParam(), SOCK_STREAM, IPPROTO_TCP),
+ ASSERT_THAT(first_fd = socket(GetParam(), SOCK_STREAM, IPPROTO_TCP),
SyscallSucceeds());
// Initialize address to the loopback one.
@@ -109,27 +151,28 @@ void TcpSocketTest::SetUp() {
&addrlen),
SyscallSucceeds());
- ASSERT_THAT(RetryEINTR(connect)(s_, reinterpret_cast<struct sockaddr*>(&addr),
- addrlen),
+ ASSERT_THAT(RetryEINTR(connect)(
+ first_fd, reinterpret_cast<struct sockaddr*>(&addr), addrlen),
SyscallSucceeds());
// Get the initial send buffer size.
socklen_t optlen = sizeof(sendbuf_size_);
- ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &sendbuf_size_, &optlen),
- SyscallSucceeds());
+ ASSERT_THAT(
+ getsockopt(first_fd, SOL_SOCKET, SO_SNDBUF, &sendbuf_size_, &optlen),
+ SyscallSucceeds());
// Accept the connection.
- ASSERT_THAT(t_ = RetryEINTR(accept)(listener_, nullptr, nullptr),
+ ASSERT_THAT(second_fd = RetryEINTR(accept)(listener_, nullptr, nullptr),
SyscallSucceeds());
}
void TcpSocketTest::TearDown() {
EXPECT_THAT(close(listener_), SyscallSucceeds());
- if (s_ >= 0) {
- EXPECT_THAT(close(s_), SyscallSucceeds());
+ if (first_fd >= 0) {
+ EXPECT_THAT(close(first_fd), SyscallSucceeds());
}
- if (t_ >= 0) {
- EXPECT_THAT(close(t_), SyscallSucceeds());
+ if (second_fd >= 0) {
+ EXPECT_THAT(close(second_fd), SyscallSucceeds());
}
}
@@ -138,48 +181,48 @@ TEST_P(TcpSocketTest, ConnectOnEstablishedConnection) {
ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam()));
socklen_t addrlen = sizeof(addr);
- ASSERT_THAT(
- connect(s_, reinterpret_cast<const struct sockaddr*>(&addr), addrlen),
- SyscallFailsWithErrno(EISCONN));
- ASSERT_THAT(
- connect(t_, reinterpret_cast<const struct sockaddr*>(&addr), addrlen),
- SyscallFailsWithErrno(EISCONN));
+ ASSERT_THAT(connect(first_fd, reinterpret_cast<const struct sockaddr*>(&addr),
+ addrlen),
+ SyscallFailsWithErrno(EISCONN));
+ ASSERT_THAT(connect(second_fd,
+ reinterpret_cast<const struct sockaddr*>(&addr), addrlen),
+ SyscallFailsWithErrno(EISCONN));
}
TEST_P(TcpSocketTest, ShutdownWriteInTimeWait) {
- EXPECT_THAT(shutdown(t_, SHUT_WR), SyscallSucceeds());
- EXPECT_THAT(shutdown(s_, SHUT_RDWR), SyscallSucceeds());
+ EXPECT_THAT(shutdown(second_fd, SHUT_WR), SyscallSucceeds());
+ EXPECT_THAT(shutdown(first_fd, SHUT_RDWR), SyscallSucceeds());
absl::SleepFor(absl::Seconds(1)); // Wait to enter TIME_WAIT.
- EXPECT_THAT(shutdown(t_, SHUT_WR), SyscallFailsWithErrno(ENOTCONN));
+ EXPECT_THAT(shutdown(second_fd, SHUT_WR), SyscallFailsWithErrno(ENOTCONN));
}
TEST_P(TcpSocketTest, ShutdownWriteInFinWait1) {
- EXPECT_THAT(shutdown(t_, SHUT_WR), SyscallSucceeds());
- EXPECT_THAT(shutdown(t_, SHUT_WR), SyscallSucceeds());
+ EXPECT_THAT(shutdown(second_fd, SHUT_WR), SyscallSucceeds());
+ EXPECT_THAT(shutdown(second_fd, SHUT_WR), SyscallSucceeds());
absl::SleepFor(absl::Seconds(1)); // Wait to enter FIN-WAIT2.
- EXPECT_THAT(shutdown(t_, SHUT_WR), SyscallSucceeds());
+ EXPECT_THAT(shutdown(second_fd, SHUT_WR), SyscallSucceeds());
}
TEST_P(TcpSocketTest, DataCoalesced) {
char buf[10];
// Write in two steps.
- ASSERT_THAT(RetryEINTR(write)(s_, buf, sizeof(buf) / 2),
+ ASSERT_THAT(RetryEINTR(write)(first_fd, buf, sizeof(buf) / 2),
SyscallSucceedsWithValue(sizeof(buf) / 2));
- ASSERT_THAT(RetryEINTR(write)(s_, buf, sizeof(buf) / 2),
+ ASSERT_THAT(RetryEINTR(write)(first_fd, buf, sizeof(buf) / 2),
SyscallSucceedsWithValue(sizeof(buf) / 2));
// Allow stack to process both packets.
absl::SleepFor(absl::Seconds(1));
// Read in one shot.
- EXPECT_THAT(RetryEINTR(recv)(t_, buf, sizeof(buf), 0),
+ EXPECT_THAT(RetryEINTR(recv)(second_fd, buf, sizeof(buf), 0),
SyscallSucceedsWithValue(sizeof(buf)));
}
TEST_P(TcpSocketTest, SenderAddressIgnored) {
char buf[3];
- ASSERT_THAT(RetryEINTR(write)(s_, buf, sizeof(buf)),
+ ASSERT_THAT(RetryEINTR(write)(first_fd, buf, sizeof(buf)),
SyscallSucceedsWithValue(sizeof(buf)));
struct sockaddr_storage addr;
@@ -187,7 +230,7 @@ TEST_P(TcpSocketTest, SenderAddressIgnored) {
memset(&addr, 0, sizeof(addr));
ASSERT_THAT(
- RetryEINTR(recvfrom)(t_, buf, sizeof(buf), 0,
+ RetryEINTR(recvfrom)(second_fd, buf, sizeof(buf), 0,
reinterpret_cast<struct sockaddr*>(&addr), &addrlen),
SyscallSucceedsWithValue(3));
@@ -200,7 +243,7 @@ TEST_P(TcpSocketTest, SenderAddressIgnored) {
TEST_P(TcpSocketTest, SenderAddressIgnoredOnPeek) {
char buf[3];
- ASSERT_THAT(RetryEINTR(write)(s_, buf, sizeof(buf)),
+ ASSERT_THAT(RetryEINTR(write)(first_fd, buf, sizeof(buf)),
SyscallSucceedsWithValue(sizeof(buf)));
struct sockaddr_storage addr;
@@ -208,7 +251,7 @@ TEST_P(TcpSocketTest, SenderAddressIgnoredOnPeek) {
memset(&addr, 0, sizeof(addr));
ASSERT_THAT(
- RetryEINTR(recvfrom)(t_, buf, sizeof(buf), MSG_PEEK,
+ RetryEINTR(recvfrom)(second_fd, buf, sizeof(buf), MSG_PEEK,
reinterpret_cast<struct sockaddr*>(&addr), &addrlen),
SyscallSucceedsWithValue(3));
@@ -226,7 +269,7 @@ TEST_P(TcpSocketTest, SendtoAddressIgnored) {
char data = '\0';
EXPECT_THAT(
- RetryEINTR(sendto)(s_, &data, sizeof(data), 0,
+ RetryEINTR(sendto)(first_fd, &data, sizeof(data), 0,
reinterpret_cast<sockaddr*>(&addr), sizeof(addr)),
SyscallSucceedsWithValue(1));
}
@@ -244,9 +287,10 @@ TEST_P(TcpSocketTest, WritevZeroIovec) {
vecs[1].iov_base = buf + 1;
vecs[1].iov_len = 0;
- EXPECT_THAT(RetryEINTR(writev)(s_, vecs, 2), SyscallSucceedsWithValue(1));
+ EXPECT_THAT(RetryEINTR(writev)(first_fd, vecs, 2),
+ SyscallSucceedsWithValue(1));
- EXPECT_THAT(RetryEINTR(recv)(t_, recv_buf, 1, 0),
+ EXPECT_THAT(RetryEINTR(recv)(second_fd, recv_buf, 1, 0),
SyscallSucceedsWithValue(1));
EXPECT_EQ(memcmp(recv_buf, buf, 1), 0);
}
@@ -254,9 +298,9 @@ TEST_P(TcpSocketTest, WritevZeroIovec) {
TEST_P(TcpSocketTest, ZeroWriteAllowed) {
char buf[3];
// Send a zero length packet.
- ASSERT_THAT(RetryEINTR(write)(s_, buf, 0), SyscallSucceedsWithValue(0));
+ ASSERT_THAT(RetryEINTR(write)(first_fd, buf, 0), SyscallSucceedsWithValue(0));
// Verify that there is no packet available.
- EXPECT_THAT(RetryEINTR(recv)(t_, buf, sizeof(buf), MSG_DONTWAIT),
+ EXPECT_THAT(RetryEINTR(recv)(second_fd, buf, sizeof(buf), MSG_DONTWAIT),
SyscallFailsWithErrno(EAGAIN));
}
@@ -266,9 +310,9 @@ TEST_P(TcpSocketTest, ZeroWriteAllowed) {
TEST_P(TcpSocketTest, NonblockingLargeWrite) {
// Set the FD to O_NONBLOCK.
int opts;
- ASSERT_THAT(opts = fcntl(s_, F_GETFL), SyscallSucceeds());
+ ASSERT_THAT(opts = fcntl(first_fd, F_GETFL), SyscallSucceeds());
opts |= O_NONBLOCK;
- ASSERT_THAT(fcntl(s_, F_SETFL, opts), SyscallSucceeds());
+ ASSERT_THAT(fcntl(first_fd, F_SETFL, opts), SyscallSucceeds());
// Allocate a buffer three times the size of the send buffer. We do this with
// a vector to avoid allocating on the stack.
@@ -277,7 +321,8 @@ TEST_P(TcpSocketTest, NonblockingLargeWrite) {
// Try to write the whole thing.
int n;
- ASSERT_THAT(n = RetryEINTR(write)(s_, buf.data(), size), SyscallSucceeds());
+ ASSERT_THAT(n = RetryEINTR(write)(first_fd, buf.data(), size),
+ SyscallSucceeds());
// We should have written something, but not the whole thing.
EXPECT_GT(n, 0);
@@ -296,12 +341,12 @@ TEST_P(TcpSocketTest, BlockingLargeWrite_NoRandomSave) {
int read_bytes = 0;
ScopedThread t([this, &read_bytes]() {
// Avoid interrupting the blocking write in main thread.
- const DisableSave ds;
+ const DisableSave disable_save;
// Take ownership of the FD so that we close it on failure. This will
// unblock the blocking write below.
- FileDescriptor fd(t_);
- t_ = -1;
+ FileDescriptor fd(second_fd);
+ second_fd = -1;
char readbuf[2500] = {};
int n = -1;
@@ -314,12 +359,12 @@ TEST_P(TcpSocketTest, BlockingLargeWrite_NoRandomSave) {
// Try to write the whole thing.
int n;
- ASSERT_THAT(n = WriteFd(s_, writebuf.data(), size), SyscallSucceeds());
+ ASSERT_THAT(n = WriteFd(first_fd, writebuf.data(), size), SyscallSucceeds());
// We should have written the whole thing.
EXPECT_EQ(n, size);
- EXPECT_THAT(close(s_), SyscallSucceedsWithValue(0));
- s_ = -1;
+ EXPECT_THAT(close(first_fd), SyscallSucceedsWithValue(0));
+ first_fd = -1;
t.Join();
// We should have read the whole thing.
@@ -337,7 +382,7 @@ TEST_P(TcpSocketTest, LargeSendDontWait) {
// Try to write the whole thing with MSG_DONTWAIT flag, which can
// return a partial write.
int n;
- ASSERT_THAT(n = RetryEINTR(send)(s_, buf.data(), size, MSG_DONTWAIT),
+ ASSERT_THAT(n = RetryEINTR(send)(first_fd, buf.data(), size, MSG_DONTWAIT),
SyscallSucceeds());
// We should have written something, but not the whole thing.
@@ -350,9 +395,9 @@ TEST_P(TcpSocketTest, LargeSendDontWait) {
TEST_P(TcpSocketTest, NonblockingLargeSend) {
// Set the FD to O_NONBLOCK.
int opts;
- ASSERT_THAT(opts = fcntl(s_, F_GETFL), SyscallSucceeds());
+ ASSERT_THAT(opts = fcntl(first_fd, F_GETFL), SyscallSucceeds());
opts |= O_NONBLOCK;
- ASSERT_THAT(fcntl(s_, F_SETFL, opts), SyscallSucceeds());
+ ASSERT_THAT(fcntl(first_fd, F_SETFL, opts), SyscallSucceeds());
// Allocate a buffer three times the size of the send buffer. We do this on
// with a vector to avoid allocating on the stack.
@@ -361,7 +406,8 @@ TEST_P(TcpSocketTest, NonblockingLargeSend) {
// Try to write the whole thing.
int n;
- ASSERT_THAT(n = RetryEINTR(send)(s_, buf.data(), size, 0), SyscallSucceeds());
+ ASSERT_THAT(n = RetryEINTR(send)(first_fd, buf.data(), size, 0),
+ SyscallSucceeds());
// We should have written something, but not the whole thing.
EXPECT_GT(n, 0);
@@ -379,12 +425,12 @@ TEST_P(TcpSocketTest, BlockingLargeSend_NoRandomSave) {
int read_bytes = 0;
ScopedThread t([this, &read_bytes]() {
// Avoid interrupting the blocking write in main thread.
- const DisableSave ds;
+ const DisableSave disable_save;
// Take ownership of the FD so that we close it on failure. This will
// unblock the blocking write below.
- FileDescriptor fd(t_);
- t_ = -1;
+ FileDescriptor fd(second_fd);
+ second_fd = -1;
char readbuf[2500] = {};
int n = -1;
@@ -397,12 +443,13 @@ TEST_P(TcpSocketTest, BlockingLargeSend_NoRandomSave) {
// Try to send the whole thing.
int n;
- ASSERT_THAT(n = SendFd(s_, writebuf.data(), size, 0), SyscallSucceeds());
+ ASSERT_THAT(n = SendFd(first_fd, writebuf.data(), size, 0),
+ SyscallSucceeds());
// We should have written the whole thing.
EXPECT_EQ(n, size);
- EXPECT_THAT(close(s_), SyscallSucceedsWithValue(0));
- s_ = -1;
+ EXPECT_THAT(close(first_fd), SyscallSucceedsWithValue(0));
+ first_fd = -1;
t.Join();
// We should have read the whole thing.
@@ -411,54 +458,89 @@ TEST_P(TcpSocketTest, BlockingLargeSend_NoRandomSave) {
// Test that polling on a socket with a full send buffer will block.
TEST_P(TcpSocketTest, PollWithFullBufferBlocks) {
- // Set the FD to O_NONBLOCK.
- int opts;
- ASSERT_THAT(opts = fcntl(s_, F_GETFL), SyscallSucceeds());
- opts |= O_NONBLOCK;
- ASSERT_THAT(fcntl(s_, F_SETFL, opts), SyscallSucceeds());
+ FillSocketBuffers(first_fd, second_fd);
+ // Now polling on the FD with a timeout should return 0 corresponding to no
+ // FDs ready.
+ struct pollfd poll_fd = {first_fd, POLLOUT, 0};
+ EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10), SyscallSucceedsWithValue(0));
+}
- // Set TCP_NODELAY, which will cause linux to fill the receive buffer from the
- // send buffer as quickly as possibly. This way we can fill up both buffers
- // faster.
- constexpr int tcp_nodelay_flag = 1;
- ASSERT_THAT(setsockopt(s_, IPPROTO_TCP, TCP_NODELAY, &tcp_nodelay_flag,
- sizeof(tcp_nodelay_flag)),
+TEST_P(TcpSocketTest, ClosedWriteBlockingSocket) {
+ FillSocketBuffers(first_fd, second_fd);
+ constexpr int timeout = 10;
+ struct timeval tv = {.tv_sec = timeout, .tv_usec = 0};
+ EXPECT_THAT(setsockopt(first_fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)),
SyscallSucceeds());
- // Set a 256KB send/receive buffer.
- int buf_sz = 1 << 18;
- EXPECT_THAT(setsockopt(t_, SOL_SOCKET, SO_RCVBUF, &buf_sz, sizeof(buf_sz)),
- SyscallSucceedsWithValue(0));
- EXPECT_THAT(setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &buf_sz, sizeof(buf_sz)),
- SyscallSucceedsWithValue(0));
+ struct timespec begin;
+ struct timespec end;
+ const DisableSave disable_save; // Timing-related.
+ EXPECT_THAT(clock_gettime(CLOCK_MONOTONIC, &begin), SyscallSucceeds());
- // Create a large buffer that will be used for sending.
- std::vector<char> buf(1 << 16);
+ ScopedThread send_thread([this]() {
+ char send_byte;
+ // Expect the send() to be blocked until receive timeout.
+ ASSERT_THAT(RetryEINTR(send)(first_fd, &send_byte, sizeof(send_byte), 0),
+ SyscallFailsWithErrno(EAGAIN));
+ });
- // Write until we receive an error.
- while (RetryEINTR(send)(s_, buf.data(), buf.size(), 0) != -1) {
- // Sleep to give linux a chance to move data from the send buffer to the
- // receive buffer.
- usleep(10000); // 10ms.
- }
- // The last error should have been EWOULDBLOCK.
- ASSERT_EQ(errno, EWOULDBLOCK);
+ // Wait for the thread to be blocked on write.
+ absl::SleepFor(absl::Milliseconds(250));
+ // Socket close does not have any effect on a blocked write.
+ ASSERT_THAT(close(first_fd), SyscallSucceeds());
+ // Indicate to the cleanup routine that we are already closed.
+ first_fd = -1;
- // Now polling on the FD with a timeout should return 0 corresponding to no
- // FDs ready.
- struct pollfd poll_fd = {s_, POLLOUT, 0};
- EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10), SyscallSucceedsWithValue(0));
+ send_thread.Join();
+
+ EXPECT_THAT(clock_gettime(CLOCK_MONOTONIC, &end), SyscallSucceeds());
+ // Check the lower bound on the timeout. Checking for an upper bound is
+ // fragile because Linux can overrun the timeout due to scheduling delays.
+ EXPECT_GT(ms_elapsed(begin, end), timeout * 1000 - 1);
+}
+
+TEST_P(TcpSocketTest, ClosedReadBlockingSocket) {
+ constexpr int timeout = 10;
+ struct timeval tv = {.tv_sec = timeout, .tv_usec = 0};
+ EXPECT_THAT(setsockopt(first_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),
+ SyscallSucceeds());
+
+ struct timespec begin;
+ struct timespec end;
+ const DisableSave disable_save; // Timing-related.
+ EXPECT_THAT(clock_gettime(CLOCK_MONOTONIC, &begin), SyscallSucceeds());
+
+ ScopedThread read_thread([this]() {
+ char read_byte;
+ // Expect the read() to be blocked until receive timeout.
+ ASSERT_THAT(read(first_fd, &read_byte, sizeof(read_byte)),
+ SyscallFailsWithErrno(EAGAIN));
+ });
+
+ // Wait for the thread to be blocked on read.
+ absl::SleepFor(absl::Milliseconds(250));
+ // Socket close does not have any effect on a blocked read.
+ ASSERT_THAT(close(first_fd), SyscallSucceeds());
+ // Indicate to the cleanup routine that we are already closed.
+ first_fd = -1;
+
+ read_thread.Join();
+
+ EXPECT_THAT(clock_gettime(CLOCK_MONOTONIC, &end), SyscallSucceeds());
+ // Check the lower bound on the timeout. Checking for an upper bound is
+ // fragile because Linux can overrun the timeout due to scheduling delays.
+ EXPECT_GT(ms_elapsed(begin, end), timeout * 1000 - 1);
}
TEST_P(TcpSocketTest, MsgTrunc) {
char sent_data[512];
RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(RetryEINTR(send)(s_, sent_data, sizeof(sent_data), 0),
+ ASSERT_THAT(RetryEINTR(send)(first_fd, sent_data, sizeof(sent_data), 0),
SyscallSucceedsWithValue(sizeof(sent_data)));
char received_data[sizeof(sent_data)] = {};
- ASSERT_THAT(
- RetryEINTR(recv)(t_, received_data, sizeof(received_data) / 2, MSG_TRUNC),
- SyscallSucceedsWithValue(sizeof(sent_data) / 2));
+ ASSERT_THAT(RetryEINTR(recv)(second_fd, received_data,
+ sizeof(received_data) / 2, MSG_TRUNC),
+ SyscallSucceedsWithValue(sizeof(sent_data) / 2));
// Check that we didn't get anything.
char zeros[sizeof(received_data)] = {};
@@ -470,12 +552,13 @@ TEST_P(TcpSocketTest, MsgTrunc) {
TEST_P(TcpSocketTest, MsgTruncWithCtrunc) {
char sent_data[512];
RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(RetryEINTR(send)(s_, sent_data, sizeof(sent_data), 0),
+ ASSERT_THAT(RetryEINTR(send)(first_fd, sent_data, sizeof(sent_data), 0),
SyscallSucceedsWithValue(sizeof(sent_data)));
char received_data[sizeof(sent_data)] = {};
- ASSERT_THAT(RetryEINTR(recv)(t_, received_data, sizeof(received_data) / 2,
- MSG_TRUNC | MSG_CTRUNC),
- SyscallSucceedsWithValue(sizeof(sent_data) / 2));
+ ASSERT_THAT(
+ RetryEINTR(recv)(second_fd, received_data, sizeof(received_data) / 2,
+ MSG_TRUNC | MSG_CTRUNC),
+ SyscallSucceedsWithValue(sizeof(sent_data) / 2));
// Check that we didn't get anything.
char zeros[sizeof(received_data)] = {};
@@ -487,11 +570,11 @@ TEST_P(TcpSocketTest, MsgTruncWithCtrunc) {
TEST_P(TcpSocketTest, MsgTruncWithCtruncOnly) {
char sent_data[512];
RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(RetryEINTR(send)(s_, sent_data, sizeof(sent_data), 0),
+ ASSERT_THAT(RetryEINTR(send)(first_fd, sent_data, sizeof(sent_data), 0),
SyscallSucceedsWithValue(sizeof(sent_data)));
char received_data[sizeof(sent_data)] = {};
- ASSERT_THAT(RetryEINTR(recv)(t_, received_data, sizeof(received_data) / 2,
- MSG_CTRUNC),
+ ASSERT_THAT(RetryEINTR(recv)(second_fd, received_data,
+ sizeof(received_data) / 2, MSG_CTRUNC),
SyscallSucceedsWithValue(sizeof(sent_data) / 2));
// Since MSG_CTRUNC here had no affect, it should not behave like MSG_TRUNC.
@@ -501,12 +584,12 @@ TEST_P(TcpSocketTest, MsgTruncWithCtruncOnly) {
TEST_P(TcpSocketTest, MsgTruncLargeSize) {
char sent_data[512];
RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(RetryEINTR(send)(s_, sent_data, sizeof(sent_data), 0),
+ ASSERT_THAT(RetryEINTR(send)(first_fd, sent_data, sizeof(sent_data), 0),
SyscallSucceedsWithValue(sizeof(sent_data)));
char received_data[sizeof(sent_data) * 2] = {};
- ASSERT_THAT(
- RetryEINTR(recv)(t_, received_data, sizeof(received_data), MSG_TRUNC),
- SyscallSucceedsWithValue(sizeof(sent_data)));
+ ASSERT_THAT(RetryEINTR(recv)(second_fd, received_data, sizeof(received_data),
+ MSG_TRUNC),
+ SyscallSucceedsWithValue(sizeof(sent_data)));
// Check that we didn't get anything.
char zeros[sizeof(received_data)] = {};
@@ -516,11 +599,11 @@ TEST_P(TcpSocketTest, MsgTruncLargeSize) {
TEST_P(TcpSocketTest, MsgTruncPeek) {
char sent_data[512];
RandomizeBuffer(sent_data, sizeof(sent_data));
- ASSERT_THAT(RetryEINTR(send)(s_, sent_data, sizeof(sent_data), 0),
+ ASSERT_THAT(RetryEINTR(send)(first_fd, sent_data, sizeof(sent_data), 0),
SyscallSucceedsWithValue(sizeof(sent_data)));
char received_data[sizeof(sent_data)] = {};
- ASSERT_THAT(RetryEINTR(recv)(t_, received_data, sizeof(received_data) / 2,
- MSG_TRUNC | MSG_PEEK),
+ ASSERT_THAT(RetryEINTR(recv)(second_fd, received_data,
+ sizeof(received_data) / 2, MSG_TRUNC | MSG_PEEK),
SyscallSucceedsWithValue(sizeof(sent_data) / 2));
// Check that we didn't get anything.
@@ -528,37 +611,38 @@ TEST_P(TcpSocketTest, MsgTruncPeek) {
EXPECT_EQ(0, memcmp(zeros, received_data, sizeof(received_data)));
// Check that we can still get all of the data.
- ASSERT_THAT(RetryEINTR(recv)(t_, received_data, sizeof(received_data), 0),
- SyscallSucceedsWithValue(sizeof(sent_data)));
+ ASSERT_THAT(
+ RetryEINTR(recv)(second_fd, received_data, sizeof(received_data), 0),
+ SyscallSucceedsWithValue(sizeof(sent_data)));
EXPECT_EQ(0, memcmp(sent_data, received_data, sizeof(sent_data)));
}
TEST_P(TcpSocketTest, NoDelayDefault) {
int get = -1;
socklen_t get_len = sizeof(get);
- EXPECT_THAT(getsockopt(s_, IPPROTO_TCP, TCP_NODELAY, &get, &get_len),
+ EXPECT_THAT(getsockopt(first_fd, IPPROTO_TCP, TCP_NODELAY, &get, &get_len),
SyscallSucceedsWithValue(0));
EXPECT_EQ(get_len, sizeof(get));
EXPECT_EQ(get, kSockOptOff);
}
TEST_P(TcpSocketTest, SetNoDelay) {
- ASSERT_THAT(
- setsockopt(s_, IPPROTO_TCP, TCP_NODELAY, &kSockOptOn, sizeof(kSockOptOn)),
- SyscallSucceeds());
+ ASSERT_THAT(setsockopt(first_fd, IPPROTO_TCP, TCP_NODELAY, &kSockOptOn,
+ sizeof(kSockOptOn)),
+ SyscallSucceeds());
int get = -1;
socklen_t get_len = sizeof(get);
- EXPECT_THAT(getsockopt(s_, IPPROTO_TCP, TCP_NODELAY, &get, &get_len),
+ EXPECT_THAT(getsockopt(first_fd, IPPROTO_TCP, TCP_NODELAY, &get, &get_len),
SyscallSucceedsWithValue(0));
EXPECT_EQ(get_len, sizeof(get));
EXPECT_EQ(get, kSockOptOn);
- ASSERT_THAT(setsockopt(s_, IPPROTO_TCP, TCP_NODELAY, &kSockOptOff,
+ ASSERT_THAT(setsockopt(first_fd, IPPROTO_TCP, TCP_NODELAY, &kSockOptOff,
sizeof(kSockOptOff)),
SyscallSucceeds());
- EXPECT_THAT(getsockopt(s_, IPPROTO_TCP, TCP_NODELAY, &get, &get_len),
+ EXPECT_THAT(getsockopt(first_fd, IPPROTO_TCP, TCP_NODELAY, &get, &get_len),
SyscallSucceedsWithValue(0));
EXPECT_EQ(get_len, sizeof(get));
EXPECT_EQ(get, kSockOptOff);
@@ -570,33 +654,33 @@ TEST_P(TcpSocketTest, SetNoDelay) {
TEST_P(TcpSocketTest, TcpInqSetSockOpt) {
char buf[1024];
- ASSERT_THAT(RetryEINTR(write)(s_, buf, sizeof(buf)),
+ ASSERT_THAT(RetryEINTR(write)(first_fd, buf, sizeof(buf)),
SyscallSucceedsWithValue(sizeof(buf)));
// TCP_INQ is disabled by default.
int val = -1;
socklen_t slen = sizeof(val);
- EXPECT_THAT(getsockopt(t_, SOL_TCP, TCP_INQ, &val, &slen),
+ EXPECT_THAT(getsockopt(second_fd, SOL_TCP, TCP_INQ, &val, &slen),
SyscallSucceedsWithValue(0));
ASSERT_EQ(val, 0);
// Try to set TCP_INQ.
val = 1;
- EXPECT_THAT(setsockopt(t_, SOL_TCP, TCP_INQ, &val, sizeof(val)),
+ EXPECT_THAT(setsockopt(second_fd, SOL_TCP, TCP_INQ, &val, sizeof(val)),
SyscallSucceedsWithValue(0));
val = -1;
slen = sizeof(val);
- EXPECT_THAT(getsockopt(t_, SOL_TCP, TCP_INQ, &val, &slen),
+ EXPECT_THAT(getsockopt(second_fd, SOL_TCP, TCP_INQ, &val, &slen),
SyscallSucceedsWithValue(0));
ASSERT_EQ(val, 1);
// Try to unset TCP_INQ.
val = 0;
- EXPECT_THAT(setsockopt(t_, SOL_TCP, TCP_INQ, &val, sizeof(val)),
+ EXPECT_THAT(setsockopt(second_fd, SOL_TCP, TCP_INQ, &val, sizeof(val)),
SyscallSucceedsWithValue(0));
val = -1;
slen = sizeof(val);
- EXPECT_THAT(getsockopt(t_, SOL_TCP, TCP_INQ, &val, &slen),
+ EXPECT_THAT(getsockopt(second_fd, SOL_TCP, TCP_INQ, &val, &slen),
SyscallSucceedsWithValue(0));
ASSERT_EQ(val, 0);
}
@@ -607,18 +691,18 @@ TEST_P(TcpSocketTest, TcpInq) {
int size = sizeof(buf);
int kChunk = sizeof(buf) / 4;
for (int i = 0; i < size; i += kChunk) {
- ASSERT_THAT(RetryEINTR(write)(s_, buf, kChunk),
+ ASSERT_THAT(RetryEINTR(write)(first_fd, buf, kChunk),
SyscallSucceedsWithValue(kChunk));
}
int val = 1;
kChunk = sizeof(buf) / 2;
- EXPECT_THAT(setsockopt(t_, SOL_TCP, TCP_INQ, &val, sizeof(val)),
+ EXPECT_THAT(setsockopt(second_fd, SOL_TCP, TCP_INQ, &val, sizeof(val)),
SyscallSucceedsWithValue(0));
// Wait when all data will be in the received queue.
while (true) {
- ASSERT_THAT(ioctl(t_, TIOCINQ, &size), SyscallSucceeds());
+ ASSERT_THAT(ioctl(second_fd, TIOCINQ, &size), SyscallSucceeds());
if (size == sizeof(buf)) {
break;
}
@@ -637,7 +721,7 @@ TEST_P(TcpSocketTest, TcpInq) {
iov.iov_len = kChunk;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
- ASSERT_THAT(RetryEINTR(recvmsg)(t_, &msg, 0),
+ ASSERT_THAT(RetryEINTR(recvmsg)(second_fd, &msg, 0),
SyscallSucceedsWithValue(kChunk));
size -= kChunk;
@@ -656,32 +740,35 @@ TEST_P(TcpSocketTest, TcpInq) {
TEST_P(TcpSocketTest, Tiocinq) {
char buf[1024];
size_t size = sizeof(buf);
- ASSERT_THAT(RetryEINTR(write)(s_, buf, size), SyscallSucceedsWithValue(size));
+ ASSERT_THAT(RetryEINTR(write)(first_fd, buf, size),
+ SyscallSucceedsWithValue(size));
uint32_t seed = time(nullptr);
const size_t max_chunk = size / 10;
while (size > 0) {
size_t chunk = (rand_r(&seed) % max_chunk) + 1;
- ssize_t read = RetryEINTR(recvfrom)(t_, buf, chunk, 0, nullptr, nullptr);
+ ssize_t read =
+ RetryEINTR(recvfrom)(second_fd, buf, chunk, 0, nullptr, nullptr);
ASSERT_THAT(read, SyscallSucceeds());
size -= read;
int inq = 0;
- ASSERT_THAT(ioctl(t_, TIOCINQ, &inq), SyscallSucceeds());
+ ASSERT_THAT(ioctl(second_fd, TIOCINQ, &inq), SyscallSucceeds());
ASSERT_EQ(inq, size);
}
}
TEST_P(TcpSocketTest, TcpSCMPriority) {
char buf[1024];
- ASSERT_THAT(RetryEINTR(write)(s_, buf, sizeof(buf)),
+ ASSERT_THAT(RetryEINTR(write)(first_fd, buf, sizeof(buf)),
SyscallSucceedsWithValue(sizeof(buf)));
int val = 1;
- EXPECT_THAT(setsockopt(t_, SOL_TCP, TCP_INQ, &val, sizeof(val)),
- SyscallSucceedsWithValue(0));
- EXPECT_THAT(setsockopt(t_, SOL_SOCKET, SO_TIMESTAMP, &val, sizeof(val)),
+ EXPECT_THAT(setsockopt(second_fd, SOL_TCP, TCP_INQ, &val, sizeof(val)),
SyscallSucceedsWithValue(0));
+ EXPECT_THAT(
+ setsockopt(second_fd, SOL_SOCKET, SO_TIMESTAMP, &val, sizeof(val)),
+ SyscallSucceedsWithValue(0));
struct msghdr msg = {};
std::vector<char> control(
@@ -694,7 +781,7 @@ TEST_P(TcpSocketTest, TcpSCMPriority) {
iov.iov_len = sizeof(buf);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
- ASSERT_THAT(RetryEINTR(recvmsg)(t_, &msg, 0),
+ ASSERT_THAT(RetryEINTR(recvmsg)(second_fd, &msg, 0),
SyscallSucceedsWithValue(sizeof(buf)));
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
@@ -721,24 +808,24 @@ TEST_P(TcpSocketTest, TcpSCMPriority) {
}
TEST_P(TcpSocketTest, TimeWaitPollHUP) {
- shutdown(s_, SHUT_RDWR);
+ shutdown(first_fd, SHUT_RDWR);
ScopedThread t([&]() {
constexpr int kTimeout = 10000;
constexpr int16_t want_events = POLLHUP;
struct pollfd pfd = {
- .fd = s_,
+ .fd = first_fd,
.events = want_events,
};
ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1));
});
- shutdown(t_, SHUT_RDWR);
+ shutdown(second_fd, SHUT_RDWR);
t.Join();
- // At this point s_ should be in TIME-WAIT and polling for POLLHUP should
- // return with 1 FD.
+ // At this point first_fd should be in TIME-WAIT and polling for POLLHUP
+ // should return with 1 FD.
constexpr int kTimeout = 10000;
constexpr int16_t want_events = POLLHUP;
struct pollfd pfd = {
- .fd = s_,
+ .fd = first_fd,
.events = want_events,
};
ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1));
@@ -803,31 +890,36 @@ TEST_P(SimpleTcpSocketTest, GetPeerNameUnconnected) {
TEST_P(TcpSocketTest, FullBuffer) {
// Set both FDs to be blocking.
int flags = 0;
- ASSERT_THAT(flags = fcntl(s_, F_GETFL), SyscallSucceeds());
- EXPECT_THAT(fcntl(s_, F_SETFL, flags & ~O_NONBLOCK), SyscallSucceeds());
+ ASSERT_THAT(flags = fcntl(first_fd, F_GETFL), SyscallSucceeds());
+ EXPECT_THAT(fcntl(first_fd, F_SETFL, flags & ~O_NONBLOCK), SyscallSucceeds());
flags = 0;
- ASSERT_THAT(flags = fcntl(t_, F_GETFL), SyscallSucceeds());
- EXPECT_THAT(fcntl(t_, F_SETFL, flags & ~O_NONBLOCK), SyscallSucceeds());
+ ASSERT_THAT(flags = fcntl(second_fd, F_GETFL), SyscallSucceeds());
+ EXPECT_THAT(fcntl(second_fd, F_SETFL, flags & ~O_NONBLOCK),
+ SyscallSucceeds());
// 2500 was chosen as a small value that can be set on Linux.
int set_snd = 2500;
- EXPECT_THAT(setsockopt(s_, SOL_SOCKET, SO_SNDBUF, &set_snd, sizeof(set_snd)),
- SyscallSucceedsWithValue(0));
+ EXPECT_THAT(
+ setsockopt(first_fd, SOL_SOCKET, SO_SNDBUF, &set_snd, sizeof(set_snd)),
+ SyscallSucceedsWithValue(0));
int get_snd = -1;
socklen_t get_snd_len = sizeof(get_snd);
- EXPECT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &get_snd, &get_snd_len),
- SyscallSucceedsWithValue(0));
+ EXPECT_THAT(
+ getsockopt(first_fd, SOL_SOCKET, SO_SNDBUF, &get_snd, &get_snd_len),
+ SyscallSucceedsWithValue(0));
EXPECT_EQ(get_snd_len, sizeof(get_snd));
EXPECT_GT(get_snd, 0);
// 2500 was chosen as a small value that can be set on Linux and gVisor.
int set_rcv = 2500;
- EXPECT_THAT(setsockopt(t_, SOL_SOCKET, SO_RCVBUF, &set_rcv, sizeof(set_rcv)),
- SyscallSucceedsWithValue(0));
+ EXPECT_THAT(
+ setsockopt(second_fd, SOL_SOCKET, SO_RCVBUF, &set_rcv, sizeof(set_rcv)),
+ SyscallSucceedsWithValue(0));
int get_rcv = -1;
socklen_t get_rcv_len = sizeof(get_rcv);
- EXPECT_THAT(getsockopt(t_, SOL_SOCKET, SO_RCVBUF, &get_rcv, &get_rcv_len),
- SyscallSucceedsWithValue(0));
+ EXPECT_THAT(
+ getsockopt(second_fd, SOL_SOCKET, SO_RCVBUF, &get_rcv, &get_rcv_len),
+ SyscallSucceedsWithValue(0));
EXPECT_EQ(get_rcv_len, sizeof(get_rcv));
EXPECT_GE(get_rcv, 2500);
@@ -844,63 +936,184 @@ TEST_P(TcpSocketTest, FullBuffer) {
}
ScopedThread t([this, &iovecs]() {
int result = -1;
- EXPECT_THAT(result = RetryEINTR(writev)(s_, iovecs.data(), iovecs.size()),
- SyscallSucceeds());
+ EXPECT_THAT(
+ result = RetryEINTR(writev)(first_fd, iovecs.data(), iovecs.size()),
+ SyscallSucceeds());
EXPECT_GT(result, 1);
EXPECT_LT(result, sizeof(data) * iovecs.size());
});
char recv = 0;
- EXPECT_THAT(RetryEINTR(read)(t_, &recv, 1), SyscallSucceedsWithValue(1));
- EXPECT_THAT(close(t_), SyscallSucceedsWithValue(0));
- t_ = -1;
+ EXPECT_THAT(RetryEINTR(read)(second_fd, &recv, 1),
+ SyscallSucceedsWithValue(1));
+ EXPECT_THAT(close(second_fd), SyscallSucceedsWithValue(0));
+ second_fd = -1;
}
TEST_P(TcpSocketTest, PollAfterShutdown) {
ScopedThread client_thread([this]() {
- EXPECT_THAT(shutdown(s_, SHUT_WR), SyscallSucceedsWithValue(0));
- struct pollfd poll_fd = {s_, POLLIN | POLLERR | POLLHUP, 0};
+ EXPECT_THAT(shutdown(first_fd, SHUT_WR), SyscallSucceedsWithValue(0));
+ struct pollfd poll_fd = {first_fd, POLLIN | POLLERR | POLLHUP, 0};
EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10000),
SyscallSucceedsWithValue(1));
});
- EXPECT_THAT(shutdown(t_, SHUT_WR), SyscallSucceedsWithValue(0));
- struct pollfd poll_fd = {t_, POLLIN | POLLERR | POLLHUP, 0};
+ EXPECT_THAT(shutdown(second_fd, SHUT_WR), SyscallSucceedsWithValue(0));
+ struct pollfd poll_fd = {second_fd, POLLIN | POLLERR | POLLHUP, 0};
EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10000),
SyscallSucceedsWithValue(1));
}
-TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListener) {
+TEST_P(SimpleTcpSocketTest, NonBlockingConnectRetry) {
+ const FileDescriptor listener =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
+
// Initialize address to the loopback one.
sockaddr_storage addr =
ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam()));
socklen_t addrlen = sizeof(addr);
- const FileDescriptor s =
+ // Bind to some port but don't listen yet.
+ ASSERT_THAT(
+ bind(listener.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
+ SyscallSucceeds());
+
+ // Get the address we're bound to, then connect to it. We need to do this
+ // because we're allowing the stack to pick a port for us.
+ ASSERT_THAT(getsockname(listener.get(),
+ reinterpret_cast<struct sockaddr*>(&addr), &addrlen),
+ SyscallSucceeds());
+
+ FileDescriptor connector =
ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
- // Set the FD to O_NONBLOCK.
- int opts;
- ASSERT_THAT(opts = fcntl(s.get(), F_GETFL), SyscallSucceeds());
- opts |= O_NONBLOCK;
- ASSERT_THAT(fcntl(s.get(), F_SETFL, opts), SyscallSucceeds());
+ // Verify that connect fails.
+ ASSERT_THAT(
+ RetryEINTR(connect)(connector.get(),
+ reinterpret_cast<struct sockaddr*>(&addr), addrlen),
+ SyscallFailsWithErrno(ECONNREFUSED));
- ASSERT_THAT(RetryEINTR(connect)(
+ // Now start listening
+ ASSERT_THAT(listen(listener.get(), SOMAXCONN), SyscallSucceeds());
+
+ // TODO(gvisor.dev/issue/3828): Issuing connect() again on a socket that
+ // failed first connect should succeed.
+ if (IsRunningOnGvisor()) {
+ ASSERT_THAT(
+ RetryEINTR(connect)(connector.get(),
+ reinterpret_cast<struct sockaddr*>(&addr), addrlen),
+ SyscallFailsWithErrno(ECONNABORTED));
+ return;
+ }
+
+ // Verify that connect now succeeds.
+ ASSERT_THAT(
+ RetryEINTR(connect)(connector.get(),
+ reinterpret_cast<struct sockaddr*>(&addr), addrlen),
+ SyscallSucceeds());
+
+ // Accept the connection.
+ const FileDescriptor accepted =
+ ASSERT_NO_ERRNO_AND_VALUE(Accept(listener.get(), nullptr, nullptr));
+}
+
+// nonBlockingConnectNoListener returns a socket on which a connect that is
+// expected to fail has been issued.
+PosixErrorOr<FileDescriptor> nonBlockingConnectNoListener(const int family,
+ sockaddr_storage addr,
+ socklen_t addrlen) {
+ // We will first create a socket and bind to ensure we bind a port but will
+ // not call listen on this socket.
+ // Then we will create a new socket that will connect to the port bound by
+ // the first socket and that shoud fail.
+ constexpr int sock_type = SOCK_STREAM | SOCK_NONBLOCK;
+ int b_sock;
+ RETURN_ERROR_IF_SYSCALL_FAIL(b_sock = socket(family, sock_type, IPPROTO_TCP));
+ FileDescriptor b(b_sock);
+ EXPECT_THAT(bind(b.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
+ SyscallSucceeds());
+
+ // Get the address bound by the listening socket.
+ EXPECT_THAT(
+ getsockname(b.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen),
+ SyscallSucceeds());
+
+ // Now create another socket and issue a connect on this one. This connect
+ // should fail as there is no listener.
+ int c_sock;
+ RETURN_ERROR_IF_SYSCALL_FAIL(c_sock = socket(family, sock_type, IPPROTO_TCP));
+ FileDescriptor s(c_sock);
+
+ // Now connect to the bound address and this should fail as nothing
+ // is listening on the bound address.
+ EXPECT_THAT(RetryEINTR(connect)(
s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
SyscallFailsWithErrno(EINPROGRESS));
- // Now polling on the FD with a timeout should return 0 corresponding to no
- // FDs ready.
- struct pollfd poll_fd = {s.get(), POLLOUT, 0};
- EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10000),
- SyscallSucceedsWithValue(1));
+ // Wait for the connect to fail.
+ struct pollfd poll_fd = {s.get(), POLLERR, 0};
+ EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 1000), SyscallSucceedsWithValue(1));
+ return std::move(s);
+}
+
+TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListener) {
+ sockaddr_storage addr =
+ ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam()));
+ socklen_t addrlen = sizeof(addr);
+
+ const FileDescriptor s =
+ nonBlockingConnectNoListener(GetParam(), addr, addrlen).ValueOrDie();
int err;
socklen_t optlen = sizeof(err);
ASSERT_THAT(getsockopt(s.get(), SOL_SOCKET, SO_ERROR, &err, &optlen),
SyscallSucceeds());
-
+ ASSERT_THAT(optlen, sizeof(err));
EXPECT_EQ(err, ECONNREFUSED);
+
+ unsigned char c;
+ ASSERT_THAT(read(s.get(), &c, sizeof(c)), SyscallSucceedsWithValue(0));
+ int opts;
+ EXPECT_THAT(opts = fcntl(s.get(), F_GETFL), SyscallSucceeds());
+ opts &= ~O_NONBLOCK;
+ EXPECT_THAT(fcntl(s.get(), F_SETFL, opts), SyscallSucceeds());
+ // Try connecting again.
+ ASSERT_THAT(RetryEINTR(connect)(
+ s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
+ SyscallFailsWithErrno(ECONNABORTED));
+}
+
+TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListenerRead) {
+ sockaddr_storage addr =
+ ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam()));
+ socklen_t addrlen = sizeof(addr);
+
+ const FileDescriptor s =
+ nonBlockingConnectNoListener(GetParam(), addr, addrlen).ValueOrDie();
+
+ unsigned char c;
+ ASSERT_THAT(read(s.get(), &c, 1), SyscallFailsWithErrno(ECONNREFUSED));
+ ASSERT_THAT(read(s.get(), &c, 1), SyscallSucceedsWithValue(0));
+ ASSERT_THAT(RetryEINTR(connect)(
+ s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
+ SyscallFailsWithErrno(ECONNABORTED));
+}
+
+TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListenerPeek) {
+ sockaddr_storage addr =
+ ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam()));
+ socklen_t addrlen = sizeof(addr);
+
+ const FileDescriptor s =
+ nonBlockingConnectNoListener(GetParam(), addr, addrlen).ValueOrDie();
+
+ unsigned char c;
+ ASSERT_THAT(recv(s.get(), &c, 1, MSG_PEEK),
+ SyscallFailsWithErrno(ECONNREFUSED));
+ ASSERT_THAT(recv(s.get(), &c, 1, MSG_PEEK), SyscallSucceedsWithValue(0));
+ ASSERT_THAT(RetryEINTR(connect)(
+ s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
+ SyscallFailsWithErrno(ECONNABORTED));
}
TEST_P(SimpleTcpSocketTest, SelfConnectSendRecv_NoRandomSave) {
@@ -930,7 +1143,7 @@ TEST_P(SimpleTcpSocketTest, SelfConnectSendRecv_NoRandomSave) {
int read_bytes = 0;
ScopedThread t([&s, &read_bytes]() {
// Too many syscalls.
- const DisableSave ds;
+ const DisableSave disable_save;
char readbuf[2500] = {};
int n = -1;
@@ -1141,6 +1354,19 @@ TEST_P(SimpleTcpSocketTest, CleanupOnConnectionRefused) {
// Attempt #2, with the new socket and reused addr our connect should fail in
// the same way as before, not with an EADDRINUSE.
+ //
+ // TODO(gvisor.dev/issue/3828): 2nd connect on a socket which failed connect
+ // first time should succeed.
+ // gVisor never issues the second connect and returns ECONNABORTED instead.
+ // Linux actually sends a SYN again and gets a RST and correctly returns
+ // ECONNREFUSED.
+ if (IsRunningOnGvisor()) {
+ ASSERT_THAT(connect(client_s.get(),
+ reinterpret_cast<const struct sockaddr*>(&bound_addr),
+ bound_addrlen),
+ SyscallFailsWithErrno(ECONNABORTED));
+ return;
+ }
ASSERT_THAT(connect(client_s.get(),
reinterpret_cast<const struct sockaddr*>(&bound_addr),
bound_addrlen),
@@ -1725,6 +1951,63 @@ TEST_P(SimpleTcpSocketTest, CloseNonConnectedLingerOption) {
ASSERT_LT((end_time - start_time), absl::Seconds(kLingerTimeout));
}
+// Tests that SO_ACCEPTCONN returns non zero value for listening sockets.
+TEST_P(TcpSocketTest, GetSocketAcceptConnListener) {
+ int got = -1;
+ socklen_t length = sizeof(got);
+ ASSERT_THAT(getsockopt(listener_, SOL_SOCKET, SO_ACCEPTCONN, &got, &length),
+ SyscallSucceeds());
+ ASSERT_EQ(length, sizeof(got));
+ EXPECT_EQ(got, 1);
+}
+
+// Tests that SO_ACCEPTCONN returns zero value for not listening sockets.
+TEST_P(TcpSocketTest, GetSocketAcceptConnNonListener) {
+ int got = -1;
+ socklen_t length = sizeof(got);
+ ASSERT_THAT(getsockopt(first_fd, SOL_SOCKET, SO_ACCEPTCONN, &got, &length),
+ SyscallSucceeds());
+ ASSERT_EQ(length, sizeof(got));
+ EXPECT_EQ(got, 0);
+
+ ASSERT_THAT(getsockopt(second_fd, SOL_SOCKET, SO_ACCEPTCONN, &got, &length),
+ SyscallSucceeds());
+ ASSERT_EQ(length, sizeof(got));
+ EXPECT_EQ(got, 0);
+}
+
+TEST_P(SimpleTcpSocketTest, GetSocketAcceptConnWithShutdown) {
+ // TODO(b/171345701): Fix the TCP state for listening socket on shutdown.
+ SKIP_IF(IsRunningOnGvisor());
+
+ FileDescriptor s =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
+
+ // Initialize address to the loopback one.
+ sockaddr_storage addr =
+ ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam()));
+ socklen_t addrlen = sizeof(addr);
+
+ // Bind to some port then start listening.
+ ASSERT_THAT(bind(s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
+ SyscallSucceeds());
+
+ ASSERT_THAT(listen(s.get(), SOMAXCONN), SyscallSucceeds());
+
+ int got = -1;
+ socklen_t length = sizeof(got);
+ ASSERT_THAT(getsockopt(s.get(), SOL_SOCKET, SO_ACCEPTCONN, &got, &length),
+ SyscallSucceeds());
+ ASSERT_EQ(length, sizeof(got));
+ EXPECT_EQ(got, 1);
+
+ EXPECT_THAT(shutdown(s.get(), SHUT_RD), SyscallSucceeds());
+ ASSERT_THAT(getsockopt(s.get(), SOL_SOCKET, SO_ACCEPTCONN, &got, &length),
+ SyscallSucceeds());
+ ASSERT_EQ(length, sizeof(got));
+ EXPECT_EQ(got, 0);
+}
+
INSTANTIATE_TEST_SUITE_P(AllInetTests, SimpleTcpSocketTest,
::testing::Values(AF_INET, AF_INET6));
diff --git a/test/syscalls/linux/timers.cc b/test/syscalls/linux/timers.cc
index cac94d9e1..93a98adb1 100644
--- a/test/syscalls/linux/timers.cc
+++ b/test/syscalls/linux/timers.cc
@@ -322,11 +322,6 @@ TEST(IntervalTimerTest, PeriodicGroupDirectedSignal) {
EXPECT_GE(counted_signals.load(), kCycles);
}
-// From Linux's include/uapi/asm-generic/siginfo.h.
-#ifndef sigev_notify_thread_id
-#define sigev_notify_thread_id _sigev_un._tid
-#endif
-
TEST(IntervalTimerTest, PeriodicThreadDirectedSignal) {
constexpr int kSigno = SIGUSR1;
constexpr int kSigvalue = 42;
diff --git a/test/syscalls/linux/udp_socket.cc b/test/syscalls/linux/udp_socket.cc
index 1a7673317..90ef8bf21 100644
--- a/test/syscalls/linux/udp_socket.cc
+++ b/test/syscalls/linux/udp_socket.cc
@@ -374,6 +374,69 @@ TEST_P(UdpSocketTest, BindInUse) {
SyscallFailsWithErrno(EADDRINUSE));
}
+TEST_P(UdpSocketTest, ConnectWriteToInvalidPort) {
+ // Discover a free unused port by creating a new UDP socket, binding it
+ // recording the just bound port and closing it. This is not guaranteed as it
+ // can still race with other port UDP sockets trying to bind a port at the
+ // same time.
+ struct sockaddr_storage addr_storage = InetLoopbackAddr();
+ socklen_t addrlen = sizeof(addr_storage);
+ struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage);
+ FileDescriptor s =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(GetFamily(), SOCK_DGRAM, IPPROTO_UDP));
+ ASSERT_THAT(bind(s.get(), addr, addrlen), SyscallSucceeds());
+ ASSERT_THAT(getsockname(s.get(), addr, &addrlen), SyscallSucceeds());
+ EXPECT_EQ(addrlen, addrlen_);
+ EXPECT_NE(*Port(&addr_storage), 0);
+ ASSERT_THAT(close(s.release()), SyscallSucceeds());
+
+ // Now connect to the port that we just released. This should generate an
+ // ECONNREFUSED error.
+ ASSERT_THAT(connect(sock_.get(), addr, addrlen_), SyscallSucceeds());
+ char buf[512];
+ RandomizeBuffer(buf, sizeof(buf));
+ // Send from sock_ to an unbound port.
+ ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, addr, addrlen_),
+ SyscallSucceedsWithValue(sizeof(buf)));
+
+ // Now verify that we got an ICMP error back of ECONNREFUSED.
+ int err;
+ socklen_t optlen = sizeof(err);
+ ASSERT_THAT(getsockopt(sock_.get(), SOL_SOCKET, SO_ERROR, &err, &optlen),
+ SyscallSucceeds());
+ ASSERT_EQ(err, ECONNREFUSED);
+ ASSERT_EQ(optlen, sizeof(err));
+}
+
+TEST_P(UdpSocketTest, ConnectSimultaneousWriteToInvalidPort) {
+ // Discover a free unused port by creating a new UDP socket, binding it
+ // recording the just bound port and closing it. This is not guaranteed as it
+ // can still race with other port UDP sockets trying to bind a port at the
+ // same time.
+ struct sockaddr_storage addr_storage = InetLoopbackAddr();
+ socklen_t addrlen = sizeof(addr_storage);
+ struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage);
+ FileDescriptor s =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(GetFamily(), SOCK_DGRAM, IPPROTO_UDP));
+ ASSERT_THAT(bind(s.get(), addr, addrlen), SyscallSucceeds());
+ ASSERT_THAT(getsockname(s.get(), addr, &addrlen), SyscallSucceeds());
+ EXPECT_EQ(addrlen, addrlen_);
+ EXPECT_NE(*Port(&addr_storage), 0);
+ ASSERT_THAT(close(s.release()), SyscallSucceeds());
+
+ // Now connect to the port that we just released.
+ ScopedThread t([&] {
+ ASSERT_THAT(connect(sock_.get(), addr, addrlen_), SyscallSucceeds());
+ });
+
+ char buf[512];
+ RandomizeBuffer(buf, sizeof(buf));
+ // Send from sock_ to an unbound port.
+ ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, addr, addrlen_),
+ SyscallSucceedsWithValue(sizeof(buf)));
+ t.Join();
+}
+
TEST_P(UdpSocketTest, ReceiveAfterConnect) {
ASSERT_NO_ERRNO(BindLoopback());
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
@@ -679,6 +742,43 @@ TEST_P(UdpSocketTest, SendToAddressOtherThanConnected) {
SyscallSucceedsWithValue(sizeof(buf)));
}
+TEST_P(UdpSocketTest, ConnectAndSendNoReceiver) {
+ ASSERT_NO_ERRNO(BindLoopback());
+ // Close the socket to release the port so that we get an ICMP error.
+ ASSERT_THAT(close(bind_.release()), SyscallSucceeds());
+
+ // Connect to loopback:bind_addr_ which should *hopefully* not be bound by an
+ // UDP socket. There is no easy way to ensure that the UDP port is not bound
+ // by another conncurrently running test. *This is potentially flaky*.
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
+
+ char buf[512];
+ EXPECT_THAT(send(sock_.get(), buf, sizeof(buf), 0),
+ SyscallSucceedsWithValue(sizeof(buf)));
+
+ constexpr int kTimeout = 1000;
+ // Poll to make sure we get the ICMP error back before issuing more writes.
+ struct pollfd pfd = {sock_.get(), POLLERR, 0};
+ ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1));
+
+ // Next write should fail with ECONNREFUSED due to the ICMP error generated in
+ // response to the previous write.
+ ASSERT_THAT(send(sock_.get(), buf, sizeof(buf), 0),
+ SyscallFailsWithErrno(ECONNREFUSED));
+
+ // The next write should succeed again since the last write call would have
+ // retrieved and cleared the socket error.
+ ASSERT_THAT(send(sock_.get(), buf, sizeof(buf), 0), SyscallSucceeds());
+
+ // Poll to make sure we get the ICMP error back before issuing more writes.
+ ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1));
+
+ // Next write should fail with ECONNREFUSED due to the ICMP error generated in
+ // response to the previous write.
+ ASSERT_THAT(send(sock_.get(), buf, sizeof(buf), 0),
+ SyscallFailsWithErrno(ECONNREFUSED));
+}
+
TEST_P(UdpSocketTest, ZerolengthWriteAllowed) {
// TODO(gvisor.dev/issue/1202): Hostinet does not support zero length writes.
SKIP_IF(IsRunningWithHostinet());
@@ -838,7 +938,7 @@ TEST_P(UdpSocketTest, ReceiveBeforeConnect) {
// Receive the data. It works because it was sent before the connect.
char received[sizeof(buf)];
EXPECT_THAT(
- RecvMsgTimeout(bind_.get(), received, sizeof(received), 1 /*timeout*/),
+ RecvTimeout(bind_.get(), received, sizeof(received), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(received)));
EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
@@ -928,9 +1028,8 @@ TEST_P(UdpSocketTest, ReadShutdownNonblockPendingData) {
SyscallSucceedsWithValue(1));
// We should get the data even though read has been shutdown.
- EXPECT_THAT(
- RecvMsgTimeout(bind_.get(), received, 2 /*buf_size*/, 1 /*timeout*/),
- IsPosixErrorOkAndHolds(2));
+ EXPECT_THAT(RecvTimeout(bind_.get(), received, 2 /*buf_size*/, 1 /*timeout*/),
+ IsPosixErrorOkAndHolds(2));
// Because we read less than the entire packet length, since it's a packet
// based socket any subsequent reads should return EWOULDBLOCK.
@@ -1698,8 +1797,8 @@ TEST_P(UdpSocketTest, RecvBufLimitsEmptyRcvBuf) {
sendto(sock_.get(), buf.data(), buf.size(), 0, bind_addr_, addrlen_),
SyscallSucceedsWithValue(buf.size()));
std::vector<char> received(buf.size());
- EXPECT_THAT(RecvMsgTimeout(bind_.get(), received.data(), received.size(),
- 1 /*timeout*/),
+ EXPECT_THAT(RecvTimeout(bind_.get(), received.data(), received.size(),
+ 1 /*timeout*/),
IsPosixErrorOkAndHolds(received.size()));
}
@@ -1714,8 +1813,8 @@ TEST_P(UdpSocketTest, RecvBufLimitsEmptyRcvBuf) {
SyscallSucceedsWithValue(buf.size()));
std::vector<char> received(buf.size());
- ASSERT_THAT(RecvMsgTimeout(bind_.get(), received.data(), received.size(),
- 1 /*timeout*/),
+ ASSERT_THAT(RecvTimeout(bind_.get(), received.data(), received.size(),
+ 1 /*timeout*/),
IsPosixErrorOkAndHolds(received.size()));
}
}
@@ -1785,8 +1884,8 @@ TEST_P(UdpSocketTest, RecvBufLimits) {
for (int i = 0; i < sent - 1; i++) {
// Receive the data.
std::vector<char> received(buf.size());
- EXPECT_THAT(RecvMsgTimeout(bind_.get(), received.data(), received.size(),
- 1 /*timeout*/),
+ EXPECT_THAT(RecvTimeout(bind_.get(), received.data(), received.size(),
+ 1 /*timeout*/),
IsPosixErrorOkAndHolds(received.size()));
EXPECT_EQ(memcmp(buf.data(), received.data(), buf.size()), 0);
}
@@ -1851,6 +1950,22 @@ TEST_P(UdpSocketTest, GetSocketDetachFilter) {
SyscallFailsWithErrno(ENOPROTOOPT));
}
+TEST_P(UdpSocketTest, SendToZeroPort) {
+ char buf[8];
+ struct sockaddr_storage addr = InetLoopbackAddr();
+
+ // Sending to an invalid port should fail.
+ SetPort(&addr, 0);
+ EXPECT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0,
+ reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)),
+ SyscallFailsWithErrno(EINVAL));
+
+ SetPort(&addr, 1234);
+ EXPECT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0,
+ reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)),
+ SyscallSucceedsWithValue(sizeof(buf)));
+}
+
INSTANTIATE_TEST_SUITE_P(AllInetTests, UdpSocketTest,
::testing::Values(AddressFamily::kIpv4,
AddressFamily::kIpv6,
diff --git a/test/util/BUILD b/test/util/BUILD
index 26c2b6a2f..1b028a477 100644
--- a/test/util/BUILD
+++ b/test/util/BUILD
@@ -155,6 +155,10 @@ cc_library(
],
hdrs = ["save_util.h"],
defines = select_system(),
+ deps = [
+ ":logging",
+ "@com_google_absl//absl/types:optional",
+ ],
)
cc_library(
diff --git a/test/util/posix_error.cc b/test/util/posix_error.cc
index cebf7e0ac..deed0c05b 100644
--- a/test/util/posix_error.cc
+++ b/test/util/posix_error.cc
@@ -87,7 +87,7 @@ bool PosixErrorIsMatcherCommonImpl::MatchAndExplain(
return false;
}
- if (!message_matcher_.Matches(error.error_message())) {
+ if (!message_matcher_.Matches(error.message())) {
return false;
}
diff --git a/test/util/posix_error.h b/test/util/posix_error.h
index ad666bce0..b634a7f78 100644
--- a/test/util/posix_error.h
+++ b/test/util/posix_error.h
@@ -26,11 +26,6 @@
namespace gvisor {
namespace testing {
-class PosixErrorIsMatcherCommonImpl;
-
-template <typename T>
-class PosixErrorOr;
-
class ABSL_MUST_USE_RESULT PosixError {
public:
PosixError() {}
@@ -49,7 +44,8 @@ class ABSL_MUST_USE_RESULT PosixError {
// PosixErrorOr.
const PosixError& error() const { return *this; }
- std::string error_message() const { return msg_; }
+ int errno_value() const { return errno_; }
+ std::string message() const { return msg_; }
// ToString produces a full string representation of this posix error
// including the printable representation of the errno and the error message.
@@ -61,14 +57,8 @@ class ABSL_MUST_USE_RESULT PosixError {
void IgnoreError() const {}
private:
- int errno_value() const { return errno_; }
int errno_ = 0;
std::string msg_;
-
- friend class PosixErrorIsMatcherCommonImpl;
-
- template <typename T>
- friend class PosixErrorOr;
};
template <typename T>
@@ -94,15 +84,12 @@ class ABSL_MUST_USE_RESULT PosixErrorOr {
template <typename U>
PosixErrorOr& operator=(PosixErrorOr<U> other);
- // Return a reference to the error or NoError().
- PosixError error() const;
-
- // Returns this->error().error_message();
- std::string error_message() const;
-
// Returns true if this PosixErrorOr contains some T.
bool ok() const;
+ // Return a copy of the contained PosixError or NoError().
+ PosixError error() const;
+
// Returns a reference to our current value, or CHECK-fails if !this->ok().
const T& ValueOrDie() const&;
T& ValueOrDie() &;
@@ -115,7 +102,6 @@ class ABSL_MUST_USE_RESULT PosixErrorOr {
void IgnoreError() const {}
private:
- int errno_value() const;
absl::variant<T, PosixError> value_;
friend class PosixErrorIsMatcherCommonImpl;
@@ -171,16 +157,6 @@ PosixError PosixErrorOr<T>::error() const {
}
template <typename T>
-int PosixErrorOr<T>::errno_value() const {
- return error().errno_value();
-}
-
-template <typename T>
-std::string PosixErrorOr<T>::error_message() const {
- return error().error_message();
-}
-
-template <typename T>
bool PosixErrorOr<T>::ok() const {
return absl::holds_alternative<T>(value_);
}
diff --git a/test/util/save_util.cc b/test/util/save_util.cc
index 384d626f0..59d47e06e 100644
--- a/test/util/save_util.cc
+++ b/test/util/save_util.cc
@@ -21,35 +21,47 @@
#include <atomic>
#include <cerrno>
-#define GVISOR_COOPERATIVE_SAVE_TEST "GVISOR_COOPERATIVE_SAVE_TEST"
+#include "absl/types/optional.h"
namespace gvisor {
namespace testing {
namespace {
-enum class CooperativeSaveMode {
- kUnknown = 0, // cooperative_save_mode is statically-initialized to 0
- kAvailable,
- kNotAvailable,
-};
-
-std::atomic<CooperativeSaveMode> cooperative_save_mode;
-
-bool CooperativeSaveEnabled() {
- auto mode = cooperative_save_mode.load();
- if (mode == CooperativeSaveMode::kUnknown) {
- mode = (getenv(GVISOR_COOPERATIVE_SAVE_TEST) != nullptr)
- ? CooperativeSaveMode::kAvailable
- : CooperativeSaveMode::kNotAvailable;
- cooperative_save_mode.store(mode);
+std::atomic<absl::optional<bool>> cooperative_save_present;
+std::atomic<absl::optional<bool>> random_save_present;
+
+bool CooperativeSavePresent() {
+ auto present = cooperative_save_present.load();
+ if (!present.has_value()) {
+ present = getenv("GVISOR_COOPERATIVE_SAVE_TEST") != nullptr;
+ cooperative_save_present.store(present);
+ }
+ return present.value();
+}
+
+bool RandomSavePresent() {
+ auto present = random_save_present.load();
+ if (!present.has_value()) {
+ present = getenv("GVISOR_RANDOM_SAVE_TEST") != nullptr;
+ random_save_present.store(present);
}
- return mode == CooperativeSaveMode::kAvailable;
+ return present.value();
}
std::atomic<int> save_disable;
} // namespace
+bool IsRunningWithSaveRestore() {
+ return CooperativeSavePresent() || RandomSavePresent();
+}
+
+void MaybeSave() {
+ if (CooperativeSavePresent() && save_disable.load() == 0) {
+ internal::DoCooperativeSave();
+ }
+}
+
DisableSave::DisableSave() { save_disable++; }
DisableSave::~DisableSave() { reset(); }
@@ -61,11 +73,5 @@ void DisableSave::reset() {
}
}
-namespace internal {
-bool ShouldSave() {
- return CooperativeSaveEnabled() && (save_disable.load() == 0);
-}
-} // namespace internal
-
} // namespace testing
} // namespace gvisor
diff --git a/test/util/save_util.h b/test/util/save_util.h
index bddad6120..e7218ae88 100644
--- a/test/util/save_util.h
+++ b/test/util/save_util.h
@@ -17,9 +17,17 @@
namespace gvisor {
namespace testing {
-// Disable save prevents saving while the given function executes.
+
+// Returns true if the environment in which the calling process is executing
+// allows the test to be checkpointed and restored during execution.
+bool IsRunningWithSaveRestore();
+
+// May perform a co-operative save cycle.
//
-// This lasts the duration of the object, unless reset is called.
+// errno is guaranteed to be preserved.
+void MaybeSave();
+
+// Causes MaybeSave to become a no-op until destroyed or reset.
class DisableSave {
public:
DisableSave();
@@ -37,13 +45,13 @@ class DisableSave {
bool reset_ = false;
};
-// May perform a co-operative save cycle.
+namespace internal {
+
+// Causes a co-operative save cycle to occur.
//
// errno is guaranteed to be preserved.
-void MaybeSave();
+void DoCooperativeSave();
-namespace internal {
-bool ShouldSave();
} // namespace internal
} // namespace testing
diff --git a/test/util/save_util_linux.cc b/test/util/save_util_linux.cc
index fbac94912..57431b3ea 100644
--- a/test/util/save_util_linux.cc
+++ b/test/util/save_util_linux.cc
@@ -30,19 +30,19 @@
namespace gvisor {
namespace testing {
-
-void MaybeSave() {
- if (internal::ShouldSave()) {
- int orig_errno = errno;
- // We use it to trigger saving the sentry state
- // when this syscall is called.
- // Notice: this needs to be a valid syscall
- // that is not used in any of the syscall tests.
- syscall(SYS_TRIGGER_SAVE, nullptr, 0);
- errno = orig_errno;
- }
+namespace internal {
+
+void DoCooperativeSave() {
+ int orig_errno = errno;
+ // We use it to trigger saving the sentry state
+ // when this syscall is called.
+ // Notice: this needs to be a valid syscall
+ // that is not used in any of the syscall tests.
+ syscall(SYS_TRIGGER_SAVE, nullptr, 0);
+ errno = orig_errno;
}
+} // namespace internal
} // namespace testing
} // namespace gvisor
diff --git a/test/util/save_util_other.cc b/test/util/save_util_other.cc
index 931af2c29..7749ded76 100644
--- a/test/util/save_util_other.cc
+++ b/test/util/save_util_other.cc
@@ -14,13 +14,17 @@
#ifndef __linux__
+#include "test/util/logging.h"
+
namespace gvisor {
namespace testing {
+namespace internal {
-void MaybeSave() {
- // Saving is never available in a non-linux environment.
+void DoCooperativeSave() {
+ TEST_CHECK_MSG(false, "DoCooperativeSave not implemented");
}
+} // namespace internal
} // namespace testing
} // namespace gvisor
diff --git a/test/util/signal_util.h b/test/util/signal_util.h
index e7b66aa51..20eebd7e4 100644
--- a/test/util/signal_util.h
+++ b/test/util/signal_util.h
@@ -88,7 +88,7 @@ inline void FixupFault(ucontext_t* ctx) {
#elif __aarch64__
inline void Fault() {
// Zero and dereference x0.
- asm("mov xzr, x0\r\n"
+ asm("mov x0, xzr\r\n"
"str xzr, [x0]\r\n"
:
:
diff --git a/test/util/test_util.h b/test/util/test_util.h
index 373c54f32..876ff58db 100644
--- a/test/util/test_util.h
+++ b/test/util/test_util.h
@@ -171,6 +171,7 @@
#include <stddef.h>
#include <stdlib.h>
#include <sys/uio.h>
+#include <time.h>
#include <unistd.h>
#include <algorithm>
@@ -271,6 +272,12 @@ PosixErrorOr<std::vector<OpenFd>> GetOpenFDs();
// Returns the number of hard links to a path.
PosixErrorOr<uint64_t> Links(const std::string& path);
+inline uint64_t ms_elapsed(const struct timespec& begin,
+ const struct timespec& end) {
+ return (end.tv_sec - begin.tv_sec) * 1000 +
+ (end.tv_nsec - begin.tv_nsec) / 1000000;
+}
+
namespace internal {
template <typename Container>
diff --git a/test/util/timer_util.h b/test/util/timer_util.h
index 926e6632f..e389108ef 100644
--- a/test/util/timer_util.h
+++ b/test/util/timer_util.h
@@ -33,6 +33,11 @@
namespace gvisor {
namespace testing {
+// From Linux's include/uapi/asm-generic/siginfo.h.
+#ifndef sigev_notify_thread_id
+#define sigev_notify_thread_id _sigev_un._tid
+#endif
+
// Returns the current time.
absl::Time Now(clockid_t id);