summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/benchmarks/database/redis_test.go82
-rw-r--r--test/cmd/test_app/BUILD2
-rw-r--r--test/cmd/test_app/main.go (renamed from test/cmd/test_app/test_app.go)20
-rw-r--r--test/fuse/BUILD140
-rw-r--r--test/packetimpact/runner/defs.bzl13
-rw-r--r--test/packetimpact/runner/dut.go5
-rw-r--r--test/packetimpact/testbench/BUILD2
-rw-r--r--test/packetimpact/testbench/connections.go54
-rw-r--r--test/packetimpact/testbench/dut.go114
-rw-r--r--test/packetimpact/testbench/layers.go71
-rw-r--r--test/packetimpact/testbench/layers_test.go2
-rw-r--r--test/packetimpact/testbench/rawsockets.go20
-rw-r--r--test/packetimpact/testbench/testbench.go26
-rw-r--r--test/packetimpact/tests/BUILD33
-rw-r--r--test/packetimpact/tests/generic_dgram_socket_send_recv_test.go781
-rw-r--r--test/packetimpact/tests/icmpv6_param_problem_test.go7
-rw-r--r--test/packetimpact/tests/ipv6_unknown_options_action_test.go20
-rw-r--r--test/packetimpact/tests/tcp_connect_icmp_error_test.go54
-rw-r--r--test/packetimpact/tests/tcp_info_test.go49
-rw-r--r--test/packetimpact/tests/tcp_linger_test.go82
-rw-r--r--test/packetimpact/tests/tcp_listen_backlog_test.go14
-rw-r--r--test/packetimpact/tests/tcp_network_unreachable_test.go65
-rw-r--r--test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go96
-rw-r--r--test/packetimpact/tests/tcp_rack_test.go16
-rw-r--r--test/packetimpact/tests/tcp_retransmits_test.go10
-rw-r--r--test/packetimpact/tests/tcp_syncookie_test.go143
-rw-r--r--test/packetimpact/tests/tcp_synsent_reset_test.go19
-rw-r--r--test/packetimpact/tests/udp_icmp_error_propagation_test.go48
-rw-r--r--test/packetimpact/tests/udp_send_recv_dgram_test.go329
-rw-r--r--test/runner/BUILD3
-rw-r--r--test/runner/defs.bzl3
-rw-r--r--test/runner/main.go (renamed from test/runner/runner.go)50
-rw-r--r--test/runner/setup_container/BUILD19
-rw-r--r--test/runner/setup_container/setup_container.cc79
-rw-r--r--test/syscalls/BUILD12
-rw-r--r--test/syscalls/linux/BUILD83
-rw-r--r--test/syscalls/linux/accept_bind.cc15
-rw-r--r--test/syscalls/linux/cgroup.cc35
-rw-r--r--test/syscalls/linux/concurrency.cc3
-rw-r--r--test/syscalls/linux/epoll.cc4
-rw-r--r--test/syscalls/linux/exec.cc4
-rw-r--r--test/syscalls/linux/exec_state_workload.cc4
-rw-r--r--test/syscalls/linux/ip_socket_test_util.cc24
-rw-r--r--test/syscalls/linux/ip_socket_test_util.h16
-rw-r--r--test/syscalls/linux/lseek.cc2
-rw-r--r--test/syscalls/linux/packet_socket.cc14
-rw-r--r--test/syscalls/linux/packet_socket_raw.cc14
-rw-r--r--test/syscalls/linux/ping_socket.cc221
-rw-r--r--test/syscalls/linux/pipe.cc50
-rw-r--r--test/syscalls/linux/poll.cc4
-rw-r--r--test/syscalls/linux/prctl.cc9
-rw-r--r--test/syscalls/linux/priority.cc23
-rw-r--r--test/syscalls/linux/proc.cc9
-rw-r--r--test/syscalls/linux/ptrace.cc458
-rw-r--r--test/syscalls/linux/pty.cc30
-rw-r--r--test/syscalls/linux/raw_socket_hdrincl.cc21
-rw-r--r--test/syscalls/linux/raw_socket_icmp.cc4
-rw-r--r--test/syscalls/linux/read.cc3
-rw-r--r--test/syscalls/linux/rename.cc56
-rw-r--r--test/syscalls/linux/semaphore.cc17
-rw-r--r--test/syscalls/linux/setgid.cc8
-rw-r--r--test/syscalls/linux/sigstop.cc49
-rw-r--r--test/syscalls/linux/socket_bind_to_device_distribution.cc28
-rw-r--r--test/syscalls/linux/socket_bind_to_device_util.cc5
-rw-r--r--test/syscalls/linux/socket_inet_loopback.cc896
-rw-r--r--test/syscalls/linux/socket_inet_loopback_isolated.cc489
-rw-r--r--test/syscalls/linux/socket_inet_loopback_nogotsan.cc96
-rw-r--r--test/syscalls/linux/socket_inet_loopback_test_params.h86
-rw-r--r--test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc4
-rw-r--r--test/syscalls/linux/socket_netdevice.cc5
-rw-r--r--test/syscalls/linux/socket_test_util.cc234
-rw-r--r--test/syscalls/linux/socket_test_util.h42
-rw-r--r--test/syscalls/linux/tcp_socket.cc56
-rw-r--r--test/syscalls/linux/timers.cc7
-rw-r--r--test/syscalls/linux/tuntap.cc13
-rw-r--r--test/syscalls/linux/udp_socket.cc12
-rw-r--r--test/syscalls/linux/uidgid.cc4
-rw-r--r--test/syscalls/linux/uname.cc3
-rw-r--r--test/syscalls/linux/verity_getdents.cc95
-rw-r--r--test/syscalls/linux/verity_mount.cc8
-rw-r--r--test/util/posix_error.h10
81 files changed, 3639 insertions, 2117 deletions
diff --git a/test/benchmarks/database/redis_test.go b/test/benchmarks/database/redis_test.go
index f3c4522ac..7497f69b3 100644
--- a/test/benchmarks/database/redis_test.go
+++ b/test/benchmarks/database/redis_test.go
@@ -1,6 +1,6 @@
-// Copyright 2020 The gVisor Authors.
+// Copyright 2021 The gVisor Authors.
//
-// Licensed under the Apache License, Version 2.0 (the "License");
+// Licensed under the Apache License, Version 3.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
@@ -17,6 +17,7 @@ package database
import (
"context"
"os"
+ "strings"
"testing"
"time"
@@ -65,6 +66,34 @@ func BenchmarkRedis(b *testing.B) {
// Redis runs on port 6379 by default.
port := 6379
ctx := context.Background()
+ server := serverMachine.GetContainer(ctx, b)
+ defer server.CleanUp(ctx)
+
+ // The redis docker container takes no arguments to run a redis server.
+ if err := server.Spawn(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/redis",
+ Ports: []int{port},
+ }); err != nil {
+ b.Fatalf("failed to start redis server with: %v", err)
+ }
+
+ if out, err := server.WaitForOutput(ctx, "Ready to accept connections", 3*time.Second); err != nil {
+ b.Fatalf("failed to start redis server: %v %s", err, out)
+ }
+
+ ip, err := serverMachine.IPAddress()
+ if err != nil {
+ b.Fatalf("failed to get IP from server: %v", err)
+ }
+
+ serverPort, err := server.FindPort(ctx, port)
+ if err != nil {
+ b.Fatalf("failed to get IP from server: %v", err)
+ }
+
+ if err = harness.WaitUntilServing(ctx, clientMachine, ip, serverPort); err != nil {
+ b.Fatalf("failed to start redis with: %v", err)
+ }
for _, operation := range operations {
param := tools.Parameter{
Name: "operation",
@@ -74,49 +103,30 @@ func BenchmarkRedis(b *testing.B) {
if err != nil {
b.Fatalf("Failed to parse paramaters: %v", err)
}
- b.Run(name, func(b *testing.B) {
- server := serverMachine.GetContainer(ctx, b)
- defer server.CleanUp(ctx)
-
- // The redis docker container takes no arguments to run a redis server.
- if err := server.Spawn(ctx, dockerutil.RunOpts{
- Image: "benchmarks/redis",
- Ports: []int{port},
- }); err != nil {
- b.Fatalf("failed to start redis server with: %v", err)
- }
- if out, err := server.WaitForOutput(ctx, "Ready to accept connections", 3*time.Second); err != nil {
- b.Fatalf("failed to start redis server: %v %s", err, out)
+ b.Run(name, func(b *testing.B) {
+ redis := tools.Redis{
+ Operation: operation,
}
- ip, err := serverMachine.IPAddress()
- if err != nil {
- b.Fatalf("failed to get IP from server: %v", err)
- }
+ // Sometimes, the connection between the redis client and server can be
+ // flaky such that the client returns infinity as the QPS measurement for
+ // a give operation. If this happens, retry the client up to 3 times.
+ out := "inf"
+ for retries := 0; strings.Contains(out, "inf") && retries < 3; retries++ {
+ b.ResetTimer()
+ client := clientMachine.GetNativeContainer(ctx, b)
+ defer client.CleanUp(ctx)
- serverPort, err := server.FindPort(ctx, port)
- if err != nil {
- b.Fatalf("failed to get IP from server: %v", err)
+ out, err = client.Run(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/redis",
+ }, redis.MakeCmd(ip, serverPort, b.N /*requests*/)...)
}
- if err = harness.WaitUntilServing(ctx, clientMachine, ip, serverPort); err != nil {
- b.Fatalf("failed to start redis with: %v", err)
- }
-
- client := clientMachine.GetNativeContainer(ctx, b)
- defer client.CleanUp(ctx)
-
- redis := tools.Redis{
- Operation: operation,
- }
- b.ResetTimer()
- 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)
}
+
b.StopTimer()
redis.Report(b, out)
})
diff --git a/test/cmd/test_app/BUILD b/test/cmd/test_app/BUILD
index 98ba5a3d9..7b8b23b4d 100644
--- a/test/cmd/test_app/BUILD
+++ b/test/cmd/test_app/BUILD
@@ -7,7 +7,7 @@ go_binary(
testonly = 1,
srcs = [
"fds.go",
- "test_app.go",
+ "main.go",
],
pure = True,
visibility = ["//runsc/container:__pkg__"],
diff --git a/test/cmd/test_app/test_app.go b/test/cmd/test_app/main.go
index 3ba4f38f8..2d08ce2db 100644
--- a/test/cmd/test_app/test_app.go
+++ b/test/cmd/test_app/main.go
@@ -160,14 +160,17 @@ func (c *taskTree) SetFlags(f *flag.FlagSet) {
// Execute implements subcommands.Command.
func (c *taskTree) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
- stop := testutil.StartReaper()
- defer stop()
-
if c.depth == 0 {
log.Printf("Child sleeping, PID: %d\n", os.Getpid())
- select {}
+ for {
+ time.Sleep(time.Hour)
+ }
}
- log.Printf("Parent %d sleeping, PID: %d\n", c.depth, os.Getpid())
+
+ log.Printf("Parent %d creating %d children, PID: %d\n", c.depth, c.width, os.Getpid())
+
+ stop := testutil.StartReaper()
+ defer stop()
var cmds []*exec.Cmd
for i := 0; i < c.width; i++ {
@@ -175,7 +178,7 @@ func (c *taskTree) Execute(ctx context.Context, f *flag.FlagSet, args ...interfa
"/proc/self/exe", c.Name(),
"--depth", strconv.Itoa(c.depth-1),
"--width", strconv.Itoa(c.width),
- "--pause", strconv.FormatBool(c.pause))
+ fmt.Sprintf("--pause=%t", c.pause))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@@ -190,7 +193,10 @@ func (c *taskTree) Execute(ctx context.Context, f *flag.FlagSet, args ...interfa
}
if c.pause {
- select {}
+ log.Printf("Parent %d sleeping, PID: %d\n", c.depth, os.Getpid())
+ for {
+ time.Sleep(time.Hour)
+ }
}
return subcommands.ExitSuccess
diff --git a/test/fuse/BUILD b/test/fuse/BUILD
index 74500ec84..2885ffdb2 100644
--- a/test/fuse/BUILD
+++ b/test/fuse/BUILD
@@ -2,75 +2,77 @@ load("//test/runner:defs.bzl", "syscall_test")
package(licenses = ["notice"])
-syscall_test(
- fuse = "True",
- test = "//test/fuse/linux:stat_test",
-)
-
-syscall_test(
- fuse = "True",
- test = "//test/fuse/linux:open_test",
-)
-
-syscall_test(
- fuse = "True",
- test = "//test/fuse/linux:release_test",
-)
-
-syscall_test(
- fuse = "True",
- test = "//test/fuse/linux:mknod_test",
-)
-
-syscall_test(
- fuse = "True",
- test = "//test/fuse/linux:symlink_test",
-)
-
-syscall_test(
- fuse = "True",
- test = "//test/fuse/linux:readlink_test",
-)
-
-syscall_test(
- fuse = "True",
- test = "//test/fuse/linux:mkdir_test",
-)
-
-syscall_test(
- fuse = "True",
- test = "//test/fuse/linux:read_test",
-)
-
-syscall_test(
- fuse = "True",
- test = "//test/fuse/linux:write_test",
-)
-
-syscall_test(
- fuse = "True",
- test = "//test/fuse/linux:rmdir_test",
-)
-
-syscall_test(
- fuse = "True",
- test = "//test/fuse/linux:readdir_test",
-)
-
-syscall_test(
- fuse = "True",
- test = "//test/fuse/linux:create_test",
-)
-
-syscall_test(
- fuse = "True",
- test = "//test/fuse/linux:unlink_test",
-)
-
-syscall_test(
- fuse = "True",
- test = "//test/fuse/linux:setstat_test",
-)
+# FIXME(b/190750110)
+#
+# syscall_test(
+# fuse = "True",
+# test = "//test/fuse/linux:stat_test",
+# )
+#
+# syscall_test(
+# fuse = "True",
+# test = "//test/fuse/linux:open_test",
+# )
+#
+# syscall_test(
+# fuse = "True",
+# test = "//test/fuse/linux:release_test",
+# )
+#
+# syscall_test(
+# fuse = "True",
+# test = "//test/fuse/linux:mknod_test",
+# )
+#
+# syscall_test(
+# fuse = "True",
+# test = "//test/fuse/linux:symlink_test",
+# )
+#
+# syscall_test(
+# fuse = "True",
+# test = "//test/fuse/linux:readlink_test",
+# )
+#
+# syscall_test(
+# fuse = "True",
+# test = "//test/fuse/linux:mkdir_test",
+# )
+#
+# syscall_test(
+# fuse = "True",
+# test = "//test/fuse/linux:read_test",
+# )
+#
+# syscall_test(
+# fuse = "True",
+# test = "//test/fuse/linux:write_test",
+# )
+#
+# syscall_test(
+# fuse = "True",
+# test = "//test/fuse/linux:rmdir_test",
+# )
+#
+# syscall_test(
+# fuse = "True",
+# test = "//test/fuse/linux:readdir_test",
+# )
+#
+# syscall_test(
+# fuse = "True",
+# test = "//test/fuse/linux:create_test",
+# )
+#
+# syscall_test(
+# fuse = "True",
+# test = "//test/fuse/linux:unlink_test",
+# )
+#
+# syscall_test(
+# fuse = "True",
+# test = "//test/fuse/linux:setstat_test",
+# )
syscall_test(
fuse = "True",
diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl
index afe73a69a..19f6cc0e0 100644
--- a/test/packetimpact/runner/defs.bzl
+++ b/test/packetimpact/runner/defs.bzl
@@ -115,7 +115,7 @@ def packetimpact_netstack_test(
**kwargs
)
-def packetimpact_go_test(name, expect_native_failure = False, expect_netstack_failure = False, num_duts = 1):
+def packetimpact_go_test(name, expect_native_failure = False, expect_netstack_failure = False, num_duts = 1, **kwargs):
"""Add packetimpact tests written in go.
Args:
@@ -123,6 +123,7 @@ def packetimpact_go_test(name, expect_native_failure = False, expect_netstack_fa
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
+ **kwargs: all the other args, forwarded to packetimpact_native_test and packetimpact_netstack_test
"""
testbench_binary = name + "_test"
packetimpact_native_test(
@@ -130,12 +131,14 @@ def packetimpact_go_test(name, expect_native_failure = False, expect_netstack_fa
expect_failure = expect_native_failure,
num_duts = num_duts,
testbench_binary = testbench_binary,
+ **kwargs
)
packetimpact_netstack_test(
name = name,
expect_failure = expect_netstack_failure,
num_duts = num_duts,
testbench_binary = testbench_binary,
+ **kwargs
)
def packetimpact_testbench(name, size = "small", pure = True, **kwargs):
@@ -163,6 +166,7 @@ PacketimpactTestInfo = provider(
doc = "Provide information for packetimpact tests",
fields = [
"name",
+ "timeout",
"expect_netstack_failure",
"num_duts",
],
@@ -271,9 +275,6 @@ ALL_TESTS = [
num_duts = 3,
),
PacketimpactTestInfo(
- name = "udp_send_recv_dgram",
- ),
- PacketimpactTestInfo(
name = "tcp_linger",
),
PacketimpactTestInfo(
@@ -289,6 +290,10 @@ ALL_TESTS = [
PacketimpactTestInfo(
name = "tcp_fin_retransmission",
),
+ PacketimpactTestInfo(
+ name = "generic_dgram_socket_send_recv",
+ timeout = "long",
+ ),
]
def validate_all_tests():
diff --git a/test/packetimpact/runner/dut.go b/test/packetimpact/runner/dut.go
index 4fb2f5c4b..02678a76a 100644
--- a/test/packetimpact/runner/dut.go
+++ b/test/packetimpact/runner/dut.go
@@ -363,6 +363,9 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co
// and receives packets and also sends POSIX socket commands to the
// posix_server to be executed on the DUT.
testArgs := []string{containerTestbenchBinary}
+ if testing.Verbose() {
+ testArgs = append(testArgs, "-test.v")
+ }
testArgs = append(testArgs, extraTestArgs...)
testArgs = append(testArgs,
fmt.Sprintf("--native=%t", native),
@@ -395,6 +398,8 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co
} else if expectFailure {
t.Logf(`test failed as expected: %v
%s`, err, testLogs)
+ } else if testing.Verbose() {
+ t.Log(testLogs)
}
}
diff --git a/test/packetimpact/testbench/BUILD b/test/packetimpact/testbench/BUILD
index 616215dc3..d8059ab98 100644
--- a/test/packetimpact/testbench/BUILD
+++ b/test/packetimpact/testbench/BUILD
@@ -16,6 +16,8 @@ go_library(
],
visibility = ["//test/packetimpact:__subpackages__"],
deps = [
+ "//pkg/abi/linux",
+ "//pkg/binary",
"//pkg/hostarch",
"//pkg/tcpip",
"//pkg/tcpip/buffer",
diff --git a/test/packetimpact/testbench/connections.go b/test/packetimpact/testbench/connections.go
index 8ad9040ff..ed56f9ac7 100644
--- a/test/packetimpact/testbench/connections.go
+++ b/test/packetimpact/testbench/connections.go
@@ -594,32 +594,50 @@ func (conn *Connection) Expect(t *testing.T, layer Layer, timeout time.Duration)
func (conn *Connection) ExpectFrame(t *testing.T, layers Layers, timeout time.Duration) (Layers, error) {
t.Helper()
- deadline := time.Now().Add(timeout)
+ frames, ok := conn.ListenForFrame(t, layers, timeout)
+ if ok {
+ return frames[len(frames)-1], nil
+ }
+ if len(frames) == 0 {
+ return nil, fmt.Errorf("got no frames matching %s during %s", layers, timeout)
+ }
+
var errs error
+ for _, got := range frames {
+ want := conn.incoming(layers)
+ if err := want.merge(layers); err != nil {
+ errs = multierr.Combine(errs, err)
+ } else {
+ errs = multierr.Combine(errs, &layersError{got: got, want: want})
+ }
+ }
+ return nil, fmt.Errorf("got frames:\n%w want %s during %s", errs, layers, timeout)
+}
+
+// ListenForFrame captures all frames until a frame matches the provided Layers,
+// or until the timeout specified. Returns all captured frames, including the
+// matched frame, and true if the desired frame was found.
+func (conn *Connection) ListenForFrame(t *testing.T, layers Layers, timeout time.Duration) ([]Layers, bool) {
+ t.Helper()
+
+ deadline := time.Now().Add(timeout)
+ var frames []Layers
for {
- var gotLayers Layers
+ var got Layers
if timeout := time.Until(deadline); timeout > 0 {
- gotLayers = conn.recvFrame(t, timeout)
+ got = conn.recvFrame(t, timeout)
}
- if gotLayers == nil {
- if errs == nil {
- return nil, fmt.Errorf("got no frames matching %s during %s", layers, timeout)
- }
- return nil, fmt.Errorf("got frames:\n%w want %s during %s", errs, layers, timeout)
+ if got == nil {
+ return frames, false
}
- if conn.match(layers, gotLayers) {
+ frames = append(frames, got)
+ if conn.match(layers, got) {
for i, s := range conn.layerStates {
- if err := s.received(gotLayers[i]); err != nil {
+ if err := s.received(got[i]); err != nil {
t.Fatalf("failed to update test connection's layer states based on received frame: %s", err)
}
}
- return gotLayers, nil
- }
- 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})
+ return frames, true
}
}
}
@@ -1025,6 +1043,8 @@ func (conn *UDPIPv4) SendIP(t *testing.T, ip IPv4, udp UDP, additionalLayers ...
// SendFrame sends a frame on the wire and updates the state of all layers.
func (conn *UDPIPv4) SendFrame(t *testing.T, overrideLayers Layers, additionalLayers ...Layer) {
+ t.Helper()
+
conn.send(t, overrideLayers, additionalLayers...)
}
diff --git a/test/packetimpact/testbench/dut.go b/test/packetimpact/testbench/dut.go
index eabdc8cb3..7e89ba2b3 100644
--- a/test/packetimpact/testbench/dut.go
+++ b/test/packetimpact/testbench/dut.go
@@ -22,11 +22,13 @@ import (
"testing"
"time"
- pb "gvisor.dev/gvisor/test/packetimpact/proto/posix_server_go_proto"
-
"golang.org/x/sys/unix"
"google.golang.org/grpc"
"google.golang.org/grpc/keepalive"
+ "gvisor.dev/gvisor/pkg/abi/linux"
+ bin "gvisor.dev/gvisor/pkg/binary"
+ "gvisor.dev/gvisor/pkg/hostarch"
+ pb "gvisor.dev/gvisor/test/packetimpact/proto/posix_server_go_proto"
)
// DUT communicates with the DUT to force it to make POSIX calls.
@@ -178,9 +180,7 @@ func (dut *DUT) CreateListener(t *testing.T, typ, proto, backlog int32) (int32,
func (dut *DUT) Accept(t *testing.T, sockfd int32) (int32, unix.Sockaddr) {
t.Helper()
- ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
- defer cancel()
- fd, sa, err := dut.AcceptWithErrno(ctx, t, sockfd)
+ fd, sa, err := dut.AcceptWithErrno(context.Background(), t, sockfd)
if fd < 0 {
t.Fatalf("failed to accept: %s", err)
}
@@ -207,9 +207,7 @@ func (dut *DUT) AcceptWithErrno(ctx context.Context, t *testing.T, sockfd int32)
func (dut *DUT) Bind(t *testing.T, fd int32, sa unix.Sockaddr) {
t.Helper()
- ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
- defer cancel()
- ret, err := dut.BindWithErrno(ctx, t, fd, sa)
+ ret, err := dut.BindWithErrno(context.Background(), t, fd, sa)
if ret != 0 {
t.Fatalf("failed to bind socket: %s", err)
}
@@ -236,9 +234,7 @@ func (dut *DUT) BindWithErrno(ctx context.Context, t *testing.T, fd int32, sa un
func (dut *DUT) Close(t *testing.T, fd int32) {
t.Helper()
- ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
- defer cancel()
- ret, err := dut.CloseWithErrno(ctx, t, fd)
+ ret, err := dut.CloseWithErrno(context.Background(), t, fd)
if ret != 0 {
t.Fatalf("failed to close: %s", err)
}
@@ -264,9 +260,7 @@ func (dut *DUT) CloseWithErrno(ctx context.Context, t *testing.T, fd int32) (int
func (dut *DUT) Connect(t *testing.T, fd int32, sa unix.Sockaddr) {
t.Helper()
- ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
- defer cancel()
- ret, err := dut.ConnectWithErrno(ctx, t, fd, sa)
+ ret, err := dut.ConnectWithErrno(context.Background(), t, fd, sa)
// Ignore 'operation in progress' error that can be returned when the socket
// is non-blocking.
if err != unix.EINPROGRESS && ret != 0 {
@@ -295,9 +289,7 @@ func (dut *DUT) ConnectWithErrno(ctx context.Context, t *testing.T, fd int32, sa
func (dut *DUT) GetSockName(t *testing.T, sockfd int32) unix.Sockaddr {
t.Helper()
- ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
- defer cancel()
- ret, sa, err := dut.GetSockNameWithErrno(ctx, t, sockfd)
+ ret, sa, err := dut.GetSockNameWithErrno(context.Background(), t, sockfd)
if ret != 0 {
t.Fatalf("failed to getsockname: %s", err)
}
@@ -347,9 +339,7 @@ func (dut *DUT) getSockOpt(ctx context.Context, t *testing.T, sockfd, level, opt
func (dut *DUT) GetSockOpt(t *testing.T, sockfd, level, optname, optlen int32) []byte {
t.Helper()
- ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
- defer cancel()
- ret, optval, err := dut.GetSockOptWithErrno(ctx, t, sockfd, level, optname, optlen)
+ ret, optval, err := dut.GetSockOptWithErrno(context.Background(), t, sockfd, level, optname, optlen)
if ret != 0 {
t.Fatalf("failed to GetSockOpt: %s", err)
}
@@ -376,9 +366,7 @@ func (dut *DUT) GetSockOptWithErrno(ctx context.Context, t *testing.T, sockfd, l
func (dut *DUT) GetSockOptInt(t *testing.T, sockfd, level, optname int32) int32 {
t.Helper()
- ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
- defer cancel()
- ret, intval, err := dut.GetSockOptIntWithErrno(ctx, t, sockfd, level, optname)
+ ret, intval, err := dut.GetSockOptIntWithErrno(context.Background(), t, sockfd, level, optname)
if ret != 0 {
t.Fatalf("failed to GetSockOptInt: %s", err)
}
@@ -403,9 +391,7 @@ func (dut *DUT) GetSockOptIntWithErrno(ctx context.Context, t *testing.T, sockfd
func (dut *DUT) GetSockOptTimeval(t *testing.T, sockfd, level, optname int32) unix.Timeval {
t.Helper()
- ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
- defer cancel()
- ret, timeval, err := dut.GetSockOptTimevalWithErrno(ctx, t, sockfd, level, optname)
+ ret, timeval, err := dut.GetSockOptTimevalWithErrno(context.Background(), t, sockfd, level, optname)
if ret != 0 {
t.Fatalf("failed to GetSockOptTimeval: %s", err)
}
@@ -428,15 +414,38 @@ func (dut *DUT) GetSockOptTimevalWithErrno(ctx context.Context, t *testing.T, so
return ret, timeval, errno
}
+// GetSockOptTCPInfo retreives TCPInfo for the given socket descriptor.
+func (dut *DUT) GetSockOptTCPInfo(t *testing.T, sockfd int32) linux.TCPInfo {
+ t.Helper()
+
+ ret, info, err := dut.GetSockOptTCPInfoWithErrno(context.Background(), t, sockfd)
+ if ret != 0 || err != unix.Errno(0) {
+ t.Fatalf("failed to GetSockOptTCPInfo: %s", err)
+ }
+ return info
+}
+
+// GetSockOptTCPInfoWithErrno retreives TCPInfo with any errno.
+func (dut *DUT) GetSockOptTCPInfoWithErrno(ctx context.Context, t *testing.T, sockfd int32) (int32, linux.TCPInfo, error) {
+ t.Helper()
+
+ info := linux.TCPInfo{}
+ ret, infoBytes, errno := dut.GetSockOptWithErrno(ctx, t, sockfd, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo))
+ if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want {
+ t.Fatalf("expected %T, got %d bytes want %d bytes", info, got, want)
+ }
+ bin.Unmarshal(infoBytes, hostarch.ByteOrder, &info)
+
+ return ret, info, errno
+}
+
// Listen calls listen on the DUT and causes a fatal test failure if it doesn't
// succeed. If more control over the timeout or error handling is needed, use
// ListenWithErrno.
func (dut *DUT) Listen(t *testing.T, sockfd, backlog int32) {
t.Helper()
- ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
- defer cancel()
- ret, err := dut.ListenWithErrno(ctx, t, sockfd, backlog)
+ ret, err := dut.ListenWithErrno(context.Background(), t, sockfd, backlog)
if ret != 0 {
t.Fatalf("failed to listen: %s", err)
}
@@ -469,8 +478,8 @@ func (dut *DUT) PollOne(t *testing.T, fd int32, events int16, timeout time.Durat
if readyFd := pfds[0].Fd; readyFd != fd {
t.Fatalf("Poll returned an fd %d that was not requested (%d)", readyFd, fd)
}
- if got, want := pfds[0].Revents, int16(events); got&want == 0 {
- t.Fatalf("Poll returned no events in our interest, got: %#b, want: %#b", got, want)
+ if got, want := pfds[0].Revents, int16(events); got&want != want {
+ t.Fatalf("Poll returned events does not include all of the interested events, got: %#b, want: %#b", got, want)
}
}
@@ -481,13 +490,7 @@ func (dut *DUT) PollOne(t *testing.T, fd int32, events int16, timeout time.Durat
func (dut *DUT) Poll(t *testing.T, pfds []unix.PollFd, timeout time.Duration) []unix.PollFd {
t.Helper()
- ctx := context.Background()
- var cancel context.CancelFunc
- if timeout >= 0 {
- ctx, cancel = context.WithTimeout(ctx, timeout+RPCTimeout)
- defer cancel()
- }
- ret, result, err := dut.PollWithErrno(ctx, t, pfds, timeout)
+ ret, result, err := dut.PollWithErrno(context.Background(), t, pfds, timeout)
if ret < 0 {
t.Fatalf("failed to poll: %s", err)
}
@@ -530,9 +533,7 @@ func (dut *DUT) PollWithErrno(ctx context.Context, t *testing.T, pfds []unix.Pol
func (dut *DUT) Send(t *testing.T, sockfd int32, buf []byte, flags int32) int32 {
t.Helper()
- ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
- defer cancel()
- ret, err := dut.SendWithErrno(ctx, t, sockfd, buf, flags)
+ ret, err := dut.SendWithErrno(context.Background(), t, sockfd, buf, flags)
if ret == -1 {
t.Fatalf("failed to send: %s", err)
}
@@ -561,9 +562,7 @@ func (dut *DUT) SendWithErrno(ctx context.Context, t *testing.T, sockfd int32, b
func (dut *DUT) SendTo(t *testing.T, sockfd int32, buf []byte, flags int32, destAddr unix.Sockaddr) int32 {
t.Helper()
- ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
- defer cancel()
- ret, err := dut.SendToWithErrno(ctx, t, sockfd, buf, flags, destAddr)
+ ret, err := dut.SendToWithErrno(context.Background(), t, sockfd, buf, flags, destAddr)
if ret == -1 {
t.Fatalf("failed to sendto: %s", err)
}
@@ -596,10 +595,8 @@ func (dut *DUT) SetNonBlocking(t *testing.T, fd int32, nonblocking bool) {
Fd: fd,
Nonblocking: nonblocking,
}
- ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
- defer cancel()
- resp, err := dut.posixServer.SetNonblocking(ctx, req)
+ resp, err := dut.posixServer.SetNonblocking(context.Background(), req)
if err != nil {
t.Fatalf("failed to call SetNonblocking: %s", err)
}
@@ -632,9 +629,7 @@ func (dut *DUT) setSockOpt(ctx context.Context, t *testing.T, sockfd, level, opt
func (dut *DUT) SetSockOpt(t *testing.T, sockfd, level, optname int32, optval []byte) {
t.Helper()
- ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
- defer cancel()
- ret, err := dut.SetSockOptWithErrno(ctx, t, sockfd, level, optname, optval)
+ ret, err := dut.SetSockOptWithErrno(context.Background(), t, sockfd, level, optname, optval)
if ret != 0 {
t.Fatalf("failed to SetSockOpt: %s", err)
}
@@ -655,9 +650,7 @@ func (dut *DUT) SetSockOptWithErrno(ctx context.Context, t *testing.T, sockfd, l
func (dut *DUT) SetSockOptInt(t *testing.T, sockfd, level, optname, optval int32) {
t.Helper()
- ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
- defer cancel()
- ret, err := dut.SetSockOptIntWithErrno(ctx, t, sockfd, level, optname, optval)
+ ret, err := dut.SetSockOptIntWithErrno(context.Background(), t, sockfd, level, optname, optval)
if ret != 0 {
t.Fatalf("failed to SetSockOptInt: %s", err)
}
@@ -676,9 +669,7 @@ func (dut *DUT) SetSockOptIntWithErrno(ctx context.Context, t *testing.T, sockfd
func (dut *DUT) SetSockOptTimeval(t *testing.T, sockfd, level, optname int32, tv *unix.Timeval) {
t.Helper()
- ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
- defer cancel()
- ret, err := dut.SetSockOptTimevalWithErrno(ctx, t, sockfd, level, optname, tv)
+ ret, err := dut.SetSockOptTimevalWithErrno(context.Background(), t, sockfd, level, optname, tv)
if ret != 0 {
t.Fatalf("failed to SetSockOptTimeval: %s", err)
}
@@ -717,8 +708,7 @@ func (dut *DUT) SocketWithErrno(t *testing.T, domain, typ, proto int32) (int32,
Type: typ,
Protocol: proto,
}
- ctx := context.Background()
- resp, err := dut.posixServer.Socket(ctx, req)
+ resp, err := dut.posixServer.Socket(context.Background(), req)
if err != nil {
t.Fatalf("failed to call Socket: %s", err)
}
@@ -731,9 +721,7 @@ func (dut *DUT) SocketWithErrno(t *testing.T, domain, typ, proto int32) (int32,
func (dut *DUT) Recv(t *testing.T, sockfd, len, flags int32) []byte {
t.Helper()
- ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
- defer cancel()
- ret, buf, err := dut.RecvWithErrno(ctx, t, sockfd, len, flags)
+ ret, buf, err := dut.RecvWithErrno(context.Background(), t, sockfd, len, flags)
if ret == -1 {
t.Fatalf("failed to recv: %s", err)
}
@@ -776,9 +764,7 @@ func (dut *DUT) SetSockLingerOption(t *testing.T, sockfd int32, timeout time.Dur
func (dut *DUT) Shutdown(t *testing.T, fd, how int32) {
t.Helper()
- ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
- defer cancel()
- ret, err := dut.ShutdownWithErrno(ctx, t, fd, how)
+ ret, err := dut.ShutdownWithErrno(context.Background(), t, fd, how)
if ret != 0 {
t.Fatalf("failed to shutdown(%d, %d): %s", fd, how, err)
}
diff --git a/test/packetimpact/testbench/layers.go b/test/packetimpact/testbench/layers.go
index 2311f7686..2644b3248 100644
--- a/test/packetimpact/testbench/layers.go
+++ b/test/packetimpact/testbench/layers.go
@@ -357,7 +357,7 @@ func (l *IPv4) ToBytes() ([]byte, error) {
case *ICMPv4:
fields.Protocol = uint8(header.ICMPv4ProtocolNumber)
default:
- // TODO(b/150301488): Support more protocols as needed.
+ // We can add support for more protocols as needed.
return nil, fmt.Errorf("ipv4 header's next layer is unrecognized: %#v", n)
}
}
@@ -824,6 +824,8 @@ type ICMPv6 struct {
Type *header.ICMPv6Type
Code *header.ICMPv6Code
Checksum *uint16
+ Ident *uint16 // Only in Echo Request/Reply.
+ Pointer *uint32 // Only in Parameter Problem.
Payload []byte
}
@@ -835,7 +837,7 @@ func (l *ICMPv6) String() string {
// ToBytes implements Layer.ToBytes.
func (l *ICMPv6) ToBytes() ([]byte, error) {
- b := make([]byte, header.ICMPv6HeaderSize+len(l.Payload))
+ b := make([]byte, header.ICMPv6MinimumSize+len(l.Payload))
h := header.ICMPv6(b)
if l.Type != nil {
h.SetType(*l.Type)
@@ -843,27 +845,34 @@ func (l *ICMPv6) ToBytes() ([]byte, error) {
if l.Code != nil {
h.SetCode(*l.Code)
}
- if n := copy(h.MessageBody(), l.Payload); n != len(l.Payload) {
+ if n := copy(h.Payload(), l.Payload); n != len(l.Payload) {
panic(fmt.Sprintf("copied %d bytes, expected to copy %d bytes", n, len(l.Payload)))
}
+ typ := h.Type()
+ switch typ {
+ case header.ICMPv6EchoRequest, header.ICMPv6EchoReply:
+ if l.Ident != nil {
+ h.SetIdent(*l.Ident)
+ }
+ case header.ICMPv6ParamProblem:
+ if l.Pointer != nil {
+ h.SetTypeSpecific(*l.Pointer)
+ }
+ }
if l.Checksum != nil {
h.SetChecksum(*l.Checksum)
} else {
// It is possible that the ICMPv6 header does not follow the IPv6 header
// immediately, there could be one or more extension headers in between.
- // We need to search forward to find the IPv6 header.
- for prev := l.Prev(); prev != nil; prev = prev.Prev() {
- if ipv6, ok := prev.(*IPv6); ok {
- payload, err := payload(l)
- if err != nil {
- return nil, err
- }
+ // We need to search backwards to find the IPv6 header.
+ for layer := l.Prev(); layer != nil; layer = layer.Prev() {
+ if ipv6, ok := layer.(*IPv6); ok {
h.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
- Header: h,
+ Header: h[:header.ICMPv6PayloadOffset],
Src: *ipv6.SrcAddr,
Dst: *ipv6.DstAddr,
- PayloadCsum: header.ChecksumVV(payload, 0 /* initial */),
- PayloadLen: payload.Size(),
+ PayloadCsum: header.Checksum(l.Payload, 0 /* initial */),
+ PayloadLen: len(l.Payload),
}))
break
}
@@ -884,20 +893,21 @@ func ICMPv6Code(v header.ICMPv6Code) *header.ICMPv6Code {
return &v
}
-// Byte is a helper routine that allocates a new byte value to store
-// v and returns a pointer to it.
-func Byte(v byte) *byte {
- return &v
-}
-
// parseICMPv6 parses the bytes assuming that they start with an ICMPv6 header.
func parseICMPv6(b []byte) (Layer, layerParser) {
h := header.ICMPv6(b)
+ msgType := h.Type()
icmpv6 := ICMPv6{
- Type: ICMPv6Type(h.Type()),
+ Type: ICMPv6Type(msgType),
Code: ICMPv6Code(h.Code()),
Checksum: Uint16(h.Checksum()),
- Payload: h.MessageBody(),
+ Payload: h.Payload(),
+ }
+ switch msgType {
+ case header.ICMPv6EchoRequest, header.ICMPv6EchoReply:
+ icmpv6.Ident = Uint16(h.Ident())
+ case header.ICMPv6ParamProblem:
+ icmpv6.Pointer = Uint32(h.TypeSpecific())
}
return &icmpv6, nil
}
@@ -907,7 +917,7 @@ func (l *ICMPv6) match(other Layer) bool {
}
func (l *ICMPv6) length() int {
- return header.ICMPv6HeaderSize + len(l.Payload)
+ return header.ICMPv6MinimumSize + len(l.Payload)
}
// merge overrides the values in l with the values from other but only in fields
@@ -954,8 +964,8 @@ 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)))
+ if n := copy(h.Payload(), l.Payload); n != len(l.Payload) {
+ panic(fmt.Sprintf("wrong number of bytes copied into h.Payload(): got = %d, want = %d", n, len(l.Payload)))
}
typ := h.Type()
switch typ {
@@ -977,16 +987,7 @@ func (l *ICMPv4) ToBytes() ([]byte, error) {
if l.Checksum != nil {
h.SetChecksum(*l.Checksum)
} 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, header.ChecksumVV(vv, 0 /* initial */)))
+ h.SetChecksum(^header.Checksum(h, 0))
}
return h, nil
@@ -1019,7 +1020,7 @@ func (l *ICMPv4) match(other Layer) bool {
}
func (l *ICMPv4) length() int {
- return header.ICMPv4MinimumSize
+ return header.ICMPv4MinimumSize + len(l.Payload)
}
// merge overrides the values in l with the values from other but only in fields
diff --git a/test/packetimpact/testbench/layers_test.go b/test/packetimpact/testbench/layers_test.go
index 614a5de1e..bc96e0c88 100644
--- a/test/packetimpact/testbench/layers_test.go
+++ b/test/packetimpact/testbench/layers_test.go
@@ -596,7 +596,7 @@ func TestIPv6ExtHdrOptions(t *testing.T) {
Type: ICMPv6Type(header.ICMPv6ParamProblem),
Code: ICMPv6Code(header.ICMPv6ErroneousHeader),
Checksum: Uint16(0x5f98),
- Payload: []byte{0x00, 0x00, 0x00, 0x06},
+ Pointer: Uint32(6),
},
},
},
diff --git a/test/packetimpact/testbench/rawsockets.go b/test/packetimpact/testbench/rawsockets.go
index feeb0888a..6d95c033d 100644
--- a/test/packetimpact/testbench/rawsockets.go
+++ b/test/packetimpact/testbench/rawsockets.go
@@ -17,7 +17,6 @@ package testbench
import (
"encoding/binary"
"fmt"
- "math"
"net"
"testing"
"time"
@@ -81,19 +80,20 @@ func (s *Sniffer) Recv(t *testing.T, timeout time.Duration) []byte {
deadline := time.Now().Add(timeout)
for {
- timeout = deadline.Sub(time.Now())
+ timeout = time.Until(deadline)
if timeout <= 0 {
return nil
}
- whole, frac := math.Modf(timeout.Seconds())
- tv := unix.Timeval{
- Sec: int64(whole),
- Usec: int64(frac * float64(time.Second/time.Microsecond)),
+ usec := timeout.Microseconds()
+ if usec == 0 {
+ // Timeout is less than a microsecond; set usec to 1 to avoid
+ // blocking indefinitely.
+ usec = 1
}
- // 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")
+ const microsInOne = 1e6
+ tv := unix.Timeval{
+ Sec: usec / microsInOne,
+ Usec: usec % microsInOne,
}
if err := unix.SetsockoptTimeval(s.fd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv); err != nil {
t.Fatalf("can't setsockopt SO_RCVTIMEO: %s", err)
diff --git a/test/packetimpact/testbench/testbench.go b/test/packetimpact/testbench/testbench.go
index 37d02365a..38ae9c1d7 100644
--- a/test/packetimpact/testbench/testbench.go
+++ b/test/packetimpact/testbench/testbench.go
@@ -31,8 +31,6 @@ var (
Native = false
// RPCKeepalive is the gRPC keepalive.
RPCKeepalive = 10 * time.Second
- // RPCTimeout is the gRPC timeout.
- RPCTimeout = 100 * time.Millisecond
// dutInfosJSON is the json string that describes information about all the
// duts available to use.
@@ -57,11 +55,21 @@ type DUTUname struct {
OperatingSystem string
}
-// IsLinux returns true if we are running natively on Linux.
+// IsLinux returns true if the DUT is running Linux.
func (n *DUTUname) IsLinux() bool {
return Native && n.OperatingSystem == "GNU/Linux"
}
+// IsGvisor returns true if the DUT is running gVisor.
+func (*DUTUname) IsGvisor() bool {
+ return !Native
+}
+
+// IsFuchsia returns true if the DUT is running Fuchsia.
+func (n *DUTUname) IsFuchsia() bool {
+ return Native && n.OperatingSystem == "Fuchsia"
+}
+
// DUTTestNet describes the test network setup on dut and how the testbench
// should connect with an existing DUT.
type DUTTestNet struct {
@@ -99,12 +107,21 @@ type DUTTestNet struct {
POSIXServerPort uint16
}
+// SubnetBroadcast returns the test network's subnet broadcast address.
+func (n *DUTTestNet) SubnetBroadcast() net.IP {
+ addr := append([]byte(nil), n.RemoteIPv4...)
+ mask := net.CIDRMask(n.IPv4PrefixLength, net.IPv4len*8)
+ for i := range addr {
+ addr[i] |= ^mask[i]
+ }
+ return addr
+}
+
// 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.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(&dutInfosJSON, "dut_infos_json", dutInfosJSON, "json that describes the DUTs")
}
@@ -112,6 +129,7 @@ func registerFlags(fs *flag.FlagSet) {
// 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) {
+ testing.Init()
registerFlags(fs)
flag.Parse()
if err := loadDUTInfos(); err != nil {
diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD
index c4fe293e0..4cff0cf4c 100644
--- a/test/packetimpact/tests/BUILD
+++ b/test/packetimpact/tests/BUILD
@@ -104,8 +104,6 @@ packetimpact_testbench(
srcs = ["tcp_retransmits_test.go"],
deps = [
"//pkg/abi/linux",
- "//pkg/binary",
- "//pkg/hostarch",
"//pkg/tcpip/header",
"//test/packetimpact/testbench",
"@org_golang_x_sys//unix:go_default_library",
@@ -189,6 +187,7 @@ packetimpact_testbench(
name = "tcp_synsent_reset",
srcs = ["tcp_synsent_reset_test.go"],
deps = [
+ "//pkg/abi/linux",
"//pkg/tcpip/header",
"//test/packetimpact/testbench",
"@org_golang_x_sys//unix:go_default_library",
@@ -307,18 +306,6 @@ packetimpact_testbench(
)
packetimpact_testbench(
- name = "udp_send_recv_dgram",
- srcs = ["udp_send_recv_dgram_test.go"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/header",
- "//test/packetimpact/testbench",
- "@com_github_google_go_cmp//cmp:go_default_library",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-packetimpact_testbench(
name = "tcp_linger",
srcs = ["tcp_linger_test.go"],
deps = [
@@ -353,8 +340,6 @@ packetimpact_testbench(
srcs = ["tcp_rack_test.go"],
deps = [
"//pkg/abi/linux",
- "//pkg/binary",
- "//pkg/hostarch",
"//pkg/tcpip/header",
"//pkg/tcpip/seqnum",
"//test/packetimpact/testbench",
@@ -367,8 +352,6 @@ packetimpact_testbench(
srcs = ["tcp_info_test.go"],
deps = [
"//pkg/abi/linux",
- "//pkg/binary",
- "//pkg/hostarch",
"//pkg/tcpip/header",
"//test/packetimpact/testbench",
"@org_golang_x_sys//unix:go_default_library",
@@ -401,6 +384,7 @@ packetimpact_testbench(
deps = [
"//pkg/tcpip/header",
"//test/packetimpact/testbench",
+ "@com_github_google_go_cmp//cmp:go_default_library",
"@org_golang_x_sys//unix:go_default_library",
],
)
@@ -415,10 +399,23 @@ packetimpact_testbench(
],
)
+packetimpact_testbench(
+ name = "generic_dgram_socket_send_recv",
+ srcs = ["generic_dgram_socket_send_recv_test.go"],
+ deps = [
+ "//pkg/tcpip",
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@com_github_google_go_cmp//cmp:go_default_library",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
validate_all_tests()
[packetimpact_go_test(
name = t.name,
+ timeout = t.timeout if hasattr(t, "timeout") else "moderate",
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]
diff --git a/test/packetimpact/tests/generic_dgram_socket_send_recv_test.go b/test/packetimpact/tests/generic_dgram_socket_send_recv_test.go
new file mode 100644
index 000000000..a9ffafc74
--- /dev/null
+++ b/test/packetimpact/tests/generic_dgram_socket_send_recv_test.go
@@ -0,0 +1,781 @@
+// Copyright 2021 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package generic_dgram_socket_send_recv_test
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "net"
+ "testing"
+ "time"
+
+ "github.com/google/go-cmp/cmp"
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+const (
+ // Even though sockets allow larger datagrams we don't test it here as they
+ // need to be fragmented and written out as individual frames.
+
+ maxICMPv4PayloadSize = header.IPv4MinimumMTU - header.EthernetMinimumSize - header.IPv4MinimumSize - header.ICMPv4MinimumSize
+ maxICMPv6PayloadSize = header.IPv6MinimumMTU - header.EthernetMinimumSize - header.IPv6MinimumSize - header.ICMPv6MinimumSize
+ maxUDPv4PayloadSize = header.IPv4MinimumMTU - header.EthernetMinimumSize - header.IPv4MinimumSize - header.UDPMinimumSize
+ maxUDPv6PayloadSize = header.IPv6MinimumMTU - header.EthernetMinimumSize - header.IPv6MinimumSize - header.UDPMinimumSize
+)
+
+func maxUDPPayloadSize(addr net.IP) int {
+ if addr.To4() != nil {
+ return maxUDPv4PayloadSize
+ }
+ return maxUDPv6PayloadSize
+}
+
+func init() {
+ testbench.Initialize(flag.CommandLine)
+}
+
+func expectedEthLayer(t *testing.T, dut testbench.DUT, socketFD int32, sendTo net.IP) testbench.Layer {
+ t.Helper()
+ dst := func() tcpip.LinkAddress {
+ if isBroadcast(dut, sendTo) {
+ dut.SetSockOptInt(t, socketFD, unix.SOL_SOCKET, unix.SO_BROADCAST, 1)
+
+ // When sending to broadcast (subnet or limited), the expected ethernet
+ // address is also broadcast.
+ return header.EthernetBroadcastAddress
+ }
+ if sendTo.IsMulticast() {
+ if sendTo4 := sendTo.To4(); sendTo4 != nil {
+ return header.EthernetAddressFromMulticastIPv4Address(tcpip.Address(sendTo4))
+ }
+ return header.EthernetAddressFromMulticastIPv6Address(tcpip.Address(sendTo.To16()))
+ }
+ return ""
+ }()
+ var ether testbench.Ether
+ if len(dst) != 0 {
+ ether.DstAddr = &dst
+ }
+ return &ether
+}
+
+type protocolTest interface {
+ Name() string
+ Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool)
+ Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool)
+}
+
+func TestSocket(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ subnetBroadcast := dut.Net.SubnetBroadcast()
+
+ for _, proto := range []protocolTest{
+ &icmpV4Test{},
+ &icmpV6Test{},
+ &udpTest{},
+ } {
+ t.Run(proto.Name(), func(t *testing.T) {
+ // Test every combination of bound/unbound, broadcast/multicast/unicast
+ // bound/destination address, and bound/not-bound to device.
+ for _, bindTo := range []net.IP{
+ nil, // Do not bind.
+ net.IPv4zero,
+ net.IPv4bcast,
+ net.IPv4allsys,
+ net.IPv6zero,
+ subnetBroadcast,
+ dut.Net.RemoteIPv4,
+ dut.Net.RemoteIPv6,
+ } {
+ t.Run(fmt.Sprintf("bindTo=%s", bindTo), func(t *testing.T) {
+ for _, sendTo := range []net.IP{
+ net.IPv4bcast,
+ net.IPv4allsys,
+ subnetBroadcast,
+ dut.Net.LocalIPv4,
+ dut.Net.LocalIPv6,
+ dut.Net.RemoteIPv4,
+ dut.Net.RemoteIPv6,
+ } {
+ t.Run(fmt.Sprintf("sendTo=%s", sendTo), func(t *testing.T) {
+ for _, bindToDevice := range []bool{true, false} {
+ t.Run(fmt.Sprintf("bindToDevice=%t", bindToDevice), func(t *testing.T) {
+ t.Run("Send", func(t *testing.T) {
+ proto.Send(t, dut, bindTo, sendTo, bindToDevice)
+ })
+ t.Run("Receive", func(t *testing.T) {
+ proto.Receive(t, dut, bindTo, sendTo, bindToDevice)
+ })
+ })
+ }
+ })
+ }
+ })
+ }
+ })
+ }
+}
+
+type icmpV4TestEnv struct {
+ socketFD int32
+ ident uint16
+ conn testbench.IPv4Conn
+ layers testbench.Layers
+}
+
+type icmpV4Test struct{}
+
+func (test *icmpV4Test) setup(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) icmpV4TestEnv {
+ t.Helper()
+
+ // Tell the DUT to create a socket.
+ var socketFD int32
+ var ident uint16
+
+ if bindTo != nil {
+ socketFD, ident = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_ICMP, bindTo)
+ } else {
+ // An unbound socket will auto-bind to INADDR_ANY.
+ socketFD = dut.Socket(t, unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_ICMP)
+ }
+ t.Cleanup(func() {
+ dut.Close(t, socketFD)
+ })
+
+ if bindToDevice {
+ dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName))
+ }
+
+ // Create a socket on the test runner.
+ conn := dut.Net.NewIPv4Conn(t, testbench.IPv4{}, testbench.IPv4{})
+ t.Cleanup(func() {
+ conn.Close(t)
+ })
+
+ return icmpV4TestEnv{
+ socketFD: socketFD,
+ ident: ident,
+ conn: conn,
+ layers: testbench.Layers{
+ expectedEthLayer(t, dut, socketFD, sendTo),
+ &testbench.IPv4{
+ DstAddr: testbench.Address(tcpip.Address(sendTo.To4())),
+ },
+ },
+ }
+}
+
+var _ protocolTest = (*icmpV4Test)(nil)
+
+func (*icmpV4Test) Name() string { return "icmpv4" }
+
+func (test *icmpV4Test) Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
+ if bindTo.To4() == nil || isBroadcastOrMulticast(dut, bindTo) {
+ // ICMPv4 sockets cannot bind to IPv6, broadcast, or multicast
+ // addresses.
+ return
+ }
+
+ isV4 := sendTo.To4() != nil
+
+ // TODO(gvisor.dev/issue/5681): Remove this case once ICMP sockets allow
+ // sending to broadcast and multicast addresses.
+ if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && isV4 && isBroadcastOrMulticast(dut, sendTo) {
+ // expectPacket cannot be false. In some cases the packet will send, but
+ // with IPv4 destination incorrectly set to RemoteIPv4. It's all bad and
+ // not worth the effort to create a special case when this occurs.
+ t.Skip("TODO(gvisor.dev/issue/5681): Allow sending to broadcast and multicast addresses with ICMP sockets.")
+ }
+
+ expectPacket := isV4 && !sendTo.Equal(dut.Net.RemoteIPv4)
+ switch {
+ case bindTo.Equal(dut.Net.RemoteIPv4):
+ // If we're explicitly bound to an interface's unicast address,
+ // packets are always sent on that interface.
+ case bindToDevice:
+ // If we're explicitly bound to an interface, packets are always
+ // sent on that interface.
+ case !sendTo.Equal(net.IPv4bcast) && !sendTo.IsMulticast():
+ // If we're not sending to limited broadcast or multicast, the route
+ // table will be consulted and packets will be sent on the correct
+ // interface.
+ default:
+ expectPacket = false
+ }
+
+ env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
+
+ for name, payload := range map[string][]byte{
+ "empty": nil,
+ "small": []byte("hello world"),
+ "random1k": testbench.GenerateRandomPayload(t, maxICMPv4PayloadSize),
+ } {
+ t.Run(name, func(t *testing.T) {
+ icmpLayer := &testbench.ICMPv4{
+ Type: testbench.ICMPv4Type(header.ICMPv4Echo),
+ Payload: payload,
+ }
+ bytes, err := icmpLayer.ToBytes()
+ if err != nil {
+ t.Fatalf("icmpLayer.ToBytes() = %s", err)
+ }
+ destSockaddr := unix.SockaddrInet4{}
+ copy(destSockaddr.Addr[:], sendTo.To4())
+
+ // Tell the DUT to send a packet out the ICMP socket.
+ if got, want := dut.SendTo(t, env.socketFD, bytes, 0, &destSockaddr), len(bytes); int(got) != want {
+ t.Fatalf("got dut.SendTo = %d, want %d", got, want)
+ }
+
+ // Verify the test runner received an ICMP packet with the correctly
+ // set "ident".
+ if env.ident != 0 {
+ icmpLayer.Ident = &env.ident
+ }
+ want := append(env.layers, icmpLayer)
+ if got, ok := env.conn.ListenForFrame(t, want, time.Second); !ok && expectPacket {
+ t.Fatalf("did not receive expected frame matching %s\nGot frames: %s", want, got)
+ } else if ok && !expectPacket {
+ matchedFrame := got[len(got)-1]
+ t.Fatalf("got unexpected frame matching %s\nGot frame: %s", want, matchedFrame)
+ }
+ })
+ }
+}
+
+func (test *icmpV4Test) Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
+ if bindTo.To4() == nil || isBroadcastOrMulticast(dut, bindTo) {
+ // ICMPv4 sockets cannot bind to IPv6, broadcast, or multicast
+ // addresses.
+ return
+ }
+
+ expectPacket := (bindTo.Equal(dut.Net.RemoteIPv4) || bindTo.Equal(net.IPv4zero)) && sendTo.Equal(dut.Net.RemoteIPv4)
+
+ // TODO(gvisor.dev/issue/5763): Remove this if statement once gVisor
+ // restricts ICMP sockets to receive only from unicast addresses.
+ if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && bindTo.Equal(net.IPv4zero) && isBroadcastOrMulticast(dut, sendTo) {
+ expectPacket = true
+ }
+
+ env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
+
+ for name, payload := range map[string][]byte{
+ "empty": nil,
+ "small": []byte("hello world"),
+ "random1k": testbench.GenerateRandomPayload(t, maxICMPv4PayloadSize),
+ } {
+ t.Run(name, func(t *testing.T) {
+ icmpLayer := &testbench.ICMPv4{
+ Type: testbench.ICMPv4Type(header.ICMPv4EchoReply),
+ Payload: payload,
+ }
+ if env.ident != 0 {
+ icmpLayer.Ident = &env.ident
+ }
+
+ // Send an ICMPv4 packet from the test runner to the DUT.
+ frame := env.conn.CreateFrame(t, env.layers, icmpLayer)
+ env.conn.SendFrame(t, frame)
+
+ // Verify the behavior of the ICMP socket on the DUT.
+ if expectPacket {
+ payload, err := icmpLayer.ToBytes()
+ if err != nil {
+ t.Fatalf("icmpLayer.ToBytes() = %s", err)
+ }
+
+ // Receive one extra byte to assert the length of the
+ // packet received in the case where the packet contains
+ // more data than expected.
+ len := int32(len(payload)) + 1
+ got, want := dut.Recv(t, env.socketFD, len, 0), payload
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff)
+ }
+ } else {
+ // Expected receive error, set a short receive timeout.
+ dut.SetSockOptTimeval(
+ t,
+ env.socketFD,
+ unix.SOL_SOCKET,
+ unix.SO_RCVTIMEO,
+ &unix.Timeval{
+ Sec: 1,
+ Usec: 0,
+ },
+ )
+ ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, env.socketFD, maxICMPv4PayloadSize, 0)
+ if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK {
+ t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno)
+ }
+ }
+ })
+ }
+}
+
+type icmpV6TestEnv struct {
+ socketFD int32
+ ident uint16
+ conn testbench.IPv6Conn
+ layers testbench.Layers
+}
+
+// icmpV6Test and icmpV4Test look substantially similar at first look, but have
+// enough subtle differences in setup and test expectations to discourage
+// refactoring:
+// - Different IP layers
+// - Different testbench.Connections
+// - Different UNIX domain and proto arguments
+// - Different expectPacket and wantErrno for send and receive
+type icmpV6Test struct{}
+
+func (test *icmpV6Test) setup(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) icmpV6TestEnv {
+ t.Helper()
+
+ // Tell the DUT to create a socket.
+ var socketFD int32
+ var ident uint16
+
+ if bindTo != nil {
+ socketFD, ident = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_ICMPV6, bindTo)
+ } else {
+ // An unbound socket will auto-bind to IN6ADDR_ANY_INIT.
+ socketFD = dut.Socket(t, unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_ICMPV6)
+ }
+ t.Cleanup(func() {
+ dut.Close(t, socketFD)
+ })
+
+ if bindToDevice {
+ dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName))
+ }
+
+ // Create a socket on the test runner.
+ conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
+ t.Cleanup(func() {
+ conn.Close(t)
+ })
+
+ return icmpV6TestEnv{
+ socketFD: socketFD,
+ ident: ident,
+ conn: conn,
+ layers: testbench.Layers{
+ expectedEthLayer(t, dut, socketFD, sendTo),
+ &testbench.IPv6{
+ DstAddr: testbench.Address(tcpip.Address(sendTo.To16())),
+ },
+ },
+ }
+}
+
+var _ protocolTest = (*icmpV6Test)(nil)
+
+func (*icmpV6Test) Name() string { return "icmpv6" }
+
+func (test *icmpV6Test) Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
+ if bindTo.To4() != nil || bindTo.IsMulticast() {
+ // ICMPv6 sockets cannot bind to IPv4 or multicast addresses.
+ return
+ }
+
+ expectPacket := sendTo.Equal(dut.Net.LocalIPv6)
+ wantErrno := unix.Errno(0)
+
+ if sendTo.To4() != nil {
+ wantErrno = unix.EINVAL
+ }
+
+ // TODO(gvisor.dev/issue/5966): Remove this if statement once ICMPv6 sockets
+ // return EINVAL after calling sendto with an IPv4 address.
+ if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && sendTo.To4() != nil {
+ switch {
+ case bindTo.Equal(dut.Net.RemoteIPv6):
+ wantErrno = unix.ENETUNREACH
+ case bindTo.Equal(net.IPv6zero) || bindTo == nil:
+ wantErrno = unix.Errno(0)
+ }
+ }
+
+ env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
+
+ for name, payload := range map[string][]byte{
+ "empty": nil,
+ "small": []byte("hello world"),
+ "random1k": testbench.GenerateRandomPayload(t, maxICMPv6PayloadSize),
+ } {
+ t.Run(name, func(t *testing.T) {
+ icmpLayer := &testbench.ICMPv6{
+ Type: testbench.ICMPv6Type(header.ICMPv6EchoRequest),
+ Payload: payload,
+ }
+ bytes, err := icmpLayer.ToBytes()
+ if err != nil {
+ t.Fatalf("icmpLayer.ToBytes() = %s", err)
+ }
+ destSockaddr := unix.SockaddrInet6{
+ ZoneId: dut.Net.RemoteDevID,
+ }
+ copy(destSockaddr.Addr[:], sendTo.To16())
+
+ // Tell the DUT to send a packet out the ICMPv6 socket.
+ gotRet, gotErrno := dut.SendToWithErrno(context.Background(), t, env.socketFD, bytes, 0, &destSockaddr)
+
+ if gotErrno != wantErrno {
+ t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (_, %s), want = (_, %s)", env.socketFD, sendTo, gotErrno, wantErrno)
+ }
+ if wantErrno != 0 {
+ return
+ }
+ if got, want := int(gotRet), len(bytes); got != want {
+ t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (%d, _), want = (%d, _)", env.socketFD, sendTo, got, want)
+ }
+
+ // Verify the test runner received an ICMPv6 packet with the
+ // correctly set "ident".
+ if env.ident != 0 {
+ icmpLayer.Ident = &env.ident
+ }
+ want := append(env.layers, icmpLayer)
+ if got, ok := env.conn.ListenForFrame(t, want, time.Second); !ok && expectPacket {
+ t.Fatalf("did not receive expected frame matching %s\nGot frames: %s", want, got)
+ } else if ok && !expectPacket {
+ matchedFrame := got[len(got)-1]
+ t.Fatalf("got unexpected frame matching %s\nGot frame: %s", want, matchedFrame)
+ }
+ })
+ }
+}
+
+func (test *icmpV6Test) Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
+ if bindTo.To4() != nil || bindTo.IsMulticast() {
+ // ICMPv6 sockets cannot bind to IPv4 or multicast addresses.
+ return
+ }
+
+ expectPacket := true
+ switch {
+ case bindTo.Equal(dut.Net.RemoteIPv6) && sendTo.Equal(dut.Net.RemoteIPv6):
+ case bindTo.Equal(net.IPv6zero) && sendTo.Equal(dut.Net.RemoteIPv6):
+ case bindTo.Equal(net.IPv6zero) && sendTo.Equal(net.IPv6linklocalallnodes):
+ default:
+ expectPacket = false
+ }
+
+ // TODO(gvisor.dev/issue/5763): Remove this if statement once gVisor
+ // restricts ICMP sockets to receive only from unicast addresses.
+ if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && bindTo.Equal(net.IPv6zero) && isBroadcastOrMulticast(dut, sendTo) {
+ expectPacket = false
+ }
+
+ env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
+
+ for name, payload := range map[string][]byte{
+ "empty": nil,
+ "small": []byte("hello world"),
+ "random1k": testbench.GenerateRandomPayload(t, maxICMPv6PayloadSize),
+ } {
+ t.Run(name, func(t *testing.T) {
+ icmpLayer := &testbench.ICMPv6{
+ Type: testbench.ICMPv6Type(header.ICMPv6EchoReply),
+ Payload: payload,
+ }
+ if env.ident != 0 {
+ icmpLayer.Ident = &env.ident
+ }
+
+ // Send an ICMPv6 packet from the test runner to the DUT.
+ frame := env.conn.CreateFrame(t, env.layers, icmpLayer)
+ env.conn.SendFrame(t, frame)
+
+ // Verify the behavior of the ICMPv6 socket on the DUT.
+ if expectPacket {
+ payload, err := icmpLayer.ToBytes()
+ if err != nil {
+ t.Fatalf("icmpLayer.ToBytes() = %s", err)
+ }
+
+ // Receive one extra byte to assert the length of the
+ // packet received in the case where the packet contains
+ // more data than expected.
+ len := int32(len(payload)) + 1
+ got, want := dut.Recv(t, env.socketFD, len, 0), payload
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff)
+ }
+ } else {
+ // Expected receive error, set a short receive timeout.
+ dut.SetSockOptTimeval(
+ t,
+ env.socketFD,
+ unix.SOL_SOCKET,
+ unix.SO_RCVTIMEO,
+ &unix.Timeval{
+ Sec: 1,
+ Usec: 0,
+ },
+ )
+ ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, env.socketFD, maxICMPv6PayloadSize, 0)
+ if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK {
+ t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno)
+ }
+ }
+ })
+ }
+}
+
+type udpConn interface {
+ SrcPort(*testing.T) uint16
+ SendFrame(*testing.T, testbench.Layers, ...testbench.Layer)
+ ListenForFrame(*testing.T, testbench.Layers, time.Duration) ([]testbench.Layers, bool)
+ Close(*testing.T)
+}
+
+type udpTestEnv struct {
+ socketFD int32
+ conn udpConn
+ layers testbench.Layers
+}
+
+type udpTest struct{}
+
+func (test *udpTest) setup(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) udpTestEnv {
+ t.Helper()
+
+ var (
+ socketFD int32
+ outgoingUDP, incomingUDP testbench.UDP
+ )
+
+ // Tell the DUT to create a socket.
+ if bindTo != nil {
+ var remotePort uint16
+ socketFD, remotePort = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, bindTo)
+ outgoingUDP.DstPort = &remotePort
+ incomingUDP.SrcPort = &remotePort
+ } else {
+ // An unbound socket will auto-bind to INADDR_ANY.
+ socketFD = dut.Socket(t, unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP)
+ }
+ t.Cleanup(func() {
+ dut.Close(t, socketFD)
+ })
+
+ if bindToDevice {
+ dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName))
+ }
+
+ // Create a socket on the test runner.
+ var conn udpConn
+ var ipLayer testbench.Layer
+ if addr := sendTo.To4(); addr != nil {
+ udpConn := dut.Net.NewUDPIPv4(t, outgoingUDP, incomingUDP)
+ conn = &udpConn
+ ipLayer = &testbench.IPv4{
+ DstAddr: testbench.Address(tcpip.Address(addr)),
+ }
+ } else {
+ udpConn := dut.Net.NewUDPIPv6(t, outgoingUDP, incomingUDP)
+ conn = &udpConn
+ ipLayer = &testbench.IPv6{
+ DstAddr: testbench.Address(tcpip.Address(sendTo.To16())),
+ }
+ }
+ t.Cleanup(func() {
+ conn.Close(t)
+ })
+
+ return udpTestEnv{
+ socketFD: socketFD,
+ conn: conn,
+ layers: testbench.Layers{
+ expectedEthLayer(t, dut, socketFD, sendTo),
+ ipLayer,
+ &incomingUDP,
+ },
+ }
+}
+
+var _ protocolTest = (*udpTest)(nil)
+
+func (*udpTest) Name() string { return "udp" }
+
+func (test *udpTest) Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
+ canSend := bindTo == nil || bindTo.Equal(net.IPv6zero) || sameIPVersion(sendTo, bindTo)
+ expectPacket := canSend && !isRemoteAddr(dut, sendTo)
+ switch {
+ case bindTo.Equal(dut.Net.RemoteIPv4):
+ // If we're explicitly bound to an interface's unicast address,
+ // packets are always sent on that interface.
+ case bindToDevice:
+ // If we're explicitly bound to an interface, packets are always
+ // sent on that interface.
+ case !sendTo.Equal(net.IPv4bcast) && !sendTo.IsMulticast():
+ // If we're not sending to limited broadcast, multicast, or local, the
+ // route table will be consulted and packets will be sent on the correct
+ // interface.
+ default:
+ expectPacket = false
+ }
+
+ wantErrno := unix.Errno(0)
+ switch {
+ case !canSend && bindTo.To4() != nil:
+ wantErrno = unix.EAFNOSUPPORT
+ case !canSend && bindTo.To4() == nil:
+ wantErrno = unix.ENETUNREACH
+ }
+
+ // TODO(gvisor.dev/issue/5967): Remove this if statement once UDPv4 sockets
+ // returns EAFNOSUPPORT after calling sendto with an IPv6 address.
+ if dut.Uname.IsGvisor() && !canSend && bindTo.To4() != nil {
+ wantErrno = unix.EINVAL
+ }
+
+ env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
+
+ for name, payload := range map[string][]byte{
+ "empty": nil,
+ "small": []byte("hello world"),
+ "random1k": testbench.GenerateRandomPayload(t, maxUDPPayloadSize(bindTo)),
+ } {
+ t.Run(name, func(t *testing.T) {
+ var destSockaddr unix.Sockaddr
+ if sendTo4 := sendTo.To4(); sendTo4 != nil {
+ addr := unix.SockaddrInet4{
+ Port: int(env.conn.SrcPort(t)),
+ }
+ copy(addr.Addr[:], sendTo4)
+ destSockaddr = &addr
+ } else {
+ addr := unix.SockaddrInet6{
+ Port: int(env.conn.SrcPort(t)),
+ ZoneId: dut.Net.RemoteDevID,
+ }
+ copy(addr.Addr[:], sendTo.To16())
+ destSockaddr = &addr
+ }
+
+ // Tell the DUT to send a packet out the UDP socket.
+ gotRet, gotErrno := dut.SendToWithErrno(context.Background(), t, env.socketFD, payload, 0, destSockaddr)
+
+ if gotErrno != wantErrno {
+ t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (_, %s), want = (_, %s)", env.socketFD, sendTo, gotErrno, wantErrno)
+ }
+ if wantErrno != 0 {
+ return
+ }
+ if got, want := int(gotRet), len(payload); got != want {
+ t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (%d, _), want = (%d, _)", env.socketFD, sendTo, got, want)
+ }
+
+ // Verify the test runner received a UDP packet with the
+ // correct payload.
+ want := append(env.layers, &testbench.Payload{
+ Bytes: payload,
+ })
+ if got, ok := env.conn.ListenForFrame(t, want, time.Second); !ok && expectPacket {
+ t.Fatalf("did not receive expected frame matching %s\nGot frames: %s", want, got)
+ } else if ok && !expectPacket {
+ matchedFrame := got[len(got)-1]
+ t.Fatalf("got unexpected frame matching %s\nGot frame: %s", want, matchedFrame)
+ }
+ })
+ }
+}
+
+func (test *udpTest) Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
+ subnetBroadcast := dut.Net.SubnetBroadcast()
+
+ expectPacket := true
+ switch {
+ case bindTo.Equal(sendTo):
+ case bindTo.Equal(net.IPv4zero) && sameIPVersion(bindTo, sendTo) && !sendTo.Equal(dut.Net.LocalIPv4):
+ case bindTo.Equal(net.IPv6zero) && isBroadcast(dut, sendTo):
+ case bindTo.Equal(net.IPv6zero) && isRemoteAddr(dut, sendTo):
+ case bindTo.Equal(subnetBroadcast) && sendTo.Equal(subnetBroadcast):
+ default:
+ expectPacket = false
+ }
+
+ // TODO(gvisor.dev/issue/5956): Remove this if statement once gVisor
+ // restricts ICMP sockets to receive only from unicast addresses.
+ if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && bindTo.Equal(net.IPv6zero) && sendTo.Equal(net.IPv4allsys) {
+ expectPacket = true
+ }
+
+ env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
+ maxPayloadSize := maxUDPPayloadSize(bindTo)
+
+ for name, payload := range map[string][]byte{
+ "empty": nil,
+ "small": []byte("hello world"),
+ "random1k": testbench.GenerateRandomPayload(t, maxPayloadSize),
+ } {
+ t.Run(name, func(t *testing.T) {
+ // Send a UDP packet from the test runner to the DUT.
+ env.conn.SendFrame(t, env.layers, &testbench.Payload{Bytes: payload})
+
+ // Verify the behavior of the ICMP socket on the DUT.
+ if expectPacket {
+ // Receive one extra byte to assert the length of the
+ // packet received in the case where the packet contains
+ // more data than expected.
+ len := int32(len(payload)) + 1
+ got, want := dut.Recv(t, env.socketFD, len, 0), payload
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff)
+ }
+ } else {
+ // Expected receive error, set a short receive timeout.
+ dut.SetSockOptTimeval(
+ t,
+ env.socketFD,
+ unix.SOL_SOCKET,
+ unix.SO_RCVTIMEO,
+ &unix.Timeval{
+ Sec: 1,
+ Usec: 0,
+ },
+ )
+ ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, env.socketFD, int32(maxPayloadSize), 0)
+ if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK {
+ t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno)
+ }
+ }
+ })
+ }
+}
+
+func isBroadcast(dut testbench.DUT, ip net.IP) bool {
+ return ip.Equal(net.IPv4bcast) || ip.Equal(dut.Net.SubnetBroadcast())
+}
+
+func isBroadcastOrMulticast(dut testbench.DUT, ip net.IP) bool {
+ return isBroadcast(dut, ip) || ip.IsMulticast()
+}
+
+func sameIPVersion(a, b net.IP) bool {
+ return (a.To4() == nil) == (b.To4() == nil)
+}
+
+func isRemoteAddr(dut testbench.DUT, ip net.IP) bool {
+ return ip.Equal(dut.Net.RemoteIPv4) || ip.Equal(dut.Net.RemoteIPv6)
+}
diff --git a/test/packetimpact/tests/icmpv6_param_problem_test.go b/test/packetimpact/tests/icmpv6_param_problem_test.go
index 1beccb6cf..fdabcf1ad 100644
--- a/test/packetimpact/tests/icmpv6_param_problem_test.go
+++ b/test/packetimpact/tests/icmpv6_param_problem_test.go
@@ -15,7 +15,6 @@
package icmpv6_param_problem_test
import (
- "encoding/binary"
"flag"
"testing"
"time"
@@ -56,13 +55,11 @@ func TestICMPv6ParamProblemTest(t *testing.T) {
t.Fatalf("can't convert %s to bytes: %s", ipv6Sent, err)
}
- // The problematic field is the NextHeader.
- b := make([]byte, 4)
- binary.BigEndian.PutUint32(b, header.IPv6NextHeaderOffset)
- expectedPayload = append(b, expectedPayload...)
expectedICMPv6 := testbench.ICMPv6{
Type: testbench.ICMPv6Type(header.ICMPv6ParamProblem),
Payload: expectedPayload,
+ // The problematic field is the NextHeader.
+ Pointer: testbench.Uint32(header.IPv6NextHeaderOffset),
}
paramProblem := testbench.Layers{
diff --git a/test/packetimpact/tests/ipv6_unknown_options_action_test.go b/test/packetimpact/tests/ipv6_unknown_options_action_test.go
index f999d13d2..d762c43a7 100644
--- a/test/packetimpact/tests/ipv6_unknown_options_action_test.go
+++ b/test/packetimpact/tests/ipv6_unknown_options_action_test.go
@@ -15,7 +15,6 @@
package ipv6_unknown_options_action_test
import (
- "encoding/binary"
"flag"
"net"
"testing"
@@ -154,23 +153,22 @@ func TestIPv6UnknownOptionAction(t *testing.T) {
outgoing := conn.CreateFrame(t, outgoingOverride, tt.mkExtHdr(optionTypeFromAction(tt.action)))
conn.SendFrame(t, outgoing)
ipv6Sent := outgoing[1:]
- invokingPacket, err := ipv6Sent.ToBytes()
+ icmpv6Payload, err := ipv6Sent.ToBytes()
if err != nil {
t.Fatalf("failed to serialize the outgoing packet: %s", err)
}
- icmpv6Payload := make([]byte, 4)
- // The pointer in the ICMPv6 parameter problem message should point to
- // the option type of the unknown option. In our test case, it is the
- // first option in the extension header whose option type is 2 bytes
- // after the IPv6 header (after NextHeader and ExtHdrLen).
- binary.BigEndian.PutUint32(icmpv6Payload, header.IPv6MinimumSize+2)
- icmpv6Payload = append(icmpv6Payload, invokingPacket...)
gotICMPv6, err := conn.ExpectFrame(t, testbench.Layers{
&testbench.Ether{},
&testbench.IPv6{},
&testbench.ICMPv6{
- Type: testbench.ICMPv6Type(header.ICMPv6ParamProblem),
- Code: testbench.ICMPv6Code(header.ICMPv6UnknownOption),
+ Type: testbench.ICMPv6Type(header.ICMPv6ParamProblem),
+ Code: testbench.ICMPv6Code(header.ICMPv6UnknownOption),
+ // The pointer in the ICMPv6 parameter problem message
+ // should point to the option type of the unknown option. In
+ // our test case, it is the first option in the extension
+ // header whose option type is 2 bytes after the IPv6 header
+ // (after NextHeader and ExtHdrLen).
+ Pointer: testbench.Uint32(header.IPv6MinimumSize + 2),
Payload: icmpv6Payload,
},
}, time.Second)
diff --git a/test/packetimpact/tests/tcp_connect_icmp_error_test.go b/test/packetimpact/tests/tcp_connect_icmp_error_test.go
index 79bfe9eb7..15d603328 100644
--- a/test/packetimpact/tests/tcp_connect_icmp_error_test.go
+++ b/test/packetimpact/tests/tcp_connect_icmp_error_test.go
@@ -15,9 +15,7 @@
package tcp_connect_icmp_error_test
import (
- "context"
"flag"
- "sync"
"testing"
"time"
@@ -33,17 +31,18 @@ func init() {
func sendICMPError(t *testing.T, conn *testbench.TCPIPv4, tcp *testbench.TCP) {
t.Helper()
- layers := conn.CreateFrame(t, nil)
- layers = layers[:len(layers)-1]
- ip, ok := tcp.Prev().(*testbench.IPv4)
- if !ok {
- t.Fatalf("expected %s to be IPv4", tcp.Prev())
+ icmpPayload := testbench.Layers{tcp.Prev(), tcp}
+ bytes, err := icmpPayload.ToBytes()
+ if err != nil {
+ t.Fatalf("got icmpPayload.ToBytes() = (_, %s), want = (_, nil)", err)
}
- icmpErr := &testbench.ICMPv4{
- Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable),
- Code: testbench.ICMPv4Code(header.ICMPv4HostUnreachable)}
- layers = append(layers, icmpErr, ip, tcp)
+ layers := conn.CreateFrame(t, nil)
+ layers[len(layers)-1] = &testbench.ICMPv4{
+ Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable),
+ Code: testbench.ICMPv4Code(header.ICMPv4HostUnreachable),
+ Payload: bytes,
+ }
conn.SendFrameStateless(t, layers)
}
@@ -65,35 +64,38 @@ func TestTCPConnectICMPError(t *testing.T) {
t.Fatalf("expected SYN, %s", err)
}
- done := make(chan bool)
- defer close(done)
- var wg sync.WaitGroup
- defer wg.Wait()
- wg.Add(1)
- var block sync.WaitGroup
- block.Add(1)
+ // Continuously try to read the ICMP error in an attempt to trigger a race
+ // condition.
+ start := make(chan struct{})
+ done := make(chan struct{})
go func() {
- defer wg.Done()
- _, cancel := context.WithTimeout(context.Background(), time.Second*3)
- defer cancel()
+ defer close(done)
- block.Done()
+ close(start)
for {
select {
case <-done:
return
default:
- if errno := dut.GetSockOptInt(t, clientFD, unix.SOL_SOCKET, unix.SO_ERROR); errno != 0 {
- return
- }
}
+ const want = unix.EHOSTUNREACH
+ switch got := unix.Errno(dut.GetSockOptInt(t, clientFD, unix.SOL_SOCKET, unix.SO_ERROR)); got {
+ case unix.Errno(0):
+ continue
+ case want:
+ return
+ default:
+ t.Fatalf("got SO_ERROR = %s, want %s", got, want)
+ }
+
}
}()
- block.Wait()
+ <-start
sendICMPError(t, &conn, tcp)
dut.PollOne(t, clientFD, unix.POLLHUP, time.Second)
+ <-done
conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
// The DUT should reply with RST to our ACK as the state should have
diff --git a/test/packetimpact/tests/tcp_info_test.go b/test/packetimpact/tests/tcp_info_test.go
index 93f58ec49..5410cc368 100644
--- a/test/packetimpact/tests/tcp_info_test.go
+++ b/test/packetimpact/tests/tcp_info_test.go
@@ -21,8 +21,6 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/binary"
- "gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/test/packetimpact/testbench"
)
@@ -49,31 +47,31 @@ func TestTCPInfo(t *testing.T) {
samplePayload := &testbench.Payload{Bytes: sampleData}
dut.Send(t, acceptFD, sampleData, 0)
if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
+ t.Fatalf("expected a packet with payload %s: %s", samplePayload, err)
}
conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
- info := linux.TCPInfo{}
- infoBytes := dut.GetSockOpt(t, acceptFD, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo))
- if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want {
- t.Fatalf("expected %T, got %d bytes want %d bytes", info, got, want)
+ info := dut.GetSockOptTCPInfo(t, acceptFD)
+ if got, want := uint32(info.State), linux.TCP_ESTABLISHED; got != want {
+ t.Fatalf("got %d want %d", got, want)
}
- binary.Unmarshal(infoBytes, hostarch.ByteOrder, &info)
-
- rtt := time.Duration(info.RTT) * time.Microsecond
- rttvar := time.Duration(info.RTTVar) * time.Microsecond
- rto := time.Duration(info.RTO) * time.Microsecond
- if rtt == 0 || rttvar == 0 || rto == 0 {
- t.Errorf("expected rtt(%v), rttvar(%v) and rto(%v) to be greater than zero", rtt, rttvar, rto)
+ if info.RTT == 0 {
+ t.Errorf("got RTT=0, want nonzero")
+ }
+ if info.RTTVar == 0 {
+ t.Errorf("got RTTVar=0, want nonzero")
+ }
+ if info.RTO == 0 {
+ t.Errorf("got RTO=0, want nonzero")
}
if info.ReordSeen != 0 {
- t.Errorf("expected the connection to not have any reordering, got: %v want: 0", info.ReordSeen)
+ t.Errorf("expected the connection to not have any reordering, got: %d want: 0", info.ReordSeen)
}
if info.SndCwnd == 0 {
t.Errorf("expected send congestion window to be greater than zero")
}
if info.CaState != linux.TCP_CA_Open {
- t.Errorf("expected the connection to be in open state, got: %v want: %v", info.CaState, linux.TCP_CA_Open)
+ t.Errorf("expected the connection to be in open state, got: %d want: %d", info.CaState, linux.TCP_CA_Open)
}
if t.Failed() {
@@ -85,25 +83,20 @@ func TestTCPInfo(t *testing.T) {
seq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)))
dut.Send(t, acceptFD, sampleData, 0)
if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
- t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
+ t.Fatalf("expected a packet with payload %s: %s", samplePayload, err)
}
- // Expect retransmission of the packet within 1.5*RTO.
- timeout := time.Duration(float64(info.RTO)*1.5) * time.Microsecond
+ // Given a generous retransmission timeout.
+ timeout := time.Duration(info.RTO) * 2 * time.Microsecond
if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: seq}, samplePayload, timeout); err != nil {
- t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
+ t.Fatalf("expected a packet with payload %s: %s", samplePayload, err)
}
- info = linux.TCPInfo{}
- infoBytes = dut.GetSockOpt(t, acceptFD, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo))
- if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want {
- t.Fatalf("expected %T, got %d bytes want %d bytes", info, got, want)
- }
- binary.Unmarshal(infoBytes, hostarch.ByteOrder, &info)
+ info = dut.GetSockOptTCPInfo(t, acceptFD)
if info.CaState != linux.TCP_CA_Loss {
- t.Errorf("expected the connection to be in loss recovery, got: %v want: %v", info.CaState, linux.TCP_CA_Loss)
+ t.Errorf("expected the connection to be in loss recovery, got: %d want: %d", info.CaState, linux.TCP_CA_Loss)
}
if info.SndCwnd != 1 {
- t.Errorf("expected send congestion window to be 1, got: %v %v", info.SndCwnd)
+ t.Errorf("expected send congestion window to be 1, got: %d", info.SndCwnd)
}
}
diff --git a/test/packetimpact/tests/tcp_linger_test.go b/test/packetimpact/tests/tcp_linger_test.go
index 88942904d..46b5ca5d8 100644
--- a/test/packetimpact/tests/tcp_linger_test.go
+++ b/test/packetimpact/tests/tcp_linger_test.go
@@ -98,20 +98,19 @@ func TestTCPLingerNonZeroTimeout(t *testing.T) {
dut.SetSockLingerOption(t, acceptFD, lingerDuration, tt.lingerOn)
- // Increase timeout as Close will take longer time to
- // return when SO_LINGER is set with non-zero timeout.
- timeout := lingerDuration + 1*time.Second
- ctx, cancel := context.WithTimeout(context.Background(), timeout)
- defer cancel()
start := time.Now()
- dut.CloseWithErrno(ctx, t, acceptFD)
- end := time.Now()
- diff := end.Sub(start)
-
- if tt.lingerOn && diff < lingerDuration {
- t.Errorf("expected close to return after %v seconds, but returned sooner", lingerDuration)
- } else if !tt.lingerOn && diff > 1*time.Second {
- t.Errorf("expected close to return within a second, but returned later")
+ dut.CloseWithErrno(context.Background(), t, acceptFD)
+ elapsed := time.Since(start)
+
+ expectedMaximum := time.Second
+ if tt.lingerOn {
+ expectedMaximum += lingerDuration
+ if elapsed < lingerDuration {
+ t.Errorf("expected close to take at least %s, but took %s", lingerDuration, elapsed)
+ }
+ }
+ if elapsed >= expectedMaximum {
+ t.Errorf("expected close to take at most %s, but took %s", expectedMaximum, elapsed)
}
if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil {
@@ -144,20 +143,19 @@ func TestTCPLingerSendNonZeroTimeout(t *testing.T) {
sampleData := []byte("Sample Data")
dut.Send(t, acceptFD, sampleData, 0)
- // Increase timeout as Close will take longer time to
- // return when SO_LINGER is set with non-zero timeout.
- timeout := lingerDuration + 1*time.Second
- ctx, cancel := context.WithTimeout(context.Background(), timeout)
- defer cancel()
start := time.Now()
- dut.CloseWithErrno(ctx, t, acceptFD)
- end := time.Now()
- diff := end.Sub(start)
-
- if tt.lingerOn && diff < lingerDuration {
- t.Errorf("expected close to return after %v seconds, but returned sooner", lingerDuration)
- } else if !tt.lingerOn && diff > 1*time.Second {
- t.Errorf("expected close to return within a second, but returned later")
+ dut.CloseWithErrno(context.Background(), t, acceptFD)
+ elapsed := time.Since(start)
+
+ expectedMaximum := time.Second
+ if tt.lingerOn {
+ expectedMaximum += lingerDuration
+ if elapsed < lingerDuration {
+ t.Errorf("expected close to take at least %s, but took %s", lingerDuration, elapsed)
+ }
+ }
+ if elapsed >= expectedMaximum {
+ t.Errorf("expected close to take at most %s, but took %s", expectedMaximum, elapsed)
}
samplePayload := &testbench.Payload{Bytes: sampleData}
@@ -221,20 +219,19 @@ func TestTCPLingerShutdownSendNonZeroTimeout(t *testing.T) {
dut.Shutdown(t, acceptFD, unix.SHUT_RDWR)
- // Increase timeout as Close will take longer time to
- // return when SO_LINGER is set with non-zero timeout.
- timeout := lingerDuration + 1*time.Second
- ctx, cancel := context.WithTimeout(context.Background(), timeout)
- defer cancel()
start := time.Now()
- dut.CloseWithErrno(ctx, t, acceptFD)
- end := time.Now()
- diff := end.Sub(start)
-
- if tt.lingerOn && diff < lingerDuration {
- t.Errorf("expected close to return after %v seconds, but returned sooner", lingerDuration)
- } else if !tt.lingerOn && diff > 1*time.Second {
- t.Errorf("expected close to return within a second, but returned later")
+ dut.CloseWithErrno(context.Background(), t, acceptFD)
+ elapsed := time.Since(start)
+
+ expectedMaximum := time.Second
+ if tt.lingerOn {
+ expectedMaximum += lingerDuration
+ if elapsed < lingerDuration {
+ t.Errorf("expected close to take at least %s, but took %s", lingerDuration, elapsed)
+ }
+ }
+ if elapsed >= expectedMaximum {
+ t.Errorf("expected close to take at most %s, but took %s", expectedMaximum, elapsed)
}
samplePayload := &testbench.Payload{Bytes: sampleData}
@@ -259,9 +256,10 @@ func TestTCPLingerNonEstablished(t *testing.T) {
// and return immediately.
start := time.Now()
dut.CloseWithErrno(context.Background(), t, newFD)
- diff := time.Since(start)
+ elapsed := time.Since(start)
- if diff > lingerDuration {
- t.Errorf("expected close to return within %s, but returned after %s", lingerDuration, diff)
+ expectedMaximum := time.Second
+ if elapsed >= time.Second {
+ t.Errorf("expected close to take at most %s, but took %s", expectedMaximum, elapsed)
}
}
diff --git a/test/packetimpact/tests/tcp_listen_backlog_test.go b/test/packetimpact/tests/tcp_listen_backlog_test.go
index 26c812d0a..fea7d5b6f 100644
--- a/test/packetimpact/tests/tcp_listen_backlog_test.go
+++ b/test/packetimpact/tests/tcp_listen_backlog_test.go
@@ -55,15 +55,23 @@ func TestTCPListenBacklog(t *testing.T) {
// Send the ACK to complete handshake.
establishedConn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
+
+ // Poll for the established connection ready for accept.
dut.PollOne(t, listenFd, unix.POLLIN, time.Second)
- // Send the ACK to complete handshake, expect this to be ignored by the
- // listener.
+ // Send the ACK to complete handshake, expect this to be dropped by the
+ // listener as the accept queue would be full because of the previous
+ // handshake.
incompleteConn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
+ // Let the test wait for sometime so that the ACK is indeed dropped by
+ // the listener. Without such a wait, the DUT accept can race with
+ // ACK handling (dropping) causing the test to be flaky.
+ time.Sleep(100 * time.Millisecond)
// Drain the accept queue to enable poll for subsequent connections on the
// listener.
- dut.Accept(t, listenFd)
+ fd, _ := dut.Accept(t, listenFd)
+ dut.Close(t, fd)
// The ACK for the incomplete connection should be ignored by the
// listening endpoint and the poll on listener should now time out.
diff --git a/test/packetimpact/tests/tcp_network_unreachable_test.go b/test/packetimpact/tests/tcp_network_unreachable_test.go
index 5168450ad..e92e6aa9b 100644
--- a/test/packetimpact/tests/tcp_network_unreachable_test.go
+++ b/test/packetimpact/tests/tcp_network_unreachable_test.go
@@ -41,39 +41,31 @@ func TestTCPSynSentUnreachable(t *testing.T) {
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[:], dut.Net.LocalIPv4)
- if _, err := dut.ConnectWithErrno(ctx, t, clientFD, &sa); err != unix.EINPROGRESS {
+ if _, err := dut.ConnectWithErrno(context.Background(), t, clientFD, &sa); err != unix.EINPROGRESS {
t.Errorf("got connect() = %v, want EINPROGRESS", err)
}
// Get the SYN.
- tcpLayers, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, nil, time.Second)
+ tcp, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second)
if err != nil {
t.Fatalf("expected SYN: %s", err)
}
// Send a host unreachable message.
- layers := conn.CreateFrame(t, nil)
- layers = layers[:len(layers)-1]
- const ipLayer = 1
- const tcpLayer = ipLayer + 1
- ip, ok := tcpLayers[ipLayer].(*testbench.IPv4)
- if !ok {
- t.Fatalf("expected %s to be IPv4", tcpLayers[ipLayer])
- }
- tcp, ok := tcpLayers[tcpLayer].(*testbench.TCP)
- if !ok {
- t.Fatalf("expected %s to be TCP", tcpLayers[tcpLayer])
- }
- var icmpv4 testbench.ICMPv4 = testbench.ICMPv4{
- Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable),
- Code: testbench.ICMPv4Code(header.ICMPv4HostUnreachable),
+ icmpPayload := testbench.Layers{tcp.Prev(), tcp}
+ bytes, err := icmpPayload.ToBytes()
+ if err != nil {
+ t.Fatalf("got icmpPayload.ToBytes() = (_, %s), want = (_, nil)", err)
}
- layers = append(layers, &icmpv4, ip, tcp)
+ layers := conn.CreateFrame(t, nil)
+ layers[len(layers)-1] = &testbench.ICMPv4{
+ Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable),
+ Code: testbench.ICMPv4Code(header.ICMPv4HostUnreachable),
+ Payload: bytes,
+ }
conn.SendFrameStateless(t, layers)
if err := getConnectError(t, &dut, clientFD); err != unix.EHOSTUNREACH {
@@ -92,43 +84,34 @@ func TestTCPSynSentUnreachable6(t *testing.T) {
defer conn.Close(t)
// Bring the DUT to SYN-SENT state with a non-blocking connect.
- ctx, cancel := context.WithTimeout(context.Background(), testbench.RPCTimeout)
- defer cancel()
sa := unix.SockaddrInet6{
Port: int(conn.SrcPort()),
ZoneId: dut.Net.RemoteDevID,
}
copy(sa.Addr[:], dut.Net.LocalIPv6)
- if _, err := dut.ConnectWithErrno(ctx, t, clientFD, &sa); err != unix.EINPROGRESS {
+ if _, err := dut.ConnectWithErrno(context.Background(), t, clientFD, &sa); err != unix.EINPROGRESS {
t.Errorf("got connect() = %v, want EINPROGRESS", err)
}
// Get the SYN.
- tcpLayers, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, nil, time.Second)
+ tcp, err := conn.Expect(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second)
if err != nil {
t.Fatalf("expected SYN: %s", err)
}
// Send a host unreachable message.
- layers := conn.CreateFrame(t, nil)
- layers = layers[:len(layers)-1]
- const ipLayer = 1
- const tcpLayer = ipLayer + 1
- ip, ok := tcpLayers[ipLayer].(*testbench.IPv6)
- if !ok {
- t.Fatalf("expected %s to be IPv6", tcpLayers[ipLayer])
- }
- tcp, ok := tcpLayers[tcpLayer].(*testbench.TCP)
- if !ok {
- t.Fatalf("expected %s to be TCP", tcpLayers[tcpLayer])
+ icmpPayload := testbench.Layers{tcp.Prev(), tcp}
+ bytes, err := icmpPayload.ToBytes()
+ if err != nil {
+ t.Fatalf("got icmpPayload.ToBytes() = (_, %s), want = (_, nil)", err)
}
- var icmpv6 testbench.ICMPv6 = testbench.ICMPv6{
- Type: testbench.ICMPv6Type(header.ICMPv6DstUnreachable),
- Code: testbench.ICMPv6Code(header.ICMPv6NetworkUnreachable),
- // Per RFC 4443 3.1, the payload contains 4 zeroed bytes.
- Payload: []byte{0, 0, 0, 0},
+
+ layers := conn.CreateFrame(t, nil)
+ layers[len(layers)-1] = &testbench.ICMPv6{
+ Type: testbench.ICMPv6Type(header.ICMPv6DstUnreachable),
+ Code: testbench.ICMPv6Code(header.ICMPv6NetworkUnreachable),
+ Payload: bytes,
}
- layers = append(layers, &icmpv6, ip, tcp)
conn.SendFrameStateless(t, layers)
if err := getConnectError(t, &dut, clientFD); err != unix.ENETUNREACH {
diff --git a/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go b/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go
index 1c8b72ebe..974c15384 100644
--- a/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go
+++ b/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go
@@ -20,7 +20,6 @@ import (
"encoding/hex"
"errors"
"flag"
- "sync"
"testing"
"time"
@@ -54,37 +53,39 @@ func TestQueueSendInSynSentHandshake(t *testing.T) {
// Test blocking send.
dut.SetNonBlocking(t, socket, false)
- var wg sync.WaitGroup
- defer wg.Wait()
- wg.Add(1)
- var block sync.WaitGroup
- block.Add(1)
+ start := make(chan struct{})
+ done := make(chan struct{})
go func() {
- defer wg.Done()
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
- defer cancel()
+ defer close(done)
- block.Done()
+ close(start)
// Issue SEND call in SYN-SENT, this should be queued for
// process until the connection is established.
- n, err := dut.SendWithErrno(ctx, t, socket, sampleData, 0)
- if n == -1 {
+ if _, err := dut.SendWithErrno(context.Background(), t, socket, sampleData, 0); err != unix.Errno(0) {
t.Errorf("failed to send on DUT: %s", err)
- return
}
}()
// Wait for the goroutine to be scheduled and before it
// blocks on endpoint send/receive.
- block.Wait()
+ <-start
// The following sleep is used to prevent the connection
// from being established before we are blocked: there is
// still a small time window between we sending the RPC
// request and the system actually being blocked.
time.Sleep(100 * time.Millisecond)
+ select {
+ case <-done:
+ t.Fatal("expected send to be blocked in SYN-SENT")
+ default:
+ }
+
// Bring the connection to Established.
conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)})
+
+ <-done
+
// Expect the data from the DUT's enqueued send request.
//
// On Linux, this can be piggybacked with the ACK completing the
@@ -126,21 +127,16 @@ func TestQueueRecvInSynSentHandshake(t *testing.T) {
// Test blocking read.
dut.SetNonBlocking(t, socket, false)
- var wg sync.WaitGroup
- defer wg.Wait()
- wg.Add(1)
- var block sync.WaitGroup
- block.Add(1)
+ start := make(chan struct{})
+ done := make(chan struct{})
go func() {
- defer wg.Done()
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
- defer cancel()
+ defer close(done)
- block.Done()
+ close(start)
// Issue RECEIVE call in SYN-SENT, this should be queued for
// process until the connection is established.
- n, buff, err := dut.RecvWithErrno(ctx, t, socket, int32(len(sampleData)), 0)
- if n == -1 {
+ n, buff, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0)
+ if err != unix.Errno(0) {
t.Errorf("failed to recv on DUT: %s", err)
return
}
@@ -151,7 +147,8 @@ func TestQueueRecvInSynSentHandshake(t *testing.T) {
// Wait for the goroutine to be scheduled and before it
// blocks on endpoint send/receive.
- block.Wait()
+ <-start
+
// The following sleep is used to prevent the connection
// from being established before we are blocked: there is
// still a small time window between we sending the RPC
@@ -169,6 +166,8 @@ func TestQueueRecvInSynSentHandshake(t *testing.T) {
if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil {
t.Fatalf("expected an ACK from DUT, but got none: %s", err)
}
+
+ <-done
}
// TestQueueSendInSynSentRST tests send behavior when the TCP state
@@ -192,20 +191,15 @@ func TestQueueSendInSynSentRST(t *testing.T) {
// Test blocking send.
dut.SetNonBlocking(t, socket, false)
- var wg sync.WaitGroup
- defer wg.Wait()
- wg.Add(1)
- var block sync.WaitGroup
- block.Add(1)
+ start := make(chan struct{})
+ done := make(chan struct{})
go func() {
- defer wg.Done()
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
- defer cancel()
+ defer close(done)
- block.Done()
+ close(start)
// Issue SEND call in SYN-SENT, this should be queued for
// process until the connection is established.
- n, err := dut.SendWithErrno(ctx, t, socket, sampleData, 0)
+ n, err := dut.SendWithErrno(context.Background(), t, socket, sampleData, 0)
if err != unix.ECONNREFUSED {
t.Errorf("expected error %s, got %s", unix.ECONNREFUSED, err)
}
@@ -216,14 +210,23 @@ func TestQueueSendInSynSentRST(t *testing.T) {
// Wait for the goroutine to be scheduled and before it
// blocks on endpoint send/receive.
- block.Wait()
+ <-start
+
// The following sleep is used to prevent the connection
// from being established before we are blocked: there is
// still a small time window between we sending the RPC
// request and the system actually being blocked.
time.Sleep(100 * time.Millisecond)
+ select {
+ case <-done:
+ t.Fatal("expected send to be blocked in SYN-SENT")
+ default:
+ }
+
conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)})
+
+ <-done
}
// TestQueueRecvInSynSentRST tests recv behavior when the TCP state
@@ -251,20 +254,15 @@ func TestQueueRecvInSynSentRST(t *testing.T) {
// Test blocking read.
dut.SetNonBlocking(t, socket, false)
- var wg sync.WaitGroup
- defer wg.Wait()
- wg.Add(1)
- var block sync.WaitGroup
- block.Add(1)
+ start := make(chan struct{})
+ done := make(chan struct{})
go func() {
- defer wg.Done()
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
- defer cancel()
+ defer close(done)
- block.Done()
+ close(start)
// Issue RECEIVE call in SYN-SENT, this should be queued for
// process until the connection is established.
- n, _, err := dut.RecvWithErrno(ctx, t, socket, int32(len(sampleData)), 0)
+ n, _, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0)
if err != unix.ECONNREFUSED {
t.Errorf("expected error %s, got %s", unix.ECONNREFUSED, err)
}
@@ -275,7 +273,8 @@ func TestQueueRecvInSynSentRST(t *testing.T) {
// Wait for the goroutine to be scheduled and before it
// blocks on endpoint send/receive.
- block.Wait()
+ <-start
+
// The following sleep is used to prevent the connection
// from being established before we are blocked: there is
// still a small time window between we sending the RPC
@@ -283,4 +282,5 @@ func TestQueueRecvInSynSentRST(t *testing.T) {
time.Sleep(100 * time.Millisecond)
conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)})
+ <-done
}
diff --git a/test/packetimpact/tests/tcp_rack_test.go b/test/packetimpact/tests/tcp_rack_test.go
index ff1431bbf..5a60bf712 100644
--- a/test/packetimpact/tests/tcp_rack_test.go
+++ b/test/packetimpact/tests/tcp_rack_test.go
@@ -21,8 +21,6 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/binary"
- "gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/seqnum"
"gvisor.dev/gvisor/test/packetimpact/testbench"
@@ -69,12 +67,7 @@ func closeSACKConnection(t *testing.T, dut testbench.DUT, conn testbench.TCPIPv4
}
func getRTTAndRTO(t *testing.T, dut testbench.DUT, acceptFd int32) (rtt, rto time.Duration) {
- info := linux.TCPInfo{}
- infoBytes := dut.GetSockOpt(t, acceptFd, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo))
- if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want {
- t.Fatalf("expected %T, got %d bytes want %d bytes", info, got, want)
- }
- binary.Unmarshal(infoBytes, hostarch.ByteOrder, &info)
+ info := dut.GetSockOptTCPInfo(t, acceptFd)
return time.Duration(info.RTT) * time.Microsecond, time.Duration(info.RTO) * time.Microsecond
}
@@ -402,12 +395,7 @@ func TestRACKWithLostRetransmission(t *testing.T) {
}
// Check the congestion control state.
- info := linux.TCPInfo{}
- infoBytes := dut.GetSockOpt(t, acceptFd, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo))
- if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want {
- t.Fatalf("expected %T, got %d bytes want %d bytes", info, got, want)
- }
- binary.Unmarshal(infoBytes, hostarch.ByteOrder, &info)
+ info := dut.GetSockOptTCPInfo(t, acceptFd)
if info.CaState != linux.TCP_CA_Recovery {
t.Fatalf("expected connection to be in fast recovery, want: %v got: %v", linux.TCP_CA_Recovery, info.CaState)
}
diff --git a/test/packetimpact/tests/tcp_retransmits_test.go b/test/packetimpact/tests/tcp_retransmits_test.go
index 1eafe20c3..d3fb789f4 100644
--- a/test/packetimpact/tests/tcp_retransmits_test.go
+++ b/test/packetimpact/tests/tcp_retransmits_test.go
@@ -21,9 +21,6 @@ import (
"time"
"golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/abi/linux"
- "gvisor.dev/gvisor/pkg/binary"
- "gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/test/packetimpact/testbench"
)
@@ -33,12 +30,7 @@ func init() {
}
func getRTO(t *testing.T, dut testbench.DUT, acceptFd int32) (rto time.Duration) {
- info := linux.TCPInfo{}
- infoBytes := dut.GetSockOpt(t, acceptFd, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo))
- if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want {
- t.Fatalf("unexpected size for TCP_INFO, got %d bytes want %d bytes", got, want)
- }
- binary.Unmarshal(infoBytes, hostarch.ByteOrder, &info)
+ info := dut.GetSockOptTCPInfo(t, acceptFd)
return time.Duration(info.RTO) * time.Microsecond
}
diff --git a/test/packetimpact/tests/tcp_syncookie_test.go b/test/packetimpact/tests/tcp_syncookie_test.go
index 1c21c62ff..6be09996b 100644
--- a/test/packetimpact/tests/tcp_syncookie_test.go
+++ b/test/packetimpact/tests/tcp_syncookie_test.go
@@ -16,9 +16,12 @@ package tcp_syncookie_test
import (
"flag"
+ "fmt"
+ "math"
"testing"
"time"
+ "github.com/google/go-cmp/cmp"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/test/packetimpact/testbench"
@@ -28,43 +31,121 @@ func init() {
testbench.Initialize(flag.CommandLine)
}
-// TestSynCookie test if the DUT listener is replying back using syn cookies.
-// The test does not complete the handshake by not sending the ACK to SYNACK.
-// When syncookies are not used, this forces the listener to retransmit SYNACK.
-// And when syncookies are being used, there is no such retransmit.
+// TestTCPSynCookie tests for ACK handling for connections in SYNRCVD state
+// connections with and without syncookies. It verifies if the passive open
+// connection is indeed using syncookies before proceeding.
func TestTCPSynCookie(t *testing.T) {
dut := testbench.NewDUT(t)
+ for _, tt := range []struct {
+ accept bool
+ flags header.TCPFlags
+ }{
+ {accept: true, flags: header.TCPFlagAck},
+ {accept: true, flags: header.TCPFlagAck | header.TCPFlagPsh},
+ {accept: false, flags: header.TCPFlagAck | header.TCPFlagSyn},
+ {accept: true, flags: header.TCPFlagAck | header.TCPFlagFin},
+ {accept: false, flags: header.TCPFlagAck | header.TCPFlagRst},
+ {accept: false, flags: header.TCPFlagRst},
+ } {
+ t.Run(fmt.Sprintf("flags=%s", tt.flags), func(t *testing.T) {
+ // Make a copy before parallelizing the test and refer to that
+ // within the test. Otherwise, the test reference could be pointing
+ // to an incorrect variant based on how it is scheduled.
+ test := tt
- // Listening endpoint accepts one more connection than the listen backlog.
- _, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/)
+ t.Parallel()
- var withoutSynCookieConn testbench.TCPIPv4
- var withSynCookieConn testbench.TCPIPv4
+ // Listening endpoint accepts one more connection than the listen
+ // backlog. Listener starts using syncookies when it sees a new SYN
+ // and has backlog size of connections in SYNRCVD state. Keep the
+ // listen backlog 1, so that the test can define 2 connections
+ // without and with using syncookies.
+ listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/)
+ defer dut.Close(t, listenFD)
- // Test if the DUT listener replies to more SYNs than listen backlog+1
- for _, conn := range []*testbench.TCPIPv4{&withoutSynCookieConn, &withSynCookieConn} {
- *conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
- }
- defer withoutSynCookieConn.Close(t)
- defer withSynCookieConn.Close(t)
+ var withoutSynCookieConn testbench.TCPIPv4
+ var withSynCookieConn testbench.TCPIPv4
- checkSynAck := func(t *testing.T, conn *testbench.TCPIPv4, expectRetransmit bool) {
- // Expect dut connection to have transitioned to SYN-RCVD state.
- conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)})
- if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil {
- t.Fatalf("expected SYN-ACK, but got %s", err)
- }
+ for _, conn := range []*testbench.TCPIPv4{&withoutSynCookieConn, &withSynCookieConn} {
+ *conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ }
+ defer withoutSynCookieConn.Close(t)
+ defer withSynCookieConn.Close(t)
- // If the DUT listener is using syn cookies, it will not retransmit SYNACK
- got, err := conn.ExpectData(t, &testbench.TCP{SeqNum: testbench.Uint32(uint32(*conn.RemoteSeqNum(t) - 1)), Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, 2*time.Second)
- if expectRetransmit && err != nil {
- t.Fatalf("expected retransmitted SYN-ACK, but got %s", err)
- }
- if !expectRetransmit && err == nil {
- t.Fatalf("expected no retransmitted SYN-ACK, but got %s", got)
- }
- }
+ // Setup the 2 connections in SYNRCVD state and verify if one of the
+ // connection is indeed using syncookies by checking for absence of
+ // SYNACK retransmits.
+ for _, c := range []struct {
+ desc string
+ conn *testbench.TCPIPv4
+ expectRetransmit bool
+ }{
+ {desc: "without syncookies", conn: &withoutSynCookieConn, expectRetransmit: true},
+ {desc: "with syncookies", conn: &withSynCookieConn, expectRetransmit: false},
+ } {
+ t.Run(c.desc, func(t *testing.T) {
+ // Expect dut connection to have transitioned to SYNRCVD state.
+ c.conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)})
+ if _, err := c.conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil {
+ t.Fatalf("expected SYNACK, but got %s", err)
+ }
+
+ // If the DUT listener is using syn cookies, it will not retransmit SYNACK.
+ got, err := c.conn.ExpectData(t, &testbench.TCP{SeqNum: testbench.Uint32(uint32(*c.conn.RemoteSeqNum(t) - 1)), Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, 2*time.Second)
+ if c.expectRetransmit && err != nil {
+ t.Fatalf("expected retransmitted SYNACK, but got %s", err)
+ }
+ if !c.expectRetransmit && err == nil {
+ t.Fatalf("expected no retransmitted SYNACK, but got %s", got)
+ }
+ })
+ }
- t.Run("without syncookies", func(t *testing.T) { checkSynAck(t, &withoutSynCookieConn, true /*expectRetransmit*/) })
- t.Run("with syncookies", func(t *testing.T) { checkSynAck(t, &withSynCookieConn, false /*expectRetransmit*/) })
+ // Check whether ACKs with the given flags completes the handshake.
+ for _, c := range []struct {
+ desc string
+ conn *testbench.TCPIPv4
+ }{
+ {desc: "with syncookies", conn: &withSynCookieConn},
+ {desc: "without syncookies", conn: &withoutSynCookieConn},
+ } {
+ t.Run(c.desc, func(t *testing.T) {
+ pfds := dut.Poll(t, []unix.PollFd{{Fd: listenFD, Events: math.MaxInt16}}, 0 /*timeout*/)
+ if got, want := len(pfds), 0; got != want {
+ t.Fatalf("dut.Poll(...) = %d, want = %d", got, want)
+ }
+
+ sampleData := []byte("Sample Data")
+ samplePayload := &testbench.Payload{Bytes: sampleData}
+
+ c.conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(test.flags)}, samplePayload)
+ pfds = dut.Poll(t, []unix.PollFd{{Fd: listenFD, Events: unix.POLLIN}}, time.Second)
+ want := 0
+ if test.accept {
+ want = 1
+ }
+ if got := len(pfds); got != want {
+ t.Fatalf("got dut.Poll(...) = %d, want = %d", got, want)
+ }
+ // Accept the connection to enable poll on any subsequent connection.
+ if test.accept {
+ fd, _ := dut.Accept(t, listenFD)
+ if test.flags.Contains(header.TCPFlagFin) {
+ if dut.Uname.IsLinux() {
+ dut.PollOne(t, fd, unix.POLLIN|unix.POLLRDHUP, time.Second)
+ } else {
+ // TODO(gvisor.dev/issue/6015): Notify POLLIN|POLLRDHUP on incoming FIN.
+ dut.PollOne(t, fd, unix.POLLIN, time.Second)
+ }
+ }
+ got := dut.Recv(t, fd, int32(len(sampleData)), 0)
+ if diff := cmp.Diff(got, sampleData); diff != "" {
+ t.Fatalf("dut.Recv: data mismatch (-want +got):\n%s", diff)
+ }
+ dut.Close(t, fd)
+ }
+ })
+ }
+ })
+ }
}
diff --git a/test/packetimpact/tests/tcp_synsent_reset_test.go b/test/packetimpact/tests/tcp_synsent_reset_test.go
index cccb0abc6..fe53e7061 100644
--- a/test/packetimpact/tests/tcp_synsent_reset_test.go
+++ b/test/packetimpact/tests/tcp_synsent_reset_test.go
@@ -20,6 +20,7 @@ import (
"time"
"golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/test/packetimpact/testbench"
)
@@ -29,7 +30,7 @@ func init() {
}
// dutSynSentState sets up the dut connection in SYN-SENT state.
-func dutSynSentState(t *testing.T) (*testbench.DUT, *testbench.TCPIPv4, uint16, uint16) {
+func dutSynSentState(t *testing.T) (*testbench.DUT, *testbench.TCPIPv4, int32, uint16, uint16) {
t.Helper()
dut := testbench.NewDUT(t)
@@ -46,26 +47,29 @@ func dutSynSentState(t *testing.T) (*testbench.DUT, *testbench.TCPIPv4, uint16,
t.Fatalf("expected SYN\n")
}
- return &dut, &conn, port, clientPort
+ return &dut, &conn, clientFD, port, clientPort
}
// TestTCPSynSentReset tests RFC793, p67: SYN-SENT to CLOSED transition.
func TestTCPSynSentReset(t *testing.T) {
- _, conn, _, _ := dutSynSentState(t)
+ dut, conn, fd, _, _ := dutSynSentState(t)
defer conn.Close(t)
conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(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, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, nil, time.Second); err != nil {
t.Fatalf("expected a TCP RST")
}
+ info := dut.GetSockOptTCPInfo(t, fd)
+ if got, want := uint32(info.State), linux.TCP_CLOSE; got != want {
+ t.Fatalf("got %d want %d", got, want)
+ }
}
// TestTCPSynSentRcvdReset tests RFC793, p70, SYN-SENT to SYN-RCVD to CLOSED
// transitions.
func TestTCPSynSentRcvdReset(t *testing.T) {
- dut, c, remotePort, clientPort := dutSynSentState(t)
+ dut, c, fd, remotePort, clientPort := dutSynSentState(t)
defer c.Close(t)
conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &remotePort, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &remotePort})
@@ -79,9 +83,12 @@ func TestTCPSynSentRcvdReset(t *testing.T) {
}
conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)})
// Expect the connection to have transitioned SYN-RCVD to CLOSED.
- // TODO(gvisor.dev/issue/478): Check for TCP_INFO on the dut side.
conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, nil, time.Second); err != nil {
t.Fatalf("expected a TCP RST")
}
+ info := dut.GetSockOptTCPInfo(t, fd)
+ if got, want := uint32(info.State), linux.TCP_CLOSE; got != want {
+ t.Fatalf("got %d want %d", got, want)
+ }
}
diff --git a/test/packetimpact/tests/udp_icmp_error_propagation_test.go b/test/packetimpact/tests/udp_icmp_error_propagation_test.go
index 3159d5b89..bb33ca4b3 100644
--- a/test/packetimpact/tests/udp_icmp_error_propagation_test.go
+++ b/test/packetimpact/tests/udp_icmp_error_propagation_test.go
@@ -58,16 +58,20 @@ func (e icmpError) String() string {
return "Unknown ICMP error"
}
-func (e icmpError) ToICMPv4() *testbench.ICMPv4 {
+func (e icmpError) ToICMPv4(payload []byte) *testbench.ICMPv4 {
switch e {
case portUnreachable:
return &testbench.ICMPv4{
- Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable),
- Code: testbench.ICMPv4Code(header.ICMPv4PortUnreachable)}
+ Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable),
+ Code: testbench.ICMPv4Code(header.ICMPv4PortUnreachable),
+ Payload: payload,
+ }
case timeToLiveExceeded:
return &testbench.ICMPv4{
- Type: testbench.ICMPv4Type(header.ICMPv4TimeExceeded),
- Code: testbench.ICMPv4Code(header.ICMPv4TTLExceeded)}
+ Type: testbench.ICMPv4Type(header.ICMPv4TimeExceeded),
+ Code: testbench.ICMPv4Code(header.ICMPv4TTLExceeded),
+ Payload: payload,
+ }
}
return nil
}
@@ -101,8 +105,6 @@ func wantErrno(c connectionMode, icmpErr icmpError) unix.Errno {
func sendICMPError(t *testing.T, conn *testbench.UDPIPv4, icmpErr icmpError, udp *testbench.UDP) {
t.Helper()
- layers := conn.CreateFrame(t, nil)
- layers = layers[:len(layers)-1]
ip, ok := udp.Prev().(*testbench.IPv4)
if !ok {
t.Fatalf("expected %s to be IPv4", udp.Prev())
@@ -113,12 +115,15 @@ func sendICMPError(t *testing.T, conn *testbench.UDPIPv4, icmpErr icmpError, udp
// to 1.
ip.Checksum = nil
}
- // Note that the ICMP payload is valid in this case because the UDP
- // payload is empty. If the UDP payload were not empty, the packet
- // length during serialization may not be calculated correctly,
- // resulting in a mal-formed packet.
- layers = append(layers, icmpErr.ToICMPv4(), ip, udp)
+ icmpPayload := testbench.Layers{ip, udp}
+ bytes, err := icmpPayload.ToBytes()
+ if err != nil {
+ t.Fatalf("got icmpPayload.ToBytes() = (_, %s), want = (_, nil)", err)
+ }
+
+ layers := conn.CreateFrame(t, nil)
+ layers[len(layers)-1] = icmpErr.ToICMPv4(bytes)
conn.SendFrameStateless(t, layers)
}
@@ -136,8 +141,6 @@ func testRecv(ctx context.Context, t *testing.T, d testData) {
d.conn.Send(t, testbench.UDP{})
if d.wantErrno != unix.Errno(0) {
- ctx, cancel := context.WithTimeout(ctx, time.Second)
- defer cancel()
ret, _, err := d.dut.RecvWithErrno(ctx, t, d.remoteFD, 100, 0)
if ret != -1 {
t.Fatalf("recv after ICMP error succeeded unexpectedly, expected (%[1]d) %[1]v", d.wantErrno)
@@ -162,8 +165,6 @@ func testSendTo(ctx context.Context, t *testing.T, d testData) {
}
if d.wantErrno != unix.Errno(0) {
- ctx, cancel := context.WithTimeout(ctx, time.Second)
- defer cancel()
ret, err := d.dut.SendToWithErrno(ctx, t, d.remoteFD, nil, 0, d.conn.LocalAddr(t))
if ret != -1 {
@@ -310,10 +311,7 @@ func TestICMPErrorDuringUDPRecv(t *testing.T) {
defer wg.Done()
if wantErrno != unix.Errno(0) {
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
-
- ret, _, err := dut.RecvWithErrno(ctx, t, remoteFD, 100, 0)
+ ret, _, err := dut.RecvWithErrno(context.Background(), t, remoteFD, 100, 0)
if ret != -1 {
t.Errorf("recv during ICMP error succeeded unexpectedly, expected (%[1]d) %[1]v", wantErrno)
return
@@ -324,10 +322,7 @@ func TestICMPErrorDuringUDPRecv(t *testing.T) {
}
}
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
-
- if ret, _, err := dut.RecvWithErrno(ctx, t, remoteFD, 100, 0); ret == -1 {
+ if ret, _, err := dut.RecvWithErrno(context.Background(), t, remoteFD, 100, 0); ret == -1 {
t.Errorf("recv after ICMP error failed with (%[1]d) %[1]", err)
}
}()
@@ -335,10 +330,7 @@ func TestICMPErrorDuringUDPRecv(t *testing.T) {
go func() {
defer wg.Done()
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
-
- if ret, _, err := dut.RecvWithErrno(ctx, t, cleanFD, 100, 0); ret == -1 {
+ if ret, _, err := dut.RecvWithErrno(context.Background(), t, cleanFD, 100, 0); ret == -1 {
t.Errorf("recv on clean socket failed with (%[1]d) %[1]", err)
}
}()
diff --git a/test/packetimpact/tests/udp_send_recv_dgram_test.go b/test/packetimpact/tests/udp_send_recv_dgram_test.go
deleted file mode 100644
index 230b012c7..000000000
--- a/test/packetimpact/tests/udp_send_recv_dgram_test.go
+++ /dev/null
@@ -1,329 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package udp_send_recv_dgram_test
-
-import (
- "context"
- "flag"
- "fmt"
- "net"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
- testbench.RPCTimeout = 500 * time.Millisecond
-}
-
-type udpConn interface {
- SrcPort(*testing.T) uint16
- SendFrame(*testing.T, testbench.Layers, ...testbench.Layer)
- ExpectFrame(*testing.T, testbench.Layers, time.Duration) (testbench.Layers, error)
- Close(*testing.T)
-}
-
-type testCase struct {
- bindTo, sendTo net.IP
- sendToBroadcast, bindToDevice, expectData bool
-}
-
-func TestUDP(t *testing.T) {
- dut := testbench.NewDUT(t)
- subnetBcast := func() net.IP {
- subnet := (&tcpip.AddressWithPrefix{
- Address: tcpip.Address(dut.Net.RemoteIPv4.To4()),
- PrefixLen: dut.Net.IPv4PrefixLength,
- }).Subnet()
- return net.IP(subnet.Broadcast())
- }()
-
- t.Run("Send", func(t *testing.T) {
- var testCases []testCase
- // Test every valid combination of bound/unbound, broadcast/multicast/unicast
- // bound/destination address, and bound/not-bound to device.
- for _, bindTo := range []net.IP{
- nil, // Do not bind.
- net.IPv4zero,
- net.IPv4bcast,
- net.IPv4allsys,
- subnetBcast,
- dut.Net.RemoteIPv4,
- dut.Net.RemoteIPv6,
- } {
- for _, sendTo := range []net.IP{
- net.IPv4bcast,
- net.IPv4allsys,
- subnetBcast,
- dut.Net.LocalIPv4,
- dut.Net.LocalIPv6,
- } {
- // Cannot send to an IPv4 address from a socket bound to IPv6 (except for IPv4-mapped IPv6),
- // and viceversa.
- if bindTo != nil && ((bindTo.To4() == nil) != (sendTo.To4() == nil)) {
- continue
- }
- for _, bindToDevice := range []bool{true, false} {
- expectData := true
- switch {
- case bindTo.Equal(dut.Net.RemoteIPv4):
- // If we're explicitly bound to an interface's unicast address,
- // packets are always sent on that interface.
- case bindToDevice:
- // If we're explicitly bound to an interface, packets are always
- // sent on that interface.
- case !sendTo.Equal(net.IPv4bcast) && !sendTo.IsMulticast():
- // If we're not sending to limited broadcast or multicast, the route table
- // will be consulted and packets will be sent on the correct interface.
- default:
- expectData = false
- }
- testCases = append(
- testCases,
- testCase{
- bindTo: bindTo,
- sendTo: sendTo,
- sendToBroadcast: sendTo.Equal(subnetBcast) || sendTo.Equal(net.IPv4bcast),
- bindToDevice: bindToDevice,
- expectData: expectData,
- },
- )
- }
- }
- }
- for _, tc := range testCases {
- boundTestCaseName := "unbound"
- if tc.bindTo != nil {
- boundTestCaseName = fmt.Sprintf("bindTo=%s", tc.bindTo)
- }
- t.Run(fmt.Sprintf("%s/sendTo=%s/bindToDevice=%t/expectData=%t", boundTestCaseName, tc.sendTo, tc.bindToDevice, tc.expectData), func(t *testing.T) {
- runTestCase(
- t,
- dut,
- tc,
- func(t *testing.T, dut testbench.DUT, conn udpConn, socketFD int32, tc testCase, payload []byte, layers testbench.Layers) {
- var destSockaddr unix.Sockaddr
- if sendTo4 := tc.sendTo.To4(); sendTo4 != nil {
- addr := unix.SockaddrInet4{
- Port: int(conn.SrcPort(t)),
- }
- copy(addr.Addr[:], sendTo4)
- destSockaddr = &addr
- } else {
- addr := unix.SockaddrInet6{
- Port: int(conn.SrcPort(t)),
- ZoneId: dut.Net.RemoteDevID,
- }
- copy(addr.Addr[:], tc.sendTo.To16())
- destSockaddr = &addr
- }
- if got, want := dut.SendTo(t, socketFD, payload, 0, destSockaddr), len(payload); int(got) != want {
- t.Fatalf("got dut.SendTo = %d, want %d", got, want)
- }
- layers = append(layers, &testbench.Payload{
- Bytes: payload,
- })
- _, err := conn.ExpectFrame(t, layers, time.Second)
-
- if !tc.expectData && err == nil {
- t.Fatal("received unexpected packet, socket is not bound to device")
- }
- if err != nil && tc.expectData {
- t.Fatal(err)
- }
- },
- )
- })
- }
- })
- t.Run("Recv", func(t *testing.T) {
- // Test every valid combination of broadcast/multicast/unicast
- // bound/destination address, and bound/not-bound to device.
- var testCases []testCase
- for _, addr := range []net.IP{
- net.IPv4bcast,
- net.IPv4allsys,
- dut.Net.RemoteIPv4,
- dut.Net.RemoteIPv6,
- } {
- for _, bindToDevice := range []bool{true, false} {
- testCases = append(
- testCases,
- testCase{
- bindTo: addr,
- sendTo: addr,
- sendToBroadcast: addr.Equal(subnetBcast) || addr.Equal(net.IPv4bcast),
- bindToDevice: bindToDevice,
- expectData: true,
- },
- )
- }
- }
- for _, bindTo := range []net.IP{
- net.IPv4zero,
- subnetBcast,
- dut.Net.RemoteIPv4,
- } {
- for _, sendTo := range []net.IP{
- subnetBcast,
- net.IPv4bcast,
- net.IPv4allsys,
- } {
- // TODO(gvisor.dev/issue/4896): Add bindTo=subnetBcast/sendTo=IPv4bcast
- // and bindTo=subnetBcast/sendTo=IPv4allsys test cases.
- if bindTo.Equal(subnetBcast) && (sendTo.Equal(net.IPv4bcast) || sendTo.IsMulticast()) {
- continue
- }
- // Expect that a socket bound to a unicast address does not receive
- // packets sent to an address other than the bound unicast address.
- //
- // Note: we cannot use net.IP.IsGlobalUnicast to test this condition
- // because IsGlobalUnicast does not check whether the address is the
- // subnet broadcast, and returns true in that case.
- expectData := !bindTo.Equal(dut.Net.RemoteIPv4) || sendTo.Equal(dut.Net.RemoteIPv4)
- for _, bindToDevice := range []bool{true, false} {
- testCases = append(
- testCases,
- testCase{
- bindTo: bindTo,
- sendTo: sendTo,
- sendToBroadcast: sendTo.Equal(subnetBcast) || sendTo.Equal(net.IPv4bcast),
- bindToDevice: bindToDevice,
- expectData: expectData,
- },
- )
- }
- }
- }
- for _, tc := range testCases {
- t.Run(fmt.Sprintf("bindTo=%s/sendTo=%s/bindToDevice=%t/expectData=%t", tc.bindTo, tc.sendTo, tc.bindToDevice, tc.expectData), func(t *testing.T) {
- runTestCase(
- t,
- dut,
- tc,
- func(t *testing.T, dut testbench.DUT, conn udpConn, socketFD int32, tc testCase, payload []byte, layers testbench.Layers) {
- conn.SendFrame(t, layers, &testbench.Payload{Bytes: payload})
-
- if tc.expectData {
- got, want := dut.Recv(t, socketFD, int32(len(payload)+1), 0), payload
- if diff := cmp.Diff(want, got); diff != "" {
- t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff)
- }
- } else {
- // Expected receive error, set a short receive timeout.
- dut.SetSockOptTimeval(
- t,
- socketFD,
- unix.SOL_SOCKET,
- unix.SO_RCVTIMEO,
- &unix.Timeval{
- Sec: 1,
- Usec: 0,
- },
- )
- ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, socketFD, 100, 0)
- if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK {
- t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno)
- }
- }
- },
- )
- })
- }
- })
-}
-
-func runTestCase(
- t *testing.T,
- dut testbench.DUT,
- tc testCase,
- runTc func(t *testing.T, dut testbench.DUT, conn udpConn, socketFD int32, tc testCase, payload []byte, layers testbench.Layers),
-) {
- var (
- socketFD int32
- outgoingUDP, incomingUDP testbench.UDP
- )
- if tc.bindTo != nil {
- var remotePort uint16
- socketFD, remotePort = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, tc.bindTo)
- outgoingUDP.DstPort = &remotePort
- incomingUDP.SrcPort = &remotePort
- } else {
- // An unbound socket will auto-bind to INNADDR_ANY and a random
- // port on sendto.
- socketFD = dut.Socket(t, unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP)
- }
- defer dut.Close(t, socketFD)
- if tc.bindToDevice {
- dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName))
- }
-
- var ethernetLayer testbench.Ether
- if tc.sendToBroadcast {
- dut.SetSockOptInt(t, socketFD, unix.SOL_SOCKET, unix.SO_BROADCAST, 1)
-
- // When sending to broadcast (subnet or limited), the expected ethernet
- // address is also broadcast.
- ethernetBroadcastAddress := header.EthernetBroadcastAddress
- ethernetLayer.DstAddr = &ethernetBroadcastAddress
- } else if tc.sendTo.IsMulticast() {
- ethernetMulticastAddress := header.EthernetAddressFromMulticastIPv4Address(tcpip.Address(tc.sendTo.To4()))
- ethernetLayer.DstAddr = &ethernetMulticastAddress
- }
- expectedLayers := testbench.Layers{&ethernetLayer}
-
- var conn udpConn
- if sendTo4 := tc.sendTo.To4(); sendTo4 != nil {
- v4Conn := dut.Net.NewUDPIPv4(t, outgoingUDP, incomingUDP)
- conn = &v4Conn
- expectedLayers = append(
- expectedLayers,
- &testbench.IPv4{
- DstAddr: testbench.Address(tcpip.Address(sendTo4)),
- },
- )
- } else {
- v6Conn := dut.Net.NewUDPIPv6(t, outgoingUDP, incomingUDP)
- conn = &v6Conn
- expectedLayers = append(
- expectedLayers,
- &testbench.IPv6{
- DstAddr: testbench.Address(tcpip.Address(tc.sendTo)),
- },
- )
- }
- defer conn.Close(t)
-
- expectedLayers = append(expectedLayers, &incomingUDP)
- for _, v := range []struct {
- name string
- payload []byte
- }{
- {"emptypayload", nil},
- {"small payload", []byte("hello world")},
- {"1kPayload", testbench.GenerateRandomPayload(t, 1<<10)},
- // Even though UDP allows larger dgrams we don't test it here as
- // they need to be fragmented and written out as individual
- // frames.
- } {
- runTc(t, dut, conn, socketFD, tc, v.payload, expectedLayers)
- }
-}
diff --git a/test/runner/BUILD b/test/runner/BUILD
index 582d2946d..2d93aa6af 100644
--- a/test/runner/BUILD
+++ b/test/runner/BUILD
@@ -5,9 +5,10 @@ package(licenses = ["notice"])
go_binary(
name = "runner",
testonly = 1,
- srcs = ["runner.go"],
+ srcs = ["main.go"],
data = [
"//runsc",
+ "//test/runner/setup_container",
],
visibility = ["//:sandbox"],
deps = [
diff --git a/test/runner/defs.bzl b/test/runner/defs.bzl
index 416f51935..405e03832 100644
--- a/test/runner/defs.bzl
+++ b/test/runner/defs.bzl
@@ -103,6 +103,8 @@ def _syscall_test(
if platform == "native":
tags.append("nogotsan")
+ container = "container" in tags
+
runner_args = [
# Arguments are passed directly to runner binary.
"--platform=" + platform,
@@ -115,6 +117,7 @@ def _syscall_test(
"--fuse=" + str(fuse),
"--strace=" + str(debug),
"--debug=" + str(debug),
+ "--container=" + str(container),
]
# Call the rule above.
diff --git a/test/runner/runner.go b/test/runner/main.go
index 7e8e88ba2..34e9c6279 100644
--- a/test/runner/runner.go
+++ b/test/runner/main.go
@@ -40,16 +40,18 @@ import (
)
var (
- debug = flag.Bool("debug", false, "enable debug logs")
- strace = flag.Bool("strace", false, "enable strace logs")
- platform = flag.String("platform", "ptrace", "platform to run on")
- network = flag.String("network", "none", "network stack to run on (sandbox, host, none)")
- useTmpfs = flag.Bool("use-tmpfs", false, "mounts tmpfs for /tmp")
- fileAccess = flag.String("file-access", "exclusive", "mounts root in exclusive or shared mode")
- overlay = flag.Bool("overlay", false, "wrap filesystem mounts with writable tmpfs overlay")
- vfs2 = flag.Bool("vfs2", false, "enable VFS2")
- fuse = flag.Bool("fuse", false, "enable FUSE")
- runscPath = flag.String("runsc", "", "path to runsc binary")
+ debug = flag.Bool("debug", false, "enable debug logs")
+ strace = flag.Bool("strace", false, "enable strace logs")
+ platform = flag.String("platform", "ptrace", "platform to run on")
+ network = flag.String("network", "none", "network stack to run on (sandbox, host, none)")
+ useTmpfs = flag.Bool("use-tmpfs", false, "mounts tmpfs for /tmp")
+ fileAccess = flag.String("file-access", "exclusive", "mounts root in exclusive or shared mode")
+ overlay = flag.Bool("overlay", false, "wrap filesystem mounts with writable tmpfs overlay")
+ vfs2 = flag.Bool("vfs2", false, "enable VFS2")
+ fuse = flag.Bool("fuse", false, "enable FUSE")
+ container = flag.Bool("container", false, "run tests in their own namespaces (user ns, network ns, etc), pretending to be root")
+ setupContainerPath = flag.String("setup-container", "", "path to setup_container binary (for use with --container)")
+ 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
@@ -105,6 +107,27 @@ func runTestCaseNative(testBin string, tc gtest.TestCase, t *testing.T) {
cmd.Stderr = os.Stderr
cmd.SysProcAttr = &unix.SysProcAttr{}
+ if *container {
+ // setup_container takes in its target argv as positional arguments.
+ cmd.Path = *setupContainerPath
+ cmd.Args = append([]string{cmd.Path}, cmd.Args...)
+ cmd.SysProcAttr = &unix.SysProcAttr{
+ Cloneflags: unix.CLONE_NEWUSER | unix.CLONE_NEWNET | unix.CLONE_NEWIPC | unix.CLONE_NEWUTS,
+ // Set current user/group as root inside the namespace.
+ UidMappings: []syscall.SysProcIDMap{
+ {ContainerID: 0, HostID: os.Getuid(), Size: 1},
+ },
+ GidMappings: []syscall.SysProcIDMap{
+ {ContainerID: 0, HostID: os.Getgid(), Size: 1},
+ },
+ GidMappingsEnableSetgroups: false,
+ Credential: &syscall.Credential{
+ Uid: 0,
+ Gid: 0,
+ },
+ }
+ }
+
if specutils.HasCapabilities(capability.CAP_SYS_ADMIN) {
cmd.SysProcAttr.Cloneflags |= unix.CLONE_NEWUTS
}
@@ -454,6 +477,13 @@ func main() {
}
*runscPath = specutils.ExePath
}
+ if *container && *setupContainerPath == "" {
+ setupContainer, err := testutil.FindFile("test/runner/setup_container/setup_container")
+ if err != nil {
+ fatalf("cannot find setup_container: %v", err)
+ }
+ *setupContainerPath = setupContainer
+ }
// Make sure stdout and stderr are opened with O_APPEND, otherwise logs
// from outside the sandbox can (and will) stomp on logs from inside
diff --git a/test/runner/setup_container/BUILD b/test/runner/setup_container/BUILD
new file mode 100644
index 000000000..5b99d1de9
--- /dev/null
+++ b/test/runner/setup_container/BUILD
@@ -0,0 +1,19 @@
+# setup_container contains a shim binary that runs within the test container
+# for syscall tests with container=True.
+
+load("//tools:defs.bzl", "cc_binary")
+
+package(licenses = ["notice"])
+
+cc_binary(
+ name = "setup_container",
+ testonly = 1,
+ srcs = ["setup_container.cc"],
+ visibility = ["//test/runner:__subpackages__"],
+ deps = [
+ "//test/syscalls/linux:socket_netlink_util",
+ "//test/syscalls/linux:socket_test_util",
+ "//test/util:capability_util",
+ "//test/util:posix_error",
+ ],
+)
diff --git a/test/runner/setup_container/setup_container.cc b/test/runner/setup_container/setup_container.cc
new file mode 100644
index 000000000..9a4e3fb8b
--- /dev/null
+++ b/test/runner/setup_container/setup_container.cc
@@ -0,0 +1,79 @@
+// Copyright 2021 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <linux/capability.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "test/syscalls/linux/socket_netlink_util.h"
+#include "test/syscalls/linux/socket_test_util.h"
+#include "test/util/capability_util.h"
+#include "test/util/posix_error.h"
+
+namespace gvisor {
+namespace testing {
+
+// SetupContainer sets up the networking settings in the current container.
+PosixError SetupContainer() {
+ const PosixErrorOr<bool> have_net_admin = HaveCapability(CAP_NET_ADMIN);
+ if (!have_net_admin.ok()) {
+ std::cerr << "Cannot determine if we have CAP_NET_ADMIN." << std::endl;
+ return have_net_admin.error();
+ }
+ if (have_net_admin.ValueOrDie() && !IsRunningOnGvisor()) {
+ PosixErrorOr<FileDescriptor> sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
+ if (!sockfd.ok()) {
+ std::cerr << "Cannot open socket." << std::endl;
+ return sockfd.error();
+ }
+ int sock = sockfd.ValueOrDie().get();
+ struct ifreq ifr = {};
+ strncpy(ifr.ifr_name, "lo", IFNAMSIZ);
+ if (ioctl(sock, SIOCGIFFLAGS, &ifr) == -1) {
+ std::cerr << "Cannot get 'lo' flags: " << strerror(errno) << std::endl;
+ return PosixError(errno);
+ }
+ if ((ifr.ifr_flags & IFF_UP) == 0) {
+ ifr.ifr_flags |= IFF_UP;
+ if (ioctl(sock, SIOCSIFFLAGS, &ifr) == -1) {
+ std::cerr << "Cannot set 'lo' as UP: " << strerror(errno) << std::endl;
+ return PosixError(errno);
+ }
+ }
+ }
+ return NoError();
+}
+
+} // namespace testing
+} // namespace gvisor
+
+using ::gvisor::testing::SetupContainer;
+
+// Binary setup_container initializes the container environment in which tests
+// with container=True will run, then execs the actual test binary.
+// Usage:
+// ./setup_container test_binary [arguments forwarded to test_binary...]
+int main(int argc, char *argv[], char *envp[]) {
+ if (!SetupContainer().ok()) {
+ return 1;
+ }
+ if (argc < 2) {
+ std::cerr << "Must provide arguments to exec." << std::endl;
+ return 2;
+ }
+ if (execve(argv[1], &argv[1], envp) == -1) {
+ std::cerr << "execv returned errno " << errno << std::endl;
+ return 1;
+ }
+}
diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD
index 85412f54b..de08091af 100644
--- a/test/syscalls/BUILD
+++ b/test/syscalls/BUILD
@@ -216,6 +216,10 @@ syscall_test(
)
syscall_test(
+ test = "//test/syscalls/linux:verity_getdents_test",
+)
+
+syscall_test(
test = "//test/syscalls/linux:getrandom_test",
)
@@ -644,6 +648,13 @@ syscall_test(
syscall_test(
size = "large",
shard_count = most_shards,
+ tags = ["container"],
+ test = "//test/syscalls/linux:socket_inet_loopback_isolated_test",
+)
+
+syscall_test(
+ size = "large",
+ 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",
@@ -727,6 +738,7 @@ syscall_test(
)
syscall_test(
+ add_hostinet = True,
test = "//test/syscalls/linux:socket_netdevice_test",
)
diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD
index 729b4c63b..2bf685524 100644
--- a/test/syscalls/linux/BUILD
+++ b/test/syscalls/linux/BUILD
@@ -1,4 +1,4 @@
-load("//tools:defs.bzl", "cc_binary", "cc_library", "default_net_util", "gtest", "select_arch", "select_system")
+load("//tools:defs.bzl", "cc_binary", "cc_library", "default_net_util", "gbenchmark", "gtest", "select_arch", "select_system")
package(
default_visibility = ["//:sandbox"],
@@ -9,6 +9,8 @@ exports_files(
[
"socket.cc",
"socket_inet_loopback.cc",
+ "socket_inet_loopback_isolated.cc",
+ "socket_inet_loopback_test_params.h",
"socket_ip_loopback_blocking.cc",
"socket_ip_tcp_generic_loopback.cc",
"socket_ip_tcp_loopback.cc",
@@ -520,13 +522,14 @@ cc_binary(
srcs = ["concurrency.cc"],
linkstatic = 1,
deps = [
- "@com_google_absl//absl/strings",
- "@com_google_absl//absl/time",
+ gbenchmark,
gtest,
"//test/util:platform_util",
"//test/util:test_main",
"//test/util:test_util",
"//test/util:thread_util",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/time",
],
)
@@ -961,6 +964,22 @@ cc_binary(
)
cc_binary(
+ name = "verity_getdents_test",
+ testonly = 1,
+ srcs = ["verity_getdents.cc"],
+ linkstatic = 1,
+ deps = [
+ "//test/util:capability_util",
+ gtest,
+ "//test/util:fs_util",
+ "//test/util:temp_path",
+ "//test/util:test_main",
+ "//test/util:test_util",
+ "//test/util:verity_util",
+ ],
+)
+
+cc_binary(
name = "getrandom_test",
testonly = 1,
srcs = ["getrandom.cc"],
@@ -1345,6 +1364,7 @@ cc_binary(
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
+ "//test/util:verity_util",
],
)
@@ -1489,6 +1509,7 @@ cc_binary(
"//test/util:cleanup",
"//test/util:posix_error",
"//test/util:pty_util",
+ "//test/util:signal_util",
"//test/util:test_main",
"//test/util:test_util",
"//test/util:thread_util",
@@ -1515,7 +1536,8 @@ cc_binary(
cc_binary(
name = "partial_bad_buffer_test",
testonly = 1,
- srcs = ["partial_bad_buffer.cc"],
+ # Android does not support preadv or pwritev in r22.
+ srcs = select_system(linux = ["partial_bad_buffer.cc"]),
linkstatic = 1,
deps = [
":socket_test_util",
@@ -1552,10 +1574,13 @@ cc_binary(
srcs = ["ping_socket.cc"],
linkstatic = 1,
deps = [
+ ":ip_socket_test_util",
":socket_test_util",
"//test/util:file_descriptor",
+ "@com_google_absl//absl/algorithm:container",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/types:optional",
gtest,
- "//test/util:save_util",
"//test/util:test_main",
"//test/util:test_util",
],
@@ -1574,6 +1599,7 @@ cc_binary(
"@com_google_absl//absl/time",
gtest,
"//test/util:posix_error",
+ "//test/util:signal_util",
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
@@ -1859,6 +1885,7 @@ cc_binary(
linkstatic = 1,
deps = [
"@com_google_absl//absl/flags:flag",
+ "@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
gtest,
"//test/util:capability_util",
@@ -3111,6 +3138,16 @@ cc_binary(
],
)
+cc_library(
+ name = "socket_inet_loopback_test_params",
+ testonly = 1,
+ hdrs = ["socket_inet_loopback_test_params.h"],
+ deps = [
+ ":socket_test_util",
+ gtest,
+ ],
+)
+
cc_binary(
name = "socket_inet_loopback_test",
testonly = 1,
@@ -3118,6 +3155,7 @@ cc_binary(
linkstatic = 1,
deps = [
":ip_socket_test_util",
+ ":socket_inet_loopback_test_params",
":socket_test_util",
"//test/util:file_descriptor",
"@com_google_absl//absl/memory",
@@ -3139,16 +3177,31 @@ cc_binary(
linkstatic = 1,
deps = [
":ip_socket_test_util",
+ ":socket_inet_loopback_test_params",
":socket_test_util",
"//test/util:file_descriptor",
- "@com_google_absl//absl/memory",
"@com_google_absl//absl/strings",
gtest,
"//test/util:posix_error",
"//test/util:save_util",
"//test/util:test_main",
"//test/util:test_util",
- "//test/util:thread_util",
+ ],
+)
+
+cc_binary(
+ name = "socket_inet_loopback_isolated_test",
+ testonly = 1,
+ srcs = ["socket_inet_loopback_isolated.cc"],
+ linkstatic = 1,
+ deps = [
+ ":socket_inet_loopback_test_params",
+ ":socket_netlink_util",
+ ":socket_test_util",
+ gtest,
+ "//test/util:test_main",
+ "//test/util:test_util",
+ "@com_google_absl//absl/time",
],
)
@@ -3671,7 +3724,8 @@ cc_binary(
cc_binary(
name = "sync_test",
testonly = 1,
- srcs = ["sync.cc"],
+ # Android does not support syncfs in r22.
+ srcs = select_system(linux = ["sync.cc"]),
linkstatic = 1,
deps = [
gtest,
@@ -3784,10 +3838,9 @@ cc_binary(
srcs = ["timers.cc"],
linkstatic = 1,
deps = [
- "//test/util:cleanup",
- "@com_google_absl//absl/flags:flag",
- "@com_google_absl//absl/time",
+ gbenchmark,
gtest,
+ "//test/util:cleanup",
"//test/util:logging",
"//test/util:multiprocess_util",
"//test/util:posix_error",
@@ -3795,6 +3848,8 @@ cc_binary(
"//test/util:test_util",
"//test/util:thread_util",
"//test/util:timer_util",
+ "@com_google_absl//absl/flags:flag",
+ "@com_google_absl//absl/time",
],
)
@@ -3966,7 +4021,8 @@ cc_binary(
cc_binary(
name = "utimes_test",
testonly = 1,
- srcs = ["utimes.cc"],
+ # Android does not support futimesat in r22.
+ srcs = select_system(linux = ["utimes.cc"]),
linkstatic = 1,
deps = [
"//test/util:file_descriptor",
@@ -4082,7 +4138,8 @@ cc_binary(
cc_binary(
name = "semaphore_test",
testonly = 1,
- srcs = ["semaphore.cc"],
+ # Android does not support XSI semaphores in r22.
+ srcs = select_system(linux = ["semaphore.cc"]),
linkstatic = 1,
deps = [
"//test/util:capability_util",
diff --git a/test/syscalls/linux/accept_bind.cc b/test/syscalls/linux/accept_bind.cc
index fe560cfc5..ba3747290 100644
--- a/test/syscalls/linux/accept_bind.cc
+++ b/test/syscalls/linux/accept_bind.cc
@@ -37,8 +37,7 @@ TEST_P(AllSocketPairTest, Listen) {
sockets->first_addr_size()),
SyscallSucceeds());
- ASSERT_THAT(listen(sockets->first_fd(), /* backlog = */ 5),
- SyscallSucceeds());
+ ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
}
TEST_P(AllSocketPairTest, ListenIncreaseBacklog) {
@@ -48,10 +47,8 @@ TEST_P(AllSocketPairTest, ListenIncreaseBacklog) {
sockets->first_addr_size()),
SyscallSucceeds());
- ASSERT_THAT(listen(sockets->first_fd(), /* backlog = */ 5),
- SyscallSucceeds());
- ASSERT_THAT(listen(sockets->first_fd(), /* backlog = */ 10),
- SyscallSucceeds());
+ ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
+ ASSERT_THAT(listen(sockets->first_fd(), 10), SyscallSucceeds());
}
TEST_P(AllSocketPairTest, ListenDecreaseBacklog) {
@@ -61,10 +58,8 @@ TEST_P(AllSocketPairTest, ListenDecreaseBacklog) {
sockets->first_addr_size()),
SyscallSucceeds());
- ASSERT_THAT(listen(sockets->first_fd(), /* backlog = */ 5),
- SyscallSucceeds());
- ASSERT_THAT(listen(sockets->first_fd(), /* backlog = */ 1),
- SyscallSucceeds());
+ ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
+ ASSERT_THAT(listen(sockets->first_fd(), 1), SyscallSucceeds());
}
TEST_P(AllSocketPairTest, ListenBacklogSizes) {
diff --git a/test/syscalls/linux/cgroup.cc b/test/syscalls/linux/cgroup.cc
index a009ade7e..f29891571 100644
--- a/test/syscalls/linux/cgroup.cc
+++ b/test/syscalls/linux/cgroup.cc
@@ -227,6 +227,41 @@ TEST(Cgroup, MountRace) {
EXPECT_NO_ERRNO(c.ContainsCallingProcess());
}
+TEST(Cgroup, MountUnmountRace) {
+ SKIP_IF(!CgroupsAvailable());
+
+ TempPath mountpoint = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+
+ const DisableSave ds; // Too many syscalls.
+
+ auto mount_thread = [&mountpoint]() {
+ for (int i = 0; i < 100; ++i) {
+ mount("none", mountpoint.path().c_str(), "cgroup", 0, 0);
+ }
+ };
+ auto unmount_thread = [&mountpoint]() {
+ for (int i = 0; i < 100; ++i) {
+ umount(mountpoint.path().c_str());
+ }
+ };
+ std::list<ScopedThread> threads;
+ for (int i = 0; i < 10; ++i) {
+ threads.emplace_back(mount_thread);
+ }
+ for (int i = 0; i < 10; ++i) {
+ threads.emplace_back(unmount_thread);
+ }
+ for (auto& t : threads) {
+ t.Join();
+ }
+
+ // We don't know how many mount refs are remaining, since the count depends on
+ // the ordering of mount and umount calls. Keep calling unmount until it
+ // returns an error.
+ while (umount(mountpoint.path().c_str()) == 0) {
+ }
+}
+
TEST(Cgroup, UnmountRepeated) {
SKIP_IF(!CgroupsAvailable());
diff --git a/test/syscalls/linux/concurrency.cc b/test/syscalls/linux/concurrency.cc
index 7cd6a75bd..f2daf49ee 100644
--- a/test/syscalls/linux/concurrency.cc
+++ b/test/syscalls/linux/concurrency.cc
@@ -20,6 +20,7 @@
#include "absl/strings/string_view.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
+#include "benchmark/benchmark.h"
#include "test/util/platform_util.h"
#include "test/util/test_util.h"
#include "test/util/thread_util.h"
@@ -106,6 +107,8 @@ TEST(ConcurrencyTest, MultiProcessConcurrency) {
pid_t child_pid = fork();
if (child_pid == 0) {
while (true) {
+ int x = 0;
+ benchmark::DoNotOptimize(x); // Don't optimize this loop away.
}
}
ASSERT_THAT(child_pid, SyscallSucceeds());
diff --git a/test/syscalls/linux/epoll.cc b/test/syscalls/linux/epoll.cc
index af3d27894..3ef8b0327 100644
--- a/test/syscalls/linux/epoll.cc
+++ b/test/syscalls/linux/epoll.cc
@@ -230,6 +230,8 @@ TEST(EpollTest, WaitThenUnblock) {
EXPECT_THAT(pthread_detach(thread), SyscallSucceeds());
}
+#ifndef ANDROID // Android does not support pthread_cancel
+
void sighandler(int s) {}
void* signaler(void* arg) {
@@ -272,6 +274,8 @@ TEST(EpollTest, UnblockWithSignal) {
EXPECT_THAT(pthread_detach(thread), SyscallSucceeds());
}
+#endif // ANDROID
+
TEST(EpollTest, TimeoutNoFds) {
auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
struct epoll_event result[kFDsPerEpoll];
diff --git a/test/syscalls/linux/exec.cc b/test/syscalls/linux/exec.cc
index c5acfc794..a0016146a 100644
--- a/test/syscalls/linux/exec.cc
+++ b/test/syscalls/linux/exec.cc
@@ -283,7 +283,7 @@ TEST(ExecTest, InterpreterScriptTrailingWhitespace) {
TempPath::CreateSymlinkTo("/tmp", RunfilePath(kBasicWorkload)));
TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path(), " "), 0755));
+ GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path(), " \n"), 0755));
CheckExec(script.path(), {script.path()}, {}, ArgEnvExitStatus(1, 0),
absl::StrCat(link.path(), "\n", script.path(), "\n"));
@@ -304,7 +304,7 @@ TEST(ExecTest, InterpreterScriptArgWhitespace) {
TEST(ExecTest, InterpreterScriptNoPath) {
TempPath script = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), "#!", 0755));
+ TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), "#!\n\n", 0755));
int execve_errno;
ASSERT_NO_ERRNO_AND_VALUE(
diff --git a/test/syscalls/linux/exec_state_workload.cc b/test/syscalls/linux/exec_state_workload.cc
index 028902b14..eafdc2bfa 100644
--- a/test/syscalls/linux/exec_state_workload.cc
+++ b/test/syscalls/linux/exec_state_workload.cc
@@ -26,6 +26,8 @@
#include "absl/strings/numbers.h"
+#ifndef ANDROID // Conflicts with existing operator<< on Android.
+
// Pretty-print a sigset_t.
std::ostream& operator<<(std::ostream& out, const sigset_t& s) {
out << "{ ";
@@ -40,6 +42,8 @@ std::ostream& operator<<(std::ostream& out, const sigset_t& s) {
return out;
}
+#endif
+
// Verify that the signo handler is handler.
int CheckSigHandler(uint32_t signo, uintptr_t handler) {
struct sigaction sa;
diff --git a/test/syscalls/linux/ip_socket_test_util.cc b/test/syscalls/linux/ip_socket_test_util.cc
index 98d07ae85..e90a7e411 100644
--- a/test/syscalls/linux/ip_socket_test_util.cc
+++ b/test/syscalls/linux/ip_socket_test_util.cc
@@ -140,6 +140,22 @@ SocketPairKind IPv4UDPUnboundSocketPair(int type) {
/* dual_stack = */ false)};
}
+SocketKind ICMPUnboundSocket(int type) {
+ std::string description =
+ absl::StrCat(DescribeSocketType(type), "ICMP socket");
+ return SocketKind{
+ description, AF_INET, type | SOCK_DGRAM, IPPROTO_ICMP,
+ UnboundSocketCreator(AF_INET, type | SOCK_DGRAM, IPPROTO_ICMP)};
+}
+
+SocketKind ICMPv6UnboundSocket(int type) {
+ std::string description =
+ absl::StrCat(DescribeSocketType(type), "ICMPv6 socket");
+ return SocketKind{
+ description, AF_INET6, type | SOCK_DGRAM, IPPROTO_ICMPV6,
+ UnboundSocketCreator(AF_INET6, type | SOCK_DGRAM, IPPROTO_ICMPV6)};
+}
+
SocketKind IPv4UDPUnboundSocket(int type) {
std::string description =
absl::StrCat(DescribeSocketType(type), "IPv4 UDP socket");
@@ -174,13 +190,21 @@ SocketKind IPv6TCPUnboundSocket(int type) {
PosixError IfAddrHelper::Load() {
Release();
+#ifndef ANDROID
RETURN_ERROR_IF_SYSCALL_FAIL(getifaddrs(&ifaddr_));
+#else
+ // Android does not support getifaddrs in r22.
+ return PosixError(ENOSYS, "getifaddrs");
+#endif
return NoError();
}
void IfAddrHelper::Release() {
if (ifaddr_) {
+#ifndef ANDROID
+ // Android does not support freeifaddrs in r22.
freeifaddrs(ifaddr_);
+#endif
ifaddr_ = nullptr;
}
}
diff --git a/test/syscalls/linux/ip_socket_test_util.h b/test/syscalls/linux/ip_socket_test_util.h
index 9c3859fcd..bde481f7e 100644
--- a/test/syscalls/linux/ip_socket_test_util.h
+++ b/test/syscalls/linux/ip_socket_test_util.h
@@ -84,20 +84,28 @@ SocketPairKind DualStackUDPBidirectionalBindSocketPair(int type);
// SocketPairs created with AF_INET and the given type.
SocketPairKind IPv4UDPUnboundSocketPair(int type);
+// ICMPUnboundSocket returns a SocketKind that represents a SimpleSocket created
+// with AF_INET, SOCK_DGRAM, IPPROTO_ICMP, and the given type.
+SocketKind ICMPUnboundSocket(int type);
+
+// ICMPv6UnboundSocket returns a SocketKind that represents a SimpleSocket
+// created with AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6, and the given type.
+SocketKind ICMPv6UnboundSocket(int type);
+
// IPv4UDPUnboundSocket returns a SocketKind that represents a SimpleSocket
-// created with AF_INET, SOCK_DGRAM, and the given type.
+// created with AF_INET, SOCK_DGRAM, IPPROTO_UDP, and the given type.
SocketKind IPv4UDPUnboundSocket(int type);
// IPv6UDPUnboundSocket returns a SocketKind that represents a SimpleSocket
-// created with AF_INET6, SOCK_DGRAM, and the given type.
+// created with AF_INET6, SOCK_DGRAM, IPPROTO_UDP, and the given type.
SocketKind IPv6UDPUnboundSocket(int type);
// IPv4TCPUnboundSocket returns a SocketKind that represents a SimpleSocket
-// created with AF_INET, SOCK_STREAM and the given type.
+// created with AF_INET, SOCK_STREAM, IPPROTO_TCP and the given type.
SocketKind IPv4TCPUnboundSocket(int type);
// IPv6TCPUnboundSocket returns a SocketKind that represents a SimpleSocket
-// created with AF_INET6, SOCK_STREAM and the given type.
+// created with AF_INET6, SOCK_STREAM, IPPROTO_TCP and the given type.
SocketKind IPv6TCPUnboundSocket(int type);
// IfAddrHelper is a helper class that determines the local interfaces present
diff --git a/test/syscalls/linux/lseek.cc b/test/syscalls/linux/lseek.cc
index 6ce1e6cc3..d4f89527c 100644
--- a/test/syscalls/linux/lseek.cc
+++ b/test/syscalls/linux/lseek.cc
@@ -150,7 +150,7 @@ TEST(LseekTest, SeekCurrentDir) {
// From include/linux/fs.h.
constexpr loff_t MAX_LFS_FILESIZE = 0x7fffffffffffffff;
- char* dir = get_current_dir_name();
+ char* dir = getcwd(NULL, 0);
const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(dir, O_RDONLY));
ASSERT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceeds());
diff --git a/test/syscalls/linux/packet_socket.cc b/test/syscalls/linux/packet_socket.cc
index 861617ff7..1e246c421 100644
--- a/test/syscalls/linux/packet_socket.cc
+++ b/test/syscalls/linux/packet_socket.cc
@@ -55,8 +55,6 @@
//
// These tests require CAP_NET_RAW to run.
-// TODO(gvisor.dev/issue/173): gVisor support.
-
namespace gvisor {
namespace testing {
@@ -188,7 +186,6 @@ void ReceiveMessage(int sock, int ifindex) {
// sizeof(sockaddr_ll).
ASSERT_THAT(src_len, AnyOf(Eq(sizeof(src)), Eq(sizeof(src) - 2)));
- // TODO(gvisor.dev/issue/173): Verify protocol once we return it.
// Verify the source address.
EXPECT_EQ(src.sll_family, AF_PACKET);
EXPECT_EQ(src.sll_ifindex, ifindex);
@@ -234,9 +231,6 @@ TEST_P(CookedPacketTest, Receive) {
// Send via a packet socket.
TEST_P(CookedPacketTest, Send) {
- // TODO(gvisor.dev/issue/173): Remove once we support packet socket writing.
- SKIP_IF(IsRunningOnGvisor());
-
// Let's send a UDP packet and receive it using a regular UDP socket.
FileDescriptor udp_sock =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
@@ -290,6 +284,14 @@ TEST_P(CookedPacketTest, Send) {
memcpy(send_buf + sizeof(iphdr), &udphdr, sizeof(udphdr));
memcpy(send_buf + sizeof(iphdr) + sizeof(udphdr), kMessage, sizeof(kMessage));
+ // We don't implement writing to packet sockets on gVisor.
+ if (IsRunningOnGvisor()) {
+ ASSERT_THAT(sendto(socket_, send_buf, sizeof(send_buf), 0,
+ reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)),
+ SyscallFailsWithErrno(EINVAL));
+ GTEST_SKIP();
+ }
+
// Send it.
ASSERT_THAT(sendto(socket_, send_buf, sizeof(send_buf), 0,
reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)),
diff --git a/test/syscalls/linux/packet_socket_raw.cc b/test/syscalls/linux/packet_socket_raw.cc
index 72080a272..7e439466e 100644
--- a/test/syscalls/linux/packet_socket_raw.cc
+++ b/test/syscalls/linux/packet_socket_raw.cc
@@ -56,8 +56,6 @@
//
// These tests require CAP_NET_RAW to run.
-// TODO(gvisor.dev/issue/173): gVisor support.
-
namespace gvisor {
namespace testing {
@@ -193,7 +191,6 @@ TEST_P(RawPacketTest, Receive) {
// sizeof(sockaddr_ll).
ASSERT_THAT(src_len, AnyOf(Eq(sizeof(src)), Eq(sizeof(src) - 2)));
- // TODO(gvisor.dev/issue/173): Verify protocol once we return it.
// Verify the source address.
EXPECT_EQ(src.sll_family, AF_PACKET);
EXPECT_EQ(src.sll_ifindex, GetLoopbackIndex());
@@ -238,9 +235,6 @@ TEST_P(RawPacketTest, Receive) {
// Send via a packet socket.
TEST_P(RawPacketTest, Send) {
- // TODO(gvisor.dev/issue/173): Remove once we support packet socket writing.
- SKIP_IF(IsRunningOnGvisor());
-
// Let's send a UDP packet and receive it using a regular UDP socket.
FileDescriptor udp_sock =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
@@ -302,6 +296,14 @@ TEST_P(RawPacketTest, Send) {
memcpy(send_buf + sizeof(ethhdr) + sizeof(iphdr) + sizeof(udphdr), kMessage,
sizeof(kMessage));
+ // We don't implement writing to packet sockets on gVisor.
+ if (IsRunningOnGvisor()) {
+ ASSERT_THAT(sendto(s_, send_buf, sizeof(send_buf), 0,
+ reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)),
+ SyscallFailsWithErrno(EINVAL));
+ GTEST_SKIP();
+ }
+
// Send it.
ASSERT_THAT(sendto(s_, send_buf, sizeof(send_buf), 0,
reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)),
diff --git a/test/syscalls/linux/ping_socket.cc b/test/syscalls/linux/ping_socket.cc
index 8b78e4b16..8268e91da 100644
--- a/test/syscalls/linux/ping_socket.cc
+++ b/test/syscalls/linux/ping_socket.cc
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include <errno.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
@@ -19,14 +20,22 @@
#include <sys/types.h>
#include <unistd.h>
+#include <cctype>
+#include <cstring>
#include <vector>
#include "gtest/gtest.h"
+#include "absl/algorithm/container.h"
+#include "absl/strings/str_join.h"
+#include "absl/types/optional.h"
+#include "test/syscalls/linux/ip_socket_test_util.h"
#include "test/syscalls/linux/socket_test_util.h"
#include "test/util/file_descriptor.h"
-#include "test/util/save_util.h"
#include "test/util/test_util.h"
+// Note: These tests require /proc/sys/net/ipv4/ping_group_range to be
+// configured to allow the tester to create ping sockets (see icmp(7)).
+
namespace gvisor {
namespace testing {
namespace {
@@ -42,7 +51,8 @@ TEST(PingSocket, ICMPPortExhaustion) {
auto s = Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
if (!s.ok()) {
ASSERT_EQ(s.error().errno_value(), EACCES);
- GTEST_SKIP();
+ GTEST_SKIP() << "TODO(gvisor.dev/issue/6126): Buildkite does not allow "
+ "creation of ICMP or ICMPv6 sockets";
}
}
@@ -70,7 +80,212 @@ TEST(PingSocket, ICMPPortExhaustion) {
}
}
-} // namespace
+struct BindTestCase {
+ TestAddress bind_to;
+ int want = 0;
+ absl::optional<int> want_gvisor;
+};
+
+// Test fixture for socket binding.
+class Fixture
+ : public ::testing::TestWithParam<std::tuple<SocketKind, BindTestCase>> {};
+
+TEST_P(Fixture, Bind) {
+ auto [socket_factory, test_case] = GetParam();
+ auto socket = socket_factory.Create();
+ if (!socket.ok()) {
+ ASSERT_EQ(socket.error().errno_value(), EACCES);
+ GTEST_SKIP() << "TODO(gvisor.dev/issue/6126): Buildkite does not allow "
+ "creation of ICMP or ICMPv6 sockets";
+ }
+ auto socket_fd = std::move(socket).ValueOrDie();
+
+ const int want = test_case.want_gvisor.has_value() && IsRunningOnGvisor()
+ ? *test_case.want_gvisor
+ : test_case.want;
+ if (want == 0) {
+ EXPECT_THAT(bind(socket_fd->get(), AsSockAddr(&test_case.bind_to.addr),
+ test_case.bind_to.addr_len),
+ SyscallSucceeds());
+ } else {
+ EXPECT_THAT(bind(socket_fd->get(), AsSockAddr(&test_case.bind_to.addr),
+ test_case.bind_to.addr_len),
+ SyscallFailsWithErrno(want));
+ }
+}
+
+std::vector<std::tuple<SocketKind, BindTestCase>> ICMPTestCases() {
+ return ApplyVec<std::tuple<SocketKind, BindTestCase>>(
+ [](const BindTestCase& test_case) {
+ return std::make_tuple(ICMPUnboundSocket(0), test_case);
+ },
+ std::vector<BindTestCase>{
+ {
+ .bind_to = V4Any(),
+ .want = 0,
+ .want_gvisor = 0,
+ },
+ {
+ .bind_to = V4Broadcast(),
+ .want = EADDRNOTAVAIL,
+ // TODO(gvisor.dev/issue/5711): Remove want_gvisor once ICMP
+ // sockets are no longer allowed to bind to broadcast addresses.
+ .want_gvisor = 0,
+ },
+ {
+ .bind_to = V4Loopback(),
+ .want = 0,
+ },
+ {
+ .bind_to = V4LoopbackSubnetBroadcast(),
+ .want = EADDRNOTAVAIL,
+ // TODO(gvisor.dev/issue/5711): Remove want_gvisor once ICMP
+ // sockets are no longer allowed to bind to broadcast addresses.
+ .want_gvisor = 0,
+ },
+ {
+ .bind_to = V4Multicast(),
+ .want = EADDRNOTAVAIL,
+ },
+ {
+ .bind_to = V4MulticastAllHosts(),
+ .want = EADDRNOTAVAIL,
+ },
+ {
+ .bind_to = V4AddrStr("IPv4UnknownUnicast", "192.168.1.1"),
+ .want = EADDRNOTAVAIL,
+ },
+ // TODO(gvisor.dev/issue/6021): Remove want_gvisor from all the test
+ // cases below once ICMP sockets return EAFNOSUPPORT when binding to
+ // IPv6 addresses.
+ {
+ .bind_to = V6Any(),
+ .want = EAFNOSUPPORT,
+ .want_gvisor = EINVAL,
+ },
+ {
+ .bind_to = V6Loopback(),
+ .want = EAFNOSUPPORT,
+ .want_gvisor = EINVAL,
+ },
+ {
+ .bind_to = V6Multicast(),
+ .want = EAFNOSUPPORT,
+ .want_gvisor = EINVAL,
+ },
+ {
+ .bind_to = V6MulticastInterfaceLocalAllNodes(),
+ .want = EAFNOSUPPORT,
+ .want_gvisor = EINVAL,
+ },
+ {
+ .bind_to = V6MulticastLinkLocalAllNodes(),
+ .want = EAFNOSUPPORT,
+ .want_gvisor = EINVAL,
+ },
+ {
+ .bind_to = V6MulticastLinkLocalAllRouters(),
+ .want = EAFNOSUPPORT,
+ .want_gvisor = EINVAL,
+ },
+ {
+ .bind_to = V6AddrStr("IPv6UnknownUnicast", "fc00::1"),
+ .want = EAFNOSUPPORT,
+ .want_gvisor = EINVAL,
+ },
+ });
+}
+std::vector<std::tuple<SocketKind, BindTestCase>> ICMPv6TestCases() {
+ return ApplyVec<std::tuple<SocketKind, BindTestCase>>(
+ [](const BindTestCase& test_case) {
+ return std::make_tuple(ICMPv6UnboundSocket(0), test_case);
+ },
+ std::vector<BindTestCase>{
+ {
+ .bind_to = V4Any(),
+ .want = EINVAL,
+ },
+ {
+ .bind_to = V4Broadcast(),
+ .want = EINVAL,
+ },
+ {
+ .bind_to = V4Loopback(),
+ .want = EINVAL,
+ },
+ {
+ .bind_to = V4LoopbackSubnetBroadcast(),
+ .want = EINVAL,
+ },
+ {
+ .bind_to = V4Multicast(),
+ .want = EINVAL,
+ },
+ {
+ .bind_to = V4MulticastAllHosts(),
+ .want = EINVAL,
+ },
+ {
+ .bind_to = V4AddrStr("IPv4UnknownUnicast", "192.168.1.1"),
+ .want = EINVAL,
+ },
+ {
+ .bind_to = V6Any(),
+ .want = 0,
+ },
+ {
+ .bind_to = V6Loopback(),
+ .want = 0,
+ },
+ // TODO(gvisor.dev/issue/6021): Remove want_gvisor from all the
+ // multicast test cases below once ICMPv6 sockets return EINVAL when
+ // binding to IPv6 multicast addresses.
+ {
+ .bind_to = V6Multicast(),
+ .want = EINVAL,
+ .want_gvisor = EADDRNOTAVAIL,
+ },
+ {
+ .bind_to = V6MulticastInterfaceLocalAllNodes(),
+ .want = EINVAL,
+ .want_gvisor = EADDRNOTAVAIL,
+ },
+ {
+ .bind_to = V6MulticastLinkLocalAllNodes(),
+ .want = EINVAL,
+ .want_gvisor = EADDRNOTAVAIL,
+ },
+ {
+ .bind_to = V6MulticastLinkLocalAllRouters(),
+ .want = EINVAL,
+ .want_gvisor = EADDRNOTAVAIL,
+ },
+ {
+ .bind_to = V6AddrStr("IPv6UnknownUnicast", "fc00::1"),
+ .want = EADDRNOTAVAIL,
+ },
+ });
+}
+
+std::vector<std::tuple<SocketKind, BindTestCase>> AllTestCases() {
+ return VecCat<std::tuple<SocketKind, BindTestCase>>(ICMPTestCases(),
+ ICMPv6TestCases());
+}
+
+std::string TestDescription(
+ const ::testing::TestParamInfo<Fixture::ParamType>& info) {
+ auto [socket_factory, test_case] = info.param;
+ std::string name = absl::StrJoin(
+ {socket_factory.description, test_case.bind_to.description}, "_");
+ absl::c_replace_if(
+ name, [](char c) { return !std::isalnum(c); }, '_');
+ return name;
+}
+
+INSTANTIATE_TEST_SUITE_P(PingSockets, Fixture,
+ ::testing::ValuesIn(AllTestCases()), TestDescription);
+
+} // namespace
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/pipe.cc b/test/syscalls/linux/pipe.cc
index 96c454485..0bba86846 100644
--- a/test/syscalls/linux/pipe.cc
+++ b/test/syscalls/linux/pipe.cc
@@ -14,6 +14,7 @@
#include <fcntl.h> /* Obtain O_* constant definitions */
#include <linux/magic.h>
+#include <signal.h>
#include <sys/ioctl.h>
#include <sys/statfs.h>
#include <sys/uio.h>
@@ -29,6 +30,7 @@
#include "test/util/file_descriptor.h"
#include "test/util/fs_util.h"
#include "test/util/posix_error.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"
@@ -44,6 +46,29 @@ constexpr int kTestValue = 0x12345678;
// Used for synchronization in race tests.
const absl::Duration syncDelay = absl::Seconds(2);
+std::atomic<int> global_num_signals_received = 0;
+void SigRecordingHandler(int signum, siginfo_t* siginfo,
+ void* unused_ucontext) {
+ global_num_signals_received++;
+}
+
+PosixErrorOr<Cleanup> RegisterSignalHandler(int signum) {
+ global_num_signals_received = 0;
+ struct sigaction handler;
+ handler.sa_sigaction = SigRecordingHandler;
+ sigemptyset(&handler.sa_mask);
+ handler.sa_flags = SA_SIGINFO;
+ return ScopedSigaction(signum, handler);
+}
+
+void WaitForSignalDelivery(absl::Duration timeout, int max_expected) {
+ absl::Time wait_start = absl::Now();
+ while (global_num_signals_received < max_expected &&
+ absl::Now() - wait_start < timeout) {
+ absl::SleepFor(absl::Milliseconds(10));
+ }
+}
+
struct PipeCreator {
std::string name_;
@@ -267,6 +292,9 @@ TEST_P(PipeTest, Seek) {
}
}
+#ifndef ANDROID
+// Android does not support preadv or pwritev in r22.
+
TEST_P(PipeTest, OffsetCalls) {
SKIP_IF(!CreateBlocking());
@@ -283,6 +311,8 @@ TEST_P(PipeTest, OffsetCalls) {
EXPECT_THAT(pwritev(rfd_.get(), &iov, 1, 0), SyscallFailsWithErrno(ESPIPE));
}
+#endif // ANDROID
+
TEST_P(PipeTest, WriterSideCloses) {
SKIP_IF(!CreateBlocking());
@@ -333,10 +363,16 @@ TEST_P(PipeTest, WriterSideClosesReadDataFirst) {
TEST_P(PipeTest, ReaderSideCloses) {
SKIP_IF(!CreateBlocking());
+ const auto signal_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGPIPE));
+
ASSERT_THAT(close(rfd_.release()), SyscallSucceeds());
int buf = kTestValue;
EXPECT_THAT(write(wfd_.get(), &buf, sizeof(buf)),
SyscallFailsWithErrno(EPIPE));
+
+ WaitForSignalDelivery(absl::Seconds(1), 1);
+ ASSERT_EQ(global_num_signals_received, 1);
}
TEST_P(PipeTest, CloseTwice) {
@@ -355,6 +391,9 @@ TEST_P(PipeTest, CloseTwice) {
TEST_P(PipeTest, BlockWriteClosed) {
SKIP_IF(!CreateBlocking());
+ const auto signal_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGPIPE));
+
absl::Notification notify;
ScopedThread t([this, &notify]() {
std::vector<char> buf(Size());
@@ -371,6 +410,10 @@ TEST_P(PipeTest, BlockWriteClosed) {
notify.WaitForNotification();
ASSERT_THAT(close(rfd_.release()), SyscallSucceeds());
+
+ WaitForSignalDelivery(absl::Seconds(1), 1);
+ ASSERT_EQ(global_num_signals_received, 1);
+
t.Join();
}
@@ -379,6 +422,9 @@ TEST_P(PipeTest, BlockWriteClosed) {
TEST_P(PipeTest, BlockPartialWriteClosed) {
SKIP_IF(!CreateBlocking());
+ const auto signal_cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(RegisterSignalHandler(SIGPIPE));
+
ScopedThread t([this]() {
const int pipe_size = Size();
std::vector<char> buf(2 * pipe_size);
@@ -396,6 +442,10 @@ TEST_P(PipeTest, BlockPartialWriteClosed) {
// Unblock the above.
ASSERT_THAT(close(rfd_.release()), SyscallSucceeds());
+
+ WaitForSignalDelivery(absl::Seconds(1), 2);
+ ASSERT_EQ(global_num_signals_received, 2);
+
t.Join();
}
diff --git a/test/syscalls/linux/poll.cc b/test/syscalls/linux/poll.cc
index 5ce7e8c8d..ccd084244 100644
--- a/test/syscalls/linux/poll.cc
+++ b/test/syscalls/linux/poll.cc
@@ -116,7 +116,7 @@ void BlockingReadableTest(int16_t mask) {
});
notify.WaitForNotification();
- absl::SleepFor(absl::Seconds(1.0));
+ absl::SleepFor(absl::Seconds(1));
// Write some data to the pipe.
char s[] = "foo\n";
@@ -221,7 +221,7 @@ TEST_F(PollTest, BlockingEventPOLLHUP) {
});
notify.WaitForNotification();
- absl::SleepFor(absl::Seconds(1.0));
+ absl::SleepFor(absl::Seconds(1));
// Write some data and close the writer fd.
fd1.reset();
diff --git a/test/syscalls/linux/prctl.cc b/test/syscalls/linux/prctl.cc
index 19a57d353..25b0e63d4 100644
--- a/test/syscalls/linux/prctl.cc
+++ b/test/syscalls/linux/prctl.cc
@@ -101,11 +101,11 @@ TEST(PrctlTest, NoNewPrivsPreservedAcrossCloneForkAndExecve) {
int no_new_privs;
ASSERT_THAT(no_new_privs = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0),
SyscallSucceeds());
- ScopedThread([] {
+ ScopedThread thread = ScopedThread([] {
ASSERT_THAT(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0), SyscallSucceeds());
EXPECT_THAT(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0),
SyscallSucceedsWithValue(1));
- ScopedThread([] {
+ ScopedThread threadInner = ScopedThread([] {
EXPECT_THAT(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0),
SyscallSucceedsWithValue(1));
// Note that these ASSERT_*s failing will only return from this thread,
@@ -129,9 +129,11 @@ TEST(PrctlTest, NoNewPrivsPreservedAcrossCloneForkAndExecve) {
EXPECT_THAT(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0),
SyscallSucceedsWithValue(1));
});
+ threadInner.Join();
EXPECT_THAT(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0),
SyscallSucceedsWithValue(1));
});
+ thread.Join();
EXPECT_THAT(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0),
SyscallSucceedsWithValue(no_new_privs));
}
@@ -141,7 +143,7 @@ TEST(PrctlTest, PDeathSig) {
// Make the new process' parent a separate thread since the parent death
// signal fires when the parent *thread* exits.
- ScopedThread([&] {
+ ScopedThread thread = ScopedThread([&] {
child_pid = fork();
TEST_CHECK(child_pid >= 0);
if (child_pid == 0) {
@@ -172,6 +174,7 @@ TEST(PrctlTest, PDeathSig) {
// Suppress the SIGSTOP and detach from the child.
ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, 0), SyscallSucceeds());
});
+ thread.Join();
// The child should have been killed by its parent death SIGKILL.
int status;
diff --git a/test/syscalls/linux/priority.cc b/test/syscalls/linux/priority.cc
index 1d9bdfa70..e381248e4 100644
--- a/test/syscalls/linux/priority.cc
+++ b/test/syscalls/linux/priority.cc
@@ -72,7 +72,8 @@ TEST(SetpriorityTest, Implemented) {
// No need to clear errno for setpriority():
// "The setpriority() call returns 0 if there is no error, or -1 if there is"
- EXPECT_THAT(setpriority(PRIO_PROCESS, /*who=*/0, /*nice=*/16),
+ EXPECT_THAT(setpriority(PRIO_PROCESS, /*who=*/0,
+ /*nice=*/16), // NOLINT(bugprone-argument-comment)
SyscallSucceeds());
}
@@ -80,7 +81,8 @@ TEST(SetpriorityTest, Implemented) {
TEST(Setpriority, InvalidWhich) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
- EXPECT_THAT(setpriority(/*which=*/3, /*who=*/0, /*nice=*/16),
+ EXPECT_THAT(setpriority(/*which=*/3, /*who=*/0,
+ /*nice=*/16), // NOLINT(bugprone-argument-comment)
SyscallFailsWithErrno(EINVAL));
}
@@ -88,7 +90,8 @@ TEST(Setpriority, InvalidWhich) {
TEST(SetpriorityTest, ValidWho) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
- EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(), /*nice=*/16),
+ EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(),
+ /*nice=*/16), // NOLINT(bugprone-argument-comment)
SyscallSucceeds());
}
@@ -142,22 +145,26 @@ TEST(SetpriorityTest, OutsideRange) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_NICE)));
// Set niceval > 19
- EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(), /*nice=*/100),
+ EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(),
+ /*nice=*/100), // NOLINT(bugprone-argument-comment)
SyscallSucceeds());
errno = 0;
// Test niceval truncated to 19
EXPECT_THAT(getpriority(PRIO_PROCESS, getpid()),
- SyscallSucceedsWithValue(/*maxnice=*/19));
+ SyscallSucceedsWithValue(
+ /*maxnice=*/19)); // NOLINT(bugprone-argument-comment)
// Set niceval < -20
- EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(), /*nice=*/-100),
+ EXPECT_THAT(setpriority(PRIO_PROCESS, getpid(),
+ /*nice=*/-100), // NOLINT(bugprone-argument-comment)
SyscallSucceeds());
errno = 0;
// Test niceval truncated to -20
EXPECT_THAT(getpriority(PRIO_PROCESS, getpid()),
- SyscallSucceedsWithValue(/*minnice=*/-20));
+ SyscallSucceedsWithValue(
+ /*minnice=*/-20)); // NOLINT(bugprone-argument-comment)
}
// Process is not found when which=PRIO_PROCESS
@@ -167,7 +174,7 @@ TEST(SetpriorityTest, InvalidWho) {
// Flaky, but it's tough to avoid a race condition when finding an unused pid
EXPECT_THAT(setpriority(PRIO_PROCESS,
/*who=*/INT_MAX - 1,
- /*nice=*/16),
+ /*nice=*/16), // NOLINT(bugprone-argument-comment)
SyscallFailsWithErrno(ESRCH));
}
diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc
index 24928d876..78aa73edc 100644
--- a/test/syscalls/linux/proc.cc
+++ b/test/syscalls/linux/proc.cc
@@ -2364,6 +2364,15 @@ TEST(ProcSysKernelHostname, MatchesUname) {
EXPECT_EQ(procfs_hostname, hostname);
}
+TEST(ProcSysVmMaxmapCount, HasNumericValue) {
+ const std::string val_str =
+ ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/vm/max_map_count"));
+ int32_t val;
+ EXPECT_TRUE(absl::SimpleAtoi(val_str, &val))
+ << "/proc/sys/vm/max_map_count does not contain a numeric value: "
+ << val_str;
+}
+
TEST(ProcSysVmMmapMinAddr, HasNumericValue) {
const std::string mmap_min_addr_str =
ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/vm/mmap_min_addr"));
diff --git a/test/syscalls/linux/ptrace.cc b/test/syscalls/linux/ptrace.cc
index d519b65e6..f64c23ac0 100644
--- a/test/syscalls/linux/ptrace.cc
+++ b/test/syscalls/linux/ptrace.cc
@@ -30,6 +30,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/flags/flag.h"
+#include "absl/strings/string_view.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "test/util/capability_util.h"
@@ -51,17 +52,10 @@ ABSL_FLAG(bool, ptrace_test_execve_child, false,
ABSL_FLAG(bool, ptrace_test_trace_descendants_allowed, false,
"If set, run the child workload for "
"PtraceTest_TraceDescendantsAllowed.");
-ABSL_FLAG(bool, ptrace_test_prctl_set_ptracer_pid, false,
- "If set, run the child workload for PtraceTest_PrctlSetPtracerPID.");
-ABSL_FLAG(bool, ptrace_test_prctl_set_ptracer_any, false,
- "If set, run the child workload for PtraceTest_PrctlSetPtracerAny.");
-ABSL_FLAG(bool, ptrace_test_prctl_clear_ptracer, false,
- "If set, run the child workload for PtraceTest_PrctlClearPtracer.");
-ABSL_FLAG(bool, ptrace_test_prctl_replace_ptracer, false,
- "If set, run the child workload for PtraceTest_PrctlReplacePtracer.");
-ABSL_FLAG(int, ptrace_test_prctl_replace_ptracer_tid, -1,
- "Specifies the replacement tracer tid in the child workload for "
- "PtraceTest_PrctlReplacePtracer.");
+ABSL_FLAG(bool, ptrace_test_ptrace_attacher, false,
+ "If set, run the child workload for PtraceAttacherSubprocess.");
+ABSL_FLAG(bool, ptrace_test_prctl_set_ptracer, false,
+ "If set, run the child workload for PrctlSetPtracerSubprocess.");
ABSL_FLAG(bool, ptrace_test_prctl_set_ptracer_and_exit_tracee_thread, false,
"If set, run the child workload for "
"PtraceTest_PrctlSetPtracerPersistsPastTraceeThreadExit.");
@@ -161,6 +155,86 @@ int CheckPtraceAttach(pid_t pid) {
return 0;
}
+class SimpleSubprocess {
+ public:
+ explicit SimpleSubprocess(absl::string_view child_flag) {
+ int sockets[2];
+ TEST_PCHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0);
+
+ // Allocate vector before forking (not async-signal-safe).
+ ExecveArray const owned_child_argv = {"/proc/self/exe", child_flag,
+ "--ptrace_test_fd",
+ std::to_string(sockets[0])};
+ char* const* const child_argv = owned_child_argv.get();
+
+ pid_ = fork();
+ if (pid_ == 0) {
+ TEST_PCHECK(close(sockets[1]) == 0);
+ execve(child_argv[0], child_argv, /* envp = */ nullptr);
+ TEST_PCHECK_MSG(false, "Survived execve to test child");
+ }
+ TEST_PCHECK(pid_ > 0);
+ TEST_PCHECK(close(sockets[0]) == 0);
+ sockfd_ = sockets[1];
+ }
+
+ SimpleSubprocess(SimpleSubprocess&& orig)
+ : pid_(orig.pid_), sockfd_(orig.sockfd_) {
+ orig.pid_ = -1;
+ orig.sockfd_ = -1;
+ }
+
+ SimpleSubprocess& operator=(SimpleSubprocess&& orig) {
+ if (this != &orig) {
+ this->~SimpleSubprocess();
+ pid_ = orig.pid_;
+ sockfd_ = orig.sockfd_;
+ orig.pid_ = -1;
+ orig.sockfd_ = -1;
+ }
+ return *this;
+ }
+
+ SimpleSubprocess(SimpleSubprocess const&) = delete;
+ SimpleSubprocess& operator=(SimpleSubprocess const&) = delete;
+
+ ~SimpleSubprocess() {
+ if (pid_ < 0) {
+ return;
+ }
+ EXPECT_THAT(shutdown(sockfd_, SHUT_RDWR), SyscallSucceeds());
+ EXPECT_THAT(close(sockfd_), SyscallSucceeds());
+ int status;
+ EXPECT_THAT(waitpid(pid_, &status, 0), SyscallSucceedsWithValue(pid_));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
+ << " status " << status;
+ }
+
+ pid_t pid() const { return pid_; }
+
+ // Sends the child process the given value, receives an errno in response, and
+ // returns a PosixError corresponding to the received errno.
+ template <typename T>
+ PosixError Cmd(T val) {
+ if (WriteFd(sockfd_, &val, sizeof(val)) < 0) {
+ return PosixError(errno, "write failed");
+ }
+ return RecvErrno();
+ }
+
+ private:
+ PosixError RecvErrno() {
+ int resp_errno;
+ if (ReadFd(sockfd_, &resp_errno, sizeof(resp_errno)) < 0) {
+ return PosixError(errno, "read failed");
+ }
+ return PosixError(resp_errno);
+ }
+
+ pid_t pid_ = -1;
+ int sockfd_ = -1;
+};
+
TEST(PtraceTest, AttachSelf) {
EXPECT_THAT(ptrace(PTRACE_ATTACH, gettid(), 0, 0),
SyscallFailsWithErrno(EPERM));
@@ -343,289 +417,128 @@ TEST(PtraceTest, PrctlSetPtracerInvalidPID) {
EXPECT_THAT(prctl(PR_SET_PTRACER, 123456789), SyscallFailsWithErrno(EINVAL));
}
-TEST(PtraceTest, PrctlSetPtracerPID) {
- SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1);
-
- AutoCapability cap(CAP_SYS_PTRACE, false);
-
- // Use sockets to synchronize between tracer and tracee.
- int sockets[2];
- ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds());
-
- // Allocate vector before forking (not async-signal-safe).
- ExecveArray const owned_child_argv = {
- "/proc/self/exe", "--ptrace_test_prctl_set_ptracer_pid",
- "--ptrace_test_fd", std::to_string(sockets[0])};
- char* const* const child_argv = owned_child_argv.get();
-
- pid_t const tracee_pid = fork();
- if (tracee_pid == 0) {
- TEST_PCHECK(close(sockets[1]) == 0);
- // This test will create a new thread in the child process.
- // pthread_create(2) isn't async-signal-safe, so we execve() first.
- execve(child_argv[0], child_argv, /* envp = */ nullptr);
- TEST_PCHECK_MSG(false, "Survived execve to test child");
- }
- ASSERT_THAT(tracee_pid, SyscallSucceeds());
- ASSERT_THAT(close(sockets[0]), SyscallSucceeds());
-
- pid_t const tracer_pid = fork();
- if (tracer_pid == 0) {
- // Wait until tracee has called prctl.
- char done;
- TEST_PCHECK(read(sockets[1], &done, 1) == 1);
- MaybeSave();
-
- TEST_PCHECK(CheckPtraceAttach(tracee_pid) == 0);
- _exit(0);
- }
- ASSERT_THAT(tracer_pid, SyscallSucceeds());
-
- // Clean up tracer.
- int status;
- ASSERT_THAT(waitpid(tracer_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
-
- // Clean up tracee.
- ASSERT_THAT(kill(tracee_pid, SIGKILL), SyscallSucceeds());
- ASSERT_THAT(waitpid(tracee_pid, &status, 0),
- SyscallSucceedsWithValue(tracee_pid));
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
- << " status " << status;
+SimpleSubprocess CreatePtraceAttacherSubprocess() {
+ return SimpleSubprocess("--ptrace_test_ptrace_attacher");
}
-[[noreturn]] void RunPrctlSetPtracerPID(int fd) {
- ScopedThread t([fd] {
- // Perform prctl in a separate thread to verify that it is process-wide.
- TEST_PCHECK(prctl(PR_SET_PTRACER, getppid()) == 0);
- MaybeSave();
- // Indicate that the prctl has been set.
- TEST_PCHECK(write(fd, "x", 1) == 1);
- MaybeSave();
+[[noreturn]] static void RunPtraceAttacher(int sockfd) {
+ // execve() may have restored CAP_SYS_PTRACE if we had real UID 0.
+ TEST_CHECK(SetCapability(CAP_SYS_PTRACE, false).ok());
+ // Perform PTRACE_ATTACH in a separate thread to verify that permissions
+ // apply process-wide.
+ ScopedThread t([&] {
+ while (true) {
+ pid_t pid;
+ int rv = read(sockfd, &pid, sizeof(pid));
+ if (rv == 0) {
+ _exit(0);
+ }
+ if (rv < 0) {
+ _exit(1);
+ }
+ int resp_errno = 0;
+ if (CheckPtraceAttach(pid) < 0) {
+ resp_errno = errno;
+ }
+ TEST_PCHECK(write(sockfd, &resp_errno, sizeof(resp_errno)) ==
+ sizeof(resp_errno));
+ }
});
while (true) {
SleepSafe(absl::Seconds(1));
}
}
-TEST(PtraceTest, PrctlSetPtracerAny) {
- SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1);
- AutoCapability cap(CAP_SYS_PTRACE, false);
-
- // Use sockets to synchronize between tracer and tracee.
- int sockets[2];
- ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds());
-
- // Allocate vector before forking (not async-signal-safe).
- ExecveArray const owned_child_argv = {
- "/proc/self/exe", "--ptrace_test_prctl_set_ptracer_any",
- "--ptrace_test_fd", std::to_string(sockets[0])};
- char* const* const child_argv = owned_child_argv.get();
-
- pid_t const tracee_pid = fork();
- if (tracee_pid == 0) {
- // This test will create a new thread in the child process.
- // pthread_create(2) isn't async-signal-safe, so we execve() first.
- TEST_PCHECK(close(sockets[1]) == 0);
- execve(child_argv[0], child_argv, /* envp = */ nullptr);
- TEST_PCHECK_MSG(false, "Survived execve to test child");
- }
- ASSERT_THAT(tracee_pid, SyscallSucceeds());
- ASSERT_THAT(close(sockets[0]), SyscallSucceeds());
-
- pid_t const tracer_pid = fork();
- if (tracer_pid == 0) {
- // Wait until tracee has called prctl.
- char done;
- TEST_PCHECK(read(sockets[1], &done, 1) == 1);
- MaybeSave();
-
- TEST_PCHECK(CheckPtraceAttach(tracee_pid) == 0);
- _exit(0);
- }
- ASSERT_THAT(tracer_pid, SyscallSucceeds());
-
- // Clean up tracer.
- int status;
- ASSERT_THAT(waitpid(tracer_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << " status " << status;
-
- // Clean up tracee.
- ASSERT_THAT(kill(tracee_pid, SIGKILL), SyscallSucceeds());
- ASSERT_THAT(waitpid(tracee_pid, &status, 0),
- SyscallSucceedsWithValue(tracee_pid));
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
- << " status " << status;
+SimpleSubprocess CreatePrctlSetPtracerSubprocess() {
+ return SimpleSubprocess("--ptrace_test_prctl_set_ptracer");
}
-[[noreturn]] void RunPrctlSetPtracerAny(int fd) {
- ScopedThread t([fd] {
- // Perform prctl in a separate thread to verify that it is process-wide.
- TEST_PCHECK(prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY) == 0);
- MaybeSave();
- // Indicate that the prctl has been set.
- TEST_PCHECK(write(fd, "x", 1) == 1);
- MaybeSave();
+[[noreturn]] static void RunPrctlSetPtracer(int sockfd) {
+ // Perform prctl in a separate thread to verify that it applies
+ // process-wide.
+ ScopedThread t([&] {
+ while (true) {
+ pid_t pid;
+ int rv = read(sockfd, &pid, sizeof(pid));
+ if (rv == 0) {
+ _exit(0);
+ }
+ if (rv < 0) {
+ _exit(1);
+ }
+ int resp_errno = 0;
+ if (prctl(PR_SET_PTRACER, pid) < 0) {
+ resp_errno = errno;
+ }
+ TEST_PCHECK(write(sockfd, &resp_errno, sizeof(resp_errno)) ==
+ sizeof(resp_errno));
+ }
});
while (true) {
SleepSafe(absl::Seconds(1));
}
}
-TEST(PtraceTest, PrctlClearPtracer) {
+TEST(PtraceTest, PrctlSetPtracer) {
SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1);
- AutoCapability cap(CAP_SYS_PTRACE, false);
-
- // Use sockets to synchronize between tracer and tracee.
- int sockets[2];
- ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds());
-
- // Allocate vector before forking (not async-signal-safe).
- ExecveArray const owned_child_argv = {
- "/proc/self/exe", "--ptrace_test_prctl_clear_ptracer", "--ptrace_test_fd",
- std::to_string(sockets[0])};
- char* const* const child_argv = owned_child_argv.get();
-
- pid_t const tracee_pid = fork();
- if (tracee_pid == 0) {
- // This test will create a new thread in the child process.
- // pthread_create(2) isn't async-signal-safe, so we execve() first.
- TEST_PCHECK(close(sockets[1]) == 0);
- execve(child_argv[0], child_argv, /* envp = */ nullptr);
- TEST_PCHECK_MSG(false, "Survived execve to test child");
- }
- ASSERT_THAT(tracee_pid, SyscallSucceeds());
- ASSERT_THAT(close(sockets[0]), SyscallSucceeds());
-
- pid_t const tracer_pid = fork();
- if (tracer_pid == 0) {
- // Wait until tracee has called prctl.
- char done;
- TEST_PCHECK(read(sockets[1], &done, 1) == 1);
- MaybeSave();
-
- TEST_CHECK(CheckPtraceAttach(tracee_pid) == -1);
- TEST_PCHECK(errno == EPERM);
- _exit(0);
- }
- ASSERT_THAT(tracer_pid, SyscallSucceeds());
-
- // Clean up tracer.
- int status;
- ASSERT_THAT(waitpid(tracer_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << " status " << status;
-
- // Clean up tracee.
- ASSERT_THAT(kill(tracee_pid, SIGKILL), SyscallSucceeds());
- ASSERT_THAT(waitpid(tracee_pid, &status, 0),
- SyscallSucceedsWithValue(tracee_pid));
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
- << " status " << status;
-}
-
-[[noreturn]] void RunPrctlClearPtracer(int fd) {
- ScopedThread t([fd] {
- // Perform prctl in a separate thread to verify that it is process-wide.
- TEST_PCHECK(prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY) == 0);
- MaybeSave();
- TEST_PCHECK(prctl(PR_SET_PTRACER, 0) == 0);
- MaybeSave();
- // Indicate that the prctl has been set/cleared.
- TEST_PCHECK(write(fd, "x", 1) == 1);
- MaybeSave();
- });
- while (true) {
- SleepSafe(absl::Seconds(1));
- }
-}
-TEST(PtraceTest, PrctlReplacePtracer) {
- SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) != 1);
AutoCapability cap(CAP_SYS_PTRACE, false);
- pid_t const unused_pid = fork();
- if (unused_pid == 0) {
- while (true) {
- SleepSafe(absl::Seconds(1));
- }
- }
- ASSERT_THAT(unused_pid, SyscallSucceeds());
+ // Ensure that initially, no tracer exception is set.
+ ASSERT_THAT(prctl(PR_SET_PTRACER, 0), SyscallSucceeds());
- // Use sockets to synchronize between tracer and tracee.
- int sockets[2];
- ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds());
+ SimpleSubprocess tracee = CreatePrctlSetPtracerSubprocess();
+ SimpleSubprocess tracer = CreatePtraceAttacherSubprocess();
- // Allocate vector before forking (not async-signal-safe).
- ExecveArray const owned_child_argv = {
- "/proc/self/exe",
- "--ptrace_test_prctl_replace_ptracer",
- "--ptrace_test_prctl_replace_ptracer_tid",
- std::to_string(unused_pid),
- "--ptrace_test_fd",
- std::to_string(sockets[0])};
- char* const* const child_argv = owned_child_argv.get();
+ // By default, Yama should prevent tracer from tracing its parent (this
+ // process) or siblings (tracee).
+ EXPECT_THAT(tracer.Cmd(gettid()), PosixErrorIs(EPERM));
+ EXPECT_THAT(tracer.Cmd(tracee.pid()), PosixErrorIs(EPERM));
- pid_t const tracee_pid = fork();
- if (tracee_pid == 0) {
- TEST_PCHECK(close(sockets[1]) == 0);
- // This test will create a new thread in the child process.
- // pthread_create(2) isn't async-signal-safe, so we execve() first.
- execve(child_argv[0], child_argv, /* envp = */ nullptr);
- TEST_PCHECK_MSG(false, "Survived execve to test child");
- }
- ASSERT_THAT(tracee_pid, SyscallSucceeds());
- ASSERT_THAT(close(sockets[0]), SyscallSucceeds());
+ // If tracee invokes PR_SET_PTRACER on either tracer's pid, the pid of any of
+ // its ancestors (i.e. us), or PR_SET_PTRACER_ANY, then tracer can trace it
+ // (but not us).
- pid_t const tracer_pid = fork();
- if (tracer_pid == 0) {
- // Wait until tracee has called prctl.
- char done;
- TEST_PCHECK(read(sockets[1], &done, 1) == 1);
- MaybeSave();
+ ASSERT_THAT(tracee.Cmd(tracer.pid()), PosixErrorIs(0));
+ EXPECT_THAT(tracer.Cmd(tracee.pid()), PosixErrorIs(0));
+ EXPECT_THAT(tracer.Cmd(gettid()), PosixErrorIs(EPERM));
- TEST_CHECK(CheckPtraceAttach(tracee_pid) == -1);
- TEST_PCHECK(errno == EPERM);
- _exit(0);
- }
- ASSERT_THAT(tracer_pid, SyscallSucceeds());
+ ASSERT_THAT(tracee.Cmd(gettid()), PosixErrorIs(0));
+ EXPECT_THAT(tracer.Cmd(tracee.pid()), PosixErrorIs(0));
+ EXPECT_THAT(tracer.Cmd(gettid()), PosixErrorIs(EPERM));
- // Clean up tracer.
- int status;
- ASSERT_THAT(waitpid(tracer_pid, &status, 0), SyscallSucceeds());
- EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
- << " status " << status;
+ ASSERT_THAT(tracee.Cmd(static_cast<pid_t>(PR_SET_PTRACER_ANY)),
+ PosixErrorIs(0));
+ EXPECT_THAT(tracer.Cmd(tracee.pid()), PosixErrorIs(0));
+ EXPECT_THAT(tracer.Cmd(gettid()), PosixErrorIs(EPERM));
- // Clean up tracee.
- ASSERT_THAT(kill(tracee_pid, SIGKILL), SyscallSucceeds());
- ASSERT_THAT(waitpid(tracee_pid, &status, 0),
- SyscallSucceedsWithValue(tracee_pid));
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
- << " status " << status;
+ // If tracee invokes PR_SET_PTRACER with pid 0, then tracer can no longer
+ // trace it.
+ ASSERT_THAT(tracee.Cmd(0), PosixErrorIs(0));
+ EXPECT_THAT(tracer.Cmd(tracee.pid()), PosixErrorIs(EPERM));
- // Clean up unused.
- ASSERT_THAT(kill(unused_pid, SIGKILL), SyscallSucceeds());
- ASSERT_THAT(waitpid(unused_pid, &status, 0),
- SyscallSucceedsWithValue(unused_pid));
- EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
- << " status " << status;
-}
+ // If we invoke PR_SET_PTRACER with tracer's pid, then it can trace us (but
+ // not our descendants).
+ ASSERT_THAT(prctl(PR_SET_PTRACER, tracer.pid()), SyscallSucceeds());
+ EXPECT_THAT(tracer.Cmd(gettid()), PosixErrorIs(0));
+ EXPECT_THAT(tracer.Cmd(tracee.pid()), PosixErrorIs(EPERM));
-[[noreturn]] void RunPrctlReplacePtracer(int new_tracer_pid, int fd) {
- TEST_PCHECK(prctl(PR_SET_PTRACER, getppid()) == 0);
- MaybeSave();
+ // If we invoke PR_SET_PTRACER with pid 0, then tracer can no longer trace us.
+ ASSERT_THAT(prctl(PR_SET_PTRACER, 0), SyscallSucceeds());
+ EXPECT_THAT(tracer.Cmd(gettid()), PosixErrorIs(EPERM));
- ScopedThread t([new_tracer_pid, fd] {
- TEST_PCHECK(prctl(PR_SET_PTRACER, new_tracer_pid) == 0);
- MaybeSave();
- // Indicate that the prctl has been set.
- TEST_PCHECK(write(fd, "x", 1) == 1);
- MaybeSave();
- });
- while (true) {
- SleepSafe(absl::Seconds(1));
- }
+ // Another thread in our thread group can invoke PR_SET_PTRACER instead; its
+ // effect applies to the whole thread group.
+ pid_t const our_tid = gettid();
+ ScopedThread([&] {
+ ASSERT_THAT(prctl(PR_SET_PTRACER, tracer.pid()), SyscallSucceeds());
+ EXPECT_THAT(tracer.Cmd(gettid()), PosixErrorIs(0));
+ EXPECT_THAT(tracer.Cmd(our_tid), PosixErrorIs(0));
+
+ ASSERT_THAT(prctl(PR_SET_PTRACER, 0), SyscallSucceeds());
+ EXPECT_THAT(tracer.Cmd(gettid()), PosixErrorIs(EPERM));
+ EXPECT_THAT(tracer.Cmd(our_tid), PosixErrorIs(EPERM));
+ }).Join();
}
// Tests that YAMA exceptions store tracees by thread group leader. Exceptions
@@ -2342,21 +2255,12 @@ int main(int argc, char** argv) {
gvisor::testing::RunTraceDescendantsAllowed(fd);
}
- if (absl::GetFlag(FLAGS_ptrace_test_prctl_set_ptracer_pid)) {
- gvisor::testing::RunPrctlSetPtracerPID(fd);
- }
-
- if (absl::GetFlag(FLAGS_ptrace_test_prctl_set_ptracer_any)) {
- gvisor::testing::RunPrctlSetPtracerAny(fd);
- }
-
- if (absl::GetFlag(FLAGS_ptrace_test_prctl_clear_ptracer)) {
- gvisor::testing::RunPrctlClearPtracer(fd);
+ if (absl::GetFlag(FLAGS_ptrace_test_ptrace_attacher)) {
+ gvisor::testing::RunPtraceAttacher(fd);
}
- if (absl::GetFlag(FLAGS_ptrace_test_prctl_replace_ptracer)) {
- gvisor::testing::RunPrctlReplacePtracer(
- absl::GetFlag(FLAGS_ptrace_test_prctl_replace_ptracer_tid), fd);
+ if (absl::GetFlag(FLAGS_ptrace_test_prctl_set_ptracer)) {
+ gvisor::testing::RunPrctlSetPtracer(fd);
}
if (absl::GetFlag(
diff --git a/test/syscalls/linux/pty.cc b/test/syscalls/linux/pty.cc
index 8d15c491e..5ff1f12a0 100644
--- a/test/syscalls/linux/pty.cc
+++ b/test/syscalls/linux/pty.cc
@@ -40,6 +40,7 @@
#include "test/util/file_descriptor.h"
#include "test/util/posix_error.h"
#include "test/util/pty_util.h"
+#include "test/util/signal_util.h"
#include "test/util/test_util.h"
#include "test/util/thread_util.h"
@@ -387,6 +388,22 @@ PosixErrorOr<size_t> PollAndReadFd(int fd, void* buf, size_t count,
}
TEST(PtyTrunc, Truncate) {
+ SKIP_IF(IsRunningWithVFS1());
+
+ // setsid either puts us in a new session or fails because we're already the
+ // session leader. Either way, this ensures we're the session leader and have
+ // no controlling terminal.
+ ASSERT_THAT(setsid(), AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(EPERM)));
+
+ // Make sure we're ignoring SIGHUP, which will be sent to this process once we
+ // disconnect the TTY.
+ struct sigaction sa = {};
+ sa.sa_handler = SIG_IGN;
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ const Cleanup cleanup =
+ ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGHUP, sa));
+
// Opening PTYs with O_TRUNC shouldn't cause an error, but calls to
// (f)truncate should.
FileDescriptor master =
@@ -395,6 +412,7 @@ TEST(PtyTrunc, Truncate) {
std::string spath = absl::StrCat("/dev/pts/", n);
FileDescriptor replica =
ASSERT_NO_ERRNO_AND_VALUE(Open(spath, O_RDWR | O_NONBLOCK | O_TRUNC));
+ ASSERT_THAT(ioctl(replica.get(), TIOCNOTTY), SyscallSucceeds());
EXPECT_THAT(truncate(kMasterPath, 0), SyscallFailsWithErrno(EINVAL));
EXPECT_THAT(truncate(spath.c_str(), 0), SyscallFailsWithErrno(EINVAL));
@@ -464,10 +482,10 @@ TEST(BasicPtyTest, OpenSetsControllingTTY) {
SKIP_IF(IsRunningWithVFS1());
// setsid either puts us in a new session or fails because we're already the
// session leader. Either way, this ensures we're the session leader.
- setsid();
+ ASSERT_THAT(setsid(), AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(EPERM)));
// Make sure we're ignoring SIGHUP, which will be sent to this process once we
- // disconnect they TTY.
+ // disconnect the TTY.
struct sigaction sa = {};
sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;
@@ -491,7 +509,7 @@ TEST(BasicPtyTest, OpenMasterDoesNotSetsControllingTTY) {
SKIP_IF(IsRunningWithVFS1());
// setsid either puts us in a new session or fails because we're already the
// session leader. Either way, this ensures we're the session leader.
- setsid();
+ ASSERT_THAT(setsid(), AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(EPERM)));
FileDescriptor master = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR));
// Opening master does not set the controlling TTY, and therefore we are
@@ -503,7 +521,7 @@ TEST(BasicPtyTest, OpenNOCTTY) {
SKIP_IF(IsRunningWithVFS1());
// setsid either puts us in a new session or fails because we're already the
// session leader. Either way, this ensures we're the session leader.
- setsid();
+ ASSERT_THAT(setsid(), AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(EPERM)));
FileDescriptor master = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/ptmx", O_RDWR));
FileDescriptor replica = ASSERT_NO_ERRNO_AND_VALUE(
OpenReplica(master, O_NOCTTY | O_NONBLOCK | O_RDWR));
@@ -1405,7 +1423,7 @@ TEST_F(JobControlTest, ReleaseTTY) {
ASSERT_THAT(ioctl(replica_.get(), TIOCSCTTY, 0), SyscallSucceeds());
// Make sure we're ignoring SIGHUP, which will be sent to this process once we
- // disconnect they TTY.
+ // disconnect the TTY.
struct sigaction sa = {};
sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;
@@ -1526,7 +1544,7 @@ TEST_F(JobControlTest, ReleaseTTYSignals) {
EXPECT_THAT(setpgid(diff_pgrp_child, diff_pgrp_child), SyscallSucceeds());
// Make sure we're ignoring SIGHUP, which will be sent to this process once we
- // disconnect they TTY.
+ // disconnect the TTY.
struct sigaction sighup_sa = {};
sighup_sa.sa_handler = SIG_IGN;
sighup_sa.sa_flags = 0;
diff --git a/test/syscalls/linux/raw_socket_hdrincl.cc b/test/syscalls/linux/raw_socket_hdrincl.cc
index 8b3d02d97..4611b6283 100644
--- a/test/syscalls/linux/raw_socket_hdrincl.cc
+++ b/test/syscalls/linux/raw_socket_hdrincl.cc
@@ -177,11 +177,10 @@ TEST_F(RawHDRINCL, ConnectToLoopback) {
SyscallSucceeds());
}
-// FIXME(gvisor.dev/issue/3159): Test currently flaky.
-TEST_F(RawHDRINCL, DISABLED_SendWithoutConnectSucceeds) {
+TEST_F(RawHDRINCL, SendWithoutConnectFails) {
struct iphdr hdr = LoopbackHeader();
ASSERT_THAT(send(socket_, &hdr, sizeof(hdr), 0),
- SyscallSucceedsWithValue(sizeof(hdr)));
+ SyscallFailsWithErrno(EDESTADDRREQ));
}
// HDRINCL implies write-only. Verify that we can't read a packet sent to
@@ -282,9 +281,6 @@ TEST_F(RawHDRINCL, SendAndReceive) {
// Send and receive a packet where the sendto address is not the same as the
// provided destination.
TEST_F(RawHDRINCL, SendAndReceiveDifferentAddress) {
- // FIXME(gvisor.dev/issue/3160): Test currently flaky.
- SKIP_IF(true);
-
int port = 40000;
if (!IsRunningOnGvisor()) {
port = static_cast<short>(ASSERT_NO_ERRNO_AND_VALUE(
@@ -302,18 +298,20 @@ TEST_F(RawHDRINCL, SendAndReceiveDifferentAddress) {
ASSERT_TRUE(
FillPacket(packet, sizeof(packet), port, kPayload, sizeof(kPayload)));
// Overwrite the IP destination address with an IP we can't get to.
+ constexpr int32_t kUnreachable = 42;
struct iphdr iphdr = {};
memcpy(&iphdr, packet, sizeof(iphdr));
- iphdr.daddr = 42;
+ iphdr.daddr = kUnreachable;
memcpy(packet, &iphdr, sizeof(iphdr));
+ // Send to localhost via loopback.
socklen_t addrlen = sizeof(addr_);
ASSERT_NO_FATAL_FAILURE(sendto(socket_, &packet, sizeof(packet), 0,
reinterpret_cast<struct sockaddr*>(&addr_),
addrlen));
- // Receive the payload, since sendto should replace the bad destination with
- // localhost.
+ // Receive the payload. Despite an unreachable destination address, sendto
+ // should have sent the packet through loopback.
char recv_buf[sizeof(packet)];
struct sockaddr_in src;
socklen_t src_size = sizeof(src);
@@ -331,9 +329,8 @@ TEST_F(RawHDRINCL, SendAndReceiveDifferentAddress) {
struct iphdr recv_iphdr = {};
memcpy(&recv_iphdr, recv_buf, sizeof(recv_iphdr));
EXPECT_NE(recv_iphdr.id, 0);
- // The destination address should be localhost, not the bad IP we set
- // initially.
- EXPECT_EQ(absl::gbswap_32(recv_iphdr.daddr), INADDR_LOOPBACK);
+ // The destination address is kUnreachable despite arriving via loopback.
+ EXPECT_EQ(recv_iphdr.daddr, kUnreachable);
}
// Send and receive a packet w/ the IP_HDRINCL option set.
diff --git a/test/syscalls/linux/raw_socket_icmp.cc b/test/syscalls/linux/raw_socket_icmp.cc
index bd779da92..275996bd3 100644
--- a/test/syscalls/linux/raw_socket_icmp.cc
+++ b/test/syscalls/linux/raw_socket_icmp.cc
@@ -262,8 +262,8 @@ TEST_F(RawSocketICMPTest, RawAndPingSockets) {
}
// A raw ICMP socket should be able to send a malformed short ICMP Echo Request,
-// while ping socket should not.
-// Neither should be able to receieve a short malformed packet.
+// while a ping socket should not. Neither should be able to receieve a short
+// malformed packet.
TEST_F(RawSocketICMPTest, ShortEchoRawAndPingSockets) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
diff --git a/test/syscalls/linux/read.cc b/test/syscalls/linux/read.cc
index 7056342d7..7756af24d 100644
--- a/test/syscalls/linux/read.cc
+++ b/test/syscalls/linux/read.cc
@@ -157,7 +157,8 @@ TEST_F(ReadTest, PartialReadSIGSEGV) {
.iov_len = size,
},
};
- EXPECT_THAT(preadv(fd.get(), iov, ABSL_ARRAYSIZE(iov), 0),
+ EXPECT_THAT(lseek(fd.get(), 0, SEEK_SET), SyscallSucceeds());
+ EXPECT_THAT(readv(fd.get(), iov, ABSL_ARRAYSIZE(iov)),
SyscallSucceedsWithValue(size));
}
diff --git a/test/syscalls/linux/rename.cc b/test/syscalls/linux/rename.cc
index 76a8da65f..561eed99f 100644
--- a/test/syscalls/linux/rename.cc
+++ b/test/syscalls/linux/rename.cc
@@ -26,6 +26,8 @@
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
+using ::testing::AnyOf;
+
namespace gvisor {
namespace testing {
@@ -438,6 +440,60 @@ TEST(RenameTest, SysfsDirectoryToSelf) {
EXPECT_THAT(rename(path.c_str(), path.c_str()), SyscallSucceeds());
}
+#ifndef SYS_renameat2
+#if defined(__x86_64__)
+#define SYS_renameat2 316
+#elif defined(__aarch64__)
+#define SYS_renameat2 276
+#else
+#error "Unknown architecture"
+#endif
+#endif // SYS_renameat2
+
+#ifndef RENAME_NOREPLACE
+#define RENAME_NOREPLACE (1 << 0)
+#endif // RENAME_NOREPLACE
+
+int renameat2(int olddirfd, const char* oldpath, int newdirfd,
+ const char* newpath, unsigned int flags) {
+ return syscall(SYS_renameat2, olddirfd, oldpath, newdirfd, newpath, flags);
+}
+
+TEST(Renameat2Test, NoReplaceSuccess) {
+ auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ std::string const newpath = NewTempAbsPath();
+ // renameat2 may fail with ENOSYS (if the syscall is unsupported) or EINVAL
+ // (if flags are unsupported), or succeed (if RENAME_NOREPLACE is operating
+ // correctly).
+ EXPECT_THAT(
+ renameat2(AT_FDCWD, f.path().c_str(), AT_FDCWD, newpath.c_str(),
+ RENAME_NOREPLACE),
+ AnyOf(SyscallFailsWithErrno(AnyOf(ENOSYS, EINVAL)), SyscallSucceeds()));
+}
+
+TEST(Renameat2Test, NoReplaceExisting) {
+ auto f1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ auto f2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ // renameat2 may fail with ENOSYS (if the syscall is unsupported), EINVAL (if
+ // flags are unsupported), or EEXIST (if RENAME_NOREPLACE is operating
+ // correctly).
+ EXPECT_THAT(renameat2(AT_FDCWD, f1.path().c_str(), AT_FDCWD,
+ f2.path().c_str(), RENAME_NOREPLACE),
+ SyscallFailsWithErrno(AnyOf(ENOSYS, EINVAL, EEXIST)));
+}
+
+TEST(Renameat2Test, NoReplaceDot) {
+ auto d1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ auto d2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ // renameat2 may fail with ENOSYS (if the syscall is unsupported), EINVAL (if
+ // flags are unsupported), or EEXIST (if RENAME_NOREPLACE is operating
+ // correctly).
+ EXPECT_THAT(
+ renameat2(AT_FDCWD, d1.path().c_str(), AT_FDCWD,
+ absl::StrCat(d2.path(), "/.").c_str(), RENAME_NOREPLACE),
+ SyscallFailsWithErrno(AnyOf(ENOSYS, EINVAL, EEXIST)));
+}
+
} // namespace
} // namespace testing
diff --git a/test/syscalls/linux/semaphore.cc b/test/syscalls/linux/semaphore.cc
index 2ce8f836c..f72957f89 100644
--- a/test/syscalls/linux/semaphore.cc
+++ b/test/syscalls/linux/semaphore.cc
@@ -49,6 +49,9 @@ constexpr int kSemAem = 32767;
class AutoSem {
public:
+ // Creates a new private semaphore.
+ AutoSem() : id_(semget(IPC_PRIVATE, 1, 0)) {}
+
explicit AutoSem(int id) : id_(id) {}
~AutoSem() {
if (id_ >= 0) {
@@ -101,6 +104,20 @@ TEST(SemaphoreTest, SemGet) {
EXPECT_NE(sem3.get(), sem2.get());
}
+// Tests system-wide limits for semget.
+TEST(SemaphoreTest, SemGetSystemLimits) {
+ // Disable save so that we don't trigger save/restore too many times.
+ const DisableSave ds;
+
+ // Exceed number of semaphores per set.
+ EXPECT_THAT(semget(IPC_PRIVATE, kSemMsl + 1, 0),
+ SyscallFailsWithErrno(EINVAL));
+
+ // Exceed system-wide limit for semaphore sets by 1.
+ AutoSem sems[kSemMni];
+ EXPECT_THAT(semget(IPC_PRIVATE, 1, 0), SyscallFailsWithErrno(ENOSPC));
+}
+
// Tests simple operations that shouldn't block in a single-thread.
TEST(SemaphoreTest, SemOpSingleNoBlock) {
AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT));
diff --git a/test/syscalls/linux/setgid.cc b/test/syscalls/linux/setgid.cc
index ce61bc36d..6278c4fab 100644
--- a/test/syscalls/linux/setgid.cc
+++ b/test/syscalls/linux/setgid.cc
@@ -115,8 +115,6 @@ class SetgidDirTest : public ::testing::Test {
void SetUp() override {
original_gid_ = getegid();
- SKIP_IF(IsRunningWithVFS1());
-
// If we can't find two usable groups, we're in an unsupporting environment.
// Skip the test.
PosixErrorOr<std::pair<gid_t, gid_t>> groups = Groups();
@@ -294,8 +292,8 @@ TEST_F(SetgidDirTest, ChownFileClears) {
EXPECT_EQ(stats.st_mode & (S_ISUID | S_ISGID), 0);
}
-// Chowning a file with setgid enabled, but not the group exec bit, does not
-// clear the setgid bit. Such files are mandatory locked.
+// Chowning a file with setgid enabled, but not the group exec bit, clears the
+// setuid bit and not the setgid bit. Such files are mandatory locked.
TEST_F(SetgidDirTest, ChownNoExecFileDoesNotClear) {
// Set group to G1, create a directory, and enable setgid.
auto g1owned = JoinPath(temp_dir_.path(), "g1owned/");
@@ -345,7 +343,6 @@ struct FileModeTestcase {
class FileModeTest : public ::testing::TestWithParam<FileModeTestcase> {};
TEST_P(FileModeTest, WriteToFile) {
- SKIP_IF(IsRunningWithVFS1());
PosixErrorOr<std::pair<gid_t, gid_t>> groups = Groups();
SKIP_IF(!groups.ok());
@@ -372,7 +369,6 @@ TEST_P(FileModeTest, WriteToFile) {
}
TEST_P(FileModeTest, TruncateFile) {
- SKIP_IF(IsRunningWithVFS1());
PosixErrorOr<std::pair<gid_t, gid_t>> groups = Groups();
SKIP_IF(!groups.ok());
diff --git a/test/syscalls/linux/sigstop.cc b/test/syscalls/linux/sigstop.cc
index b2fcedd62..8de03b4dc 100644
--- a/test/syscalls/linux/sigstop.cc
+++ b/test/syscalls/linux/sigstop.cc
@@ -123,6 +123,55 @@ void SleepIgnoreStopped(absl::Duration d) {
}
}
+TEST(SigstopTest, RestartSyscall) {
+ pid_t pid;
+ constexpr absl::Duration kStopDelay = absl::Seconds(5);
+ constexpr absl::Duration kSleepDelay = absl::Seconds(15);
+ constexpr absl::Duration kStartupDelay = absl::Seconds(5);
+ constexpr absl::Duration kErrorDelay = absl::Seconds(3);
+
+ const DisableSave ds; // Timing-related.
+
+ pid = fork();
+ if (pid == 0) {
+ struct timespec ts = {.tv_sec = kSleepDelay / absl::Seconds(1)};
+ auto start = absl::Now();
+ TEST_CHECK(nanosleep(&ts, nullptr) == 0);
+ auto finish = absl::Now();
+ // Check that time spent stopped is counted as time spent sleeping.
+ TEST_CHECK(finish - start >= kSleepDelay);
+ TEST_CHECK(finish - start < kSleepDelay + kErrorDelay);
+ _exit(kChildMainThreadExitCode);
+ }
+ ASSERT_THAT(pid, SyscallSucceeds());
+
+ // Wait for the child subprocess to start sleeping before stopping it.
+ absl::SleepFor(kStartupDelay);
+ ASSERT_THAT(kill(pid, SIGSTOP), SyscallSucceeds());
+ int status;
+ EXPECT_THAT(RetryEINTR(waitpid)(pid, &status, WUNTRACED),
+ SyscallSucceedsWithValue(pid));
+ EXPECT_TRUE(WIFSTOPPED(status));
+ EXPECT_EQ(SIGSTOP, WSTOPSIG(status));
+
+ // Sleep for shorter than the sleep in the child subprocess.
+ absl::SleepFor(kStopDelay);
+ ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, WNOHANG),
+ SyscallSucceedsWithValue(0));
+
+ // Resume the child.
+ ASSERT_THAT(kill(pid, SIGCONT), SyscallSucceeds());
+
+ EXPECT_THAT(RetryEINTR(waitpid)(pid, &status, WCONTINUED),
+ SyscallSucceedsWithValue(pid));
+ EXPECT_TRUE(WIFCONTINUED(status));
+
+ // Expect it to die.
+ ASSERT_THAT(RetryEINTR(waitpid)(pid, &status, 0), SyscallSucceeds());
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(WEXITSTATUS(status), kChildMainThreadExitCode);
+}
+
void RunChild() {
// Start another thread that attempts to call exit_group with a different
// error code, in order to verify that SIGSTOP stops this thread as well.
diff --git a/test/syscalls/linux/socket_bind_to_device_distribution.cc b/test/syscalls/linux/socket_bind_to_device_distribution.cc
index 3b108cbd3..70b0b2742 100644
--- a/test/syscalls/linux/socket_bind_to_device_distribution.cc
+++ b/test/syscalls/linux/socket_bind_to_device_distribution.cc
@@ -77,34 +77,6 @@ class BindToDeviceDistributionTest
}
};
-PosixErrorOr<uint16_t> AddrPort(int family, sockaddr_storage const& addr) {
- switch (family) {
- case AF_INET:
- return static_cast<uint16_t>(
- reinterpret_cast<sockaddr_in const*>(&addr)->sin_port);
- case AF_INET6:
- return static_cast<uint16_t>(
- reinterpret_cast<sockaddr_in6 const*>(&addr)->sin6_port);
- default:
- return PosixError(EINVAL,
- absl::StrCat("unknown socket family: ", family));
- }
-}
-
-PosixError SetAddrPort(int family, sockaddr_storage* addr, uint16_t port) {
- switch (family) {
- case AF_INET:
- reinterpret_cast<sockaddr_in*>(addr)->sin_port = port;
- return NoError();
- case AF_INET6:
- reinterpret_cast<sockaddr_in6*>(addr)->sin6_port = port;
- return NoError();
- default:
- return PosixError(EINVAL,
- absl::StrCat("unknown socket family: ", family));
- }
-}
-
// Binds sockets to different devices and then creates many TCP connections.
// Checks that the distribution of connections received on the sockets matches
// the expectation.
diff --git a/test/syscalls/linux/socket_bind_to_device_util.cc b/test/syscalls/linux/socket_bind_to_device_util.cc
index f4ee775bd..ce5f63938 100644
--- a/test/syscalls/linux/socket_bind_to_device_util.cc
+++ b/test/syscalls/linux/socket_bind_to_device_util.cc
@@ -58,8 +58,10 @@ PosixErrorOr<std::unique_ptr<Tunnel>> Tunnel::New(string tunnel_name) {
}
std::unordered_set<string> GetInterfaceNames() {
- struct if_nameindex* interfaces = if_nameindex();
std::unordered_set<string> names;
+#ifndef ANDROID
+ // Android does not support if_nameindex in r22.
+ struct if_nameindex* interfaces = if_nameindex();
if (interfaces == nullptr) {
return names;
}
@@ -68,6 +70,7 @@ std::unordered_set<string> GetInterfaceNames() {
names.insert(interface->if_name);
}
if_freenameindex(interfaces);
+#endif
return names;
}
diff --git a/test/syscalls/linux/socket_inet_loopback.cc b/test/syscalls/linux/socket_inet_loopback.cc
index 9a6b089f6..badc42ec5 100644
--- a/test/syscalls/linux/socket_inet_loopback.cc
+++ b/test/syscalls/linux/socket_inet_loopback.cc
@@ -34,6 +34,7 @@
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "test/syscalls/linux/ip_socket_test_util.h"
+#include "test/syscalls/linux/socket_inet_loopback_test_params.h"
#include "test/syscalls/linux/socket_test_util.h"
#include "test/util/file_descriptor.h"
#include "test/util/posix_error.h"
@@ -48,45 +49,7 @@ namespace {
using ::testing::Gt;
-PosixErrorOr<uint16_t> AddrPort(int family, sockaddr_storage const& addr) {
- switch (family) {
- case AF_INET:
- return static_cast<uint16_t>(
- reinterpret_cast<sockaddr_in const*>(&addr)->sin_port);
- case AF_INET6:
- return static_cast<uint16_t>(
- reinterpret_cast<sockaddr_in6 const*>(&addr)->sin6_port);
- default:
- return PosixError(EINVAL,
- absl::StrCat("unknown socket family: ", family));
- }
-}
-
-PosixError SetAddrPort(int family, sockaddr_storage* addr, uint16_t port) {
- switch (family) {
- case AF_INET:
- reinterpret_cast<sockaddr_in*>(addr)->sin_port = port;
- return NoError();
- case AF_INET6:
- reinterpret_cast<sockaddr_in6*>(addr)->sin6_port = port;
- return NoError();
- default:
- return PosixError(EINVAL,
- absl::StrCat("unknown socket family: ", family));
- }
-}
-
-struct TestParam {
- TestAddress listener;
- TestAddress connector;
-};
-
-std::string DescribeTestParam(::testing::TestParamInfo<TestParam> const& info) {
- return absl::StrCat("Listen", info.param.listener.description, "_Connect",
- info.param.connector.description);
-}
-
-using SocketInetLoopbackTest = ::testing::TestWithParam<TestParam>;
+using SocketInetLoopbackTest = ::testing::TestWithParam<SocketInetTestParam>;
TEST(BadSocketPairArgs, ValidateErrForBadCallsToSocketPair) {
int fd[2] = {};
@@ -299,7 +262,7 @@ void tcpSimpleConnectTest(TestAddress const& listener,
}
TEST_P(SocketInetLoopbackTest, TCP) {
- auto const& param = GetParam();
+ SocketInetTestParam const& param = GetParam();
TestAddress const& listener = param.listener;
TestAddress const& connector = param.connector;
@@ -307,7 +270,7 @@ TEST_P(SocketInetLoopbackTest, TCP) {
}
TEST_P(SocketInetLoopbackTest, TCPListenUnbound) {
- auto const& param = GetParam();
+ SocketInetTestParam const& param = GetParam();
TestAddress const& listener = param.listener;
TestAddress const& connector = param.connector;
@@ -316,7 +279,7 @@ TEST_P(SocketInetLoopbackTest, TCPListenUnbound) {
}
TEST_P(SocketInetLoopbackTest, TCPListenShutdownListen) {
- const auto& param = GetParam();
+ SocketInetTestParam const& param = GetParam();
const TestAddress& listener = param.listener;
const TestAddress& connector = param.connector;
@@ -362,13 +325,14 @@ TEST_P(SocketInetLoopbackTest, TCPListenShutdownListen) {
}
TEST_P(SocketInetLoopbackTest, TCPListenShutdown) {
- auto const& param = GetParam();
+ SocketInetTestParam const& param = GetParam();
TestAddress const& listener = param.listener;
TestAddress const& connector = param.connector;
constexpr int kBacklog = 2;
- constexpr int kFDs = kBacklog + 1;
+ // See the comment in TCPBacklog for why this isn't kBacklog + 1.
+ constexpr int kFDs = kBacklog;
// Create the listening socket.
FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
@@ -429,7 +393,7 @@ TEST_P(SocketInetLoopbackTest, TCPListenShutdown) {
}
TEST_P(SocketInetLoopbackTest, TCPListenClose) {
- auto const& param = GetParam();
+ SocketInetTestParam const& param = GetParam();
TestAddress const& listener = param.listener;
TestAddress const& connector = param.connector;
@@ -454,6 +418,8 @@ TEST_P(SocketInetLoopbackTest, TCPListenClose) {
uint16_t const port =
ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
+ // Connect repeatedly, keeping each connection open. After kBacklog
+ // connections, we'll start getting EINPROGRESS.
sockaddr_storage conn_addr = connector.addr;
ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
std::vector<FileDescriptor> clients;
@@ -472,7 +438,78 @@ TEST_P(SocketInetLoopbackTest, TCPListenClose) {
}
}
-void TestHangupDuringConnect(const TestParam& param,
+// Test the protocol state information returned by TCPINFO.
+TEST_P(SocketInetLoopbackTest, TCPInfoState) {
+ SocketInetTestParam const& param = GetParam();
+ TestAddress const& listener = param.listener;
+ TestAddress const& connector = param.connector;
+
+ // Create the listening socket.
+ FileDescriptor const listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP));
+
+ auto state = [](int fd) -> int {
+ struct tcp_info opt = {};
+ socklen_t optLen = sizeof(opt);
+ EXPECT_THAT(getsockopt(fd, SOL_TCP, TCP_INFO, &opt, &optLen),
+ SyscallSucceeds());
+ return opt.tcpi_state;
+ };
+ ASSERT_EQ(state(listen_fd.get()), TCP_CLOSE);
+
+ sockaddr_storage listen_addr = listener.addr;
+ ASSERT_THAT(
+ bind(listen_fd.get(), AsSockAddr(&listen_addr), listener.addr_len),
+ SyscallSucceeds());
+ ASSERT_EQ(state(listen_fd.get()), TCP_CLOSE);
+
+ ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds());
+ ASSERT_EQ(state(listen_fd.get()), TCP_LISTEN);
+
+ // Get the port bound by the listening socket.
+ socklen_t addrlen = listener.addr_len;
+ ASSERT_THAT(getsockname(listen_fd.get(), AsSockAddr(&listen_addr), &addrlen),
+ SyscallSucceeds());
+ uint16_t const port =
+ ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
+
+ // Connect to the listening socket.
+ FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
+ sockaddr_storage conn_addr = connector.addr;
+ ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
+ ASSERT_EQ(state(conn_fd.get()), TCP_CLOSE);
+ ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), AsSockAddr(&conn_addr),
+ connector.addr_len),
+ SyscallSucceeds());
+ ASSERT_EQ(state(conn_fd.get()), TCP_ESTABLISHED);
+
+ auto accepted =
+ ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr));
+ ASSERT_EQ(state(accepted.get()), TCP_ESTABLISHED);
+
+ ASSERT_THAT(close(accepted.release()), SyscallSucceeds());
+
+ struct pollfd pfd = {
+ .fd = conn_fd.get(),
+ .events = POLLIN | POLLRDHUP,
+ };
+ constexpr int kTimeout = 10000;
+ int n = poll(&pfd, 1, kTimeout);
+ ASSERT_GE(n, 0) << strerror(errno);
+ ASSERT_EQ(n, 1);
+ if (IsRunningOnGvisor() && GvisorPlatform() != Platform::kFuchsia) {
+ // TODO(gvisor.dev/issue/6015): Notify POLLRDHUP on incoming FIN.
+ ASSERT_EQ(pfd.revents, POLLIN);
+ } else {
+ ASSERT_EQ(pfd.revents, POLLIN | POLLRDHUP);
+ }
+
+ ASSERT_THAT(state(conn_fd.get()), TCP_CLOSE_WAIT);
+ ASSERT_THAT(close(conn_fd.release()), SyscallSucceeds());
+}
+
+void TestHangupDuringConnect(const SocketInetTestParam& param,
void (*hangup)(FileDescriptor&)) {
TestAddress const& listener = param.listener;
TestAddress const& connector = param.connector;
@@ -535,8 +572,10 @@ TEST_P(SocketInetLoopbackTest, TCPListenShutdownDuringConnect) {
});
}
-void TestListenHangupConnectingRead(const TestParam& param,
+void TestListenHangupConnectingRead(const SocketInetTestParam& param,
void (*hangup)(FileDescriptor&)) {
+ constexpr int kTimeout = 10000;
+
TestAddress const& listener = param.listener;
TestAddress const& connector = param.connector;
@@ -566,14 +605,33 @@ void TestListenHangupConnectingRead(const TestParam& param,
sockaddr_storage conn_addr = connector.addr;
ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
FileDescriptor established_client = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
- ASSERT_THAT(connect(established_client.get(), AsSockAddr(&conn_addr),
- connector.addr_len),
- SyscallSucceeds());
+ Socket(connector.family(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP));
+ int ret = connect(established_client.get(), AsSockAddr(&conn_addr),
+ connector.addr_len);
+ if (ret != 0) {
+ EXPECT_THAT(ret, SyscallFailsWithErrno(EINPROGRESS));
+ }
- // Ensure that the accept queue has the completed connection.
- constexpr int kTimeout = 10000;
+ // On some kernels a backlog of 0 means no backlog, while on others it means a
+ // backlog of 1. See commit c609e6aae4efcf383fe86b195d1b060befcb3666 for more
+ // explanation.
+ //
+ // If we timeout connecting to loopback, we're on a kernel with no backlog.
pollfd pfd = {
+ .fd = established_client.get(),
+ .events = POLLIN | POLLOUT,
+ };
+ if (!poll(&pfd, 1, kTimeout)) {
+ // We're on one of those kernels. It should be impossible to establish the
+ // connection, so connect will always return EALREADY.
+ EXPECT_THAT(connect(established_client.get(), AsSockAddr(&conn_addr),
+ connector.addr_len),
+ SyscallFailsWithErrno(EALREADY));
+ return;
+ }
+
+ // Ensure that the accept queue has the completed connection.
+ pfd = {
.fd = listen_fd.get(),
.events = POLLIN,
};
@@ -583,8 +641,8 @@ void TestListenHangupConnectingRead(const TestParam& param,
FileDescriptor connecting_client = ASSERT_NO_ERRNO_AND_VALUE(
Socket(connector.family(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP));
// Keep the last client in connecting state.
- int ret = connect(connecting_client.get(), AsSockAddr(&conn_addr),
- connector.addr_len);
+ ret = connect(connecting_client.get(), AsSockAddr(&conn_addr),
+ connector.addr_len);
if (ret != 0) {
EXPECT_THAT(ret, SyscallFailsWithErrno(EINPROGRESS));
}
@@ -621,12 +679,84 @@ TEST_P(SocketInetLoopbackTest, TCPListenShutdownConnectingRead) {
});
}
+// Test close of a non-blocking connecting socket.
+TEST_P(SocketInetLoopbackTest, TCPNonBlockingConnectClose) {
+ SocketInetTestParam const& param = GetParam();
+ TestAddress const& listener = param.listener;
+ TestAddress const& connector = param.connector;
+
+ // Create the listening socket.
+ FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(listener.family(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP));
+ sockaddr_storage listen_addr = listener.addr;
+ ASSERT_THAT(
+ bind(listen_fd.get(), AsSockAddr(&listen_addr), listener.addr_len),
+ SyscallSucceeds());
+ ASSERT_THAT(listen(listen_fd.get(), 0), SyscallSucceeds());
+
+ // Get the port bound by the listening socket.
+ socklen_t addrlen = listener.addr_len;
+ ASSERT_THAT(getsockname(listen_fd.get(), AsSockAddr(&listen_addr), &addrlen),
+ SyscallSucceeds());
+ ASSERT_EQ(addrlen, listener.addr_len);
+ uint16_t const port =
+ ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
+
+ sockaddr_storage conn_addr = connector.addr;
+ ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
+
+ // Try many iterations to catch a race with socket close and handshake
+ // completion.
+ for (int i = 0; i < 1000; ++i) {
+ FileDescriptor client = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(connector.family(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP));
+ ASSERT_THAT(
+ connect(client.get(), AsSockAddr(&conn_addr), connector.addr_len),
+ SyscallFailsWithErrno(EINPROGRESS));
+ ASSERT_THAT(close(client.release()), SyscallSucceeds());
+
+ // Accept any connections and check if they were closed from the peer. Not
+ // all client connects would result in an acceptable connection as the
+ // client handshake might never complete if the socket close was processed
+ // sooner than the non-blocking connect OR the accept queue is full. We are
+ // only interested in the case where we do have an acceptable completed
+ // connection. The accept is non-blocking here, which means that at the time
+ // of listener close (after the loop ends), we could still have a completed
+ // connection (from connect of any previous iteration) in the accept queue.
+ // The listener close would clean up the accept queue.
+ int accepted_fd;
+ ASSERT_THAT(accepted_fd = accept(listen_fd.get(), nullptr, nullptr),
+ AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(EWOULDBLOCK)));
+ if (accepted_fd < 0) {
+ continue;
+ }
+ FileDescriptor accepted(accepted_fd);
+ struct pollfd pfd = {
+ .fd = accepted.get(),
+ .events = POLLIN | POLLRDHUP,
+ };
+ // Use a large timeout to accomodate for retransmitted FINs.
+ constexpr int kTimeout = 30000;
+ int n = poll(&pfd, 1, kTimeout);
+ ASSERT_GE(n, 0) << strerror(errno);
+ ASSERT_EQ(n, 1);
+
+ if (IsRunningOnGvisor() && GvisorPlatform() != Platform::kFuchsia) {
+ // TODO(gvisor.dev/issue/6015): Notify POLLRDHUP on incoming FIN.
+ ASSERT_EQ(pfd.revents, POLLIN);
+ } else {
+ ASSERT_EQ(pfd.revents, POLLIN | POLLRDHUP);
+ }
+ ASSERT_THAT(close(accepted.release()), SyscallSucceeds());
+ }
+}
+
// TODO(b/157236388): Remove 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, TCPAcceptBacklogSizes) {
- auto const& param = GetParam();
+ SocketInetTestParam const& param = GetParam();
TestAddress const& listener = param.listener;
TestAddress const& connector = param.connector;
@@ -652,7 +782,8 @@ TEST_P(SocketInetLoopbackTest, TCPAcceptBacklogSizes) {
if (backlog < 0) {
expected_accepts = 1024;
} else {
- expected_accepts = backlog + 1;
+ // See the comment in TCPBacklog for why this isn't backlog + 1.
+ expected_accepts = backlog;
}
for (int i = 0; i < expected_accepts; i++) {
SCOPED_TRACE(absl::StrCat("i=", i));
@@ -675,7 +806,7 @@ TEST_P(SocketInetLoopbackTest, TCPAcceptBacklogSizes) {
// 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) {
- auto const& param = GetParam();
+ SocketInetTestParam const& param = GetParam();
TestAddress const& listener = param.listener;
TestAddress const& connector = param.connector;
@@ -753,7 +884,11 @@ TEST_P(SocketInetLoopbackTest, TCPBacklog) {
// enqueuing established connections to the accept queue, newer SYNs could
// still be replied to causing those client connections would be accepted as
// we start dequeuing the queue.
- ASSERT_GE(accepted_conns, kBacklogSize + 1);
+ //
+ // On some kernels this can value can be off by one, so we don't add 1 to
+ // kBacklogSize. See commit c609e6aae4efcf383fe86b195d1b060befcb3666 for more
+ // explanation.
+ ASSERT_GE(accepted_conns, kBacklogSize);
ASSERT_GE(client_conns, accepted_conns);
}
@@ -762,7 +897,7 @@ TEST_P(SocketInetLoopbackTest, TCPBacklog) {
// queue because the queue is full are not correctly delivered after restore
// causing the last accept to timeout on the restore.
TEST_P(SocketInetLoopbackTest, TCPBacklogAcceptAll) {
- auto const& param = GetParam();
+ SocketInetTestParam const& param = GetParam();
TestAddress const& listener = param.listener;
TestAddress const& connector = param.connector;
@@ -788,7 +923,9 @@ TEST_P(SocketInetLoopbackTest, TCPBacklogAcceptAll) {
// Fill up the accept queue and trigger more client connections which would be
// waiting to be accepted.
- std::array<FileDescriptor, kBacklog + 1> established_clients;
+ //
+ // See the comment in TCPBacklog for why this isn't backlog + 1.
+ std::array<FileDescriptor, kBacklog> established_clients;
for (auto& fd : established_clients) {
fd = ASSERT_NO_ERRNO_AND_VALUE(
Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
@@ -850,175 +987,12 @@ TEST_P(SocketInetLoopbackTest, TCPBacklogAcceptAll) {
}
}
-// TCPFinWait2Test creates a pair of connected sockets then closes one end to
-// trigger FIN_WAIT2 state for the closed endpoint. Then it binds the same local
-// IP/port on a new socket and tries to connect. The connect should fail w/
-// an EADDRINUSE. Then we wait till the FIN_WAIT2 timeout is over and try the
-// connect again with a new socket and this time it should succeed.
-//
-// TCP timers are not S/R today, this can cause this test to be flaky when run
-// under random S/R due to timer being reset on a restore.
-TEST_P(SocketInetLoopbackTest, TCPFinWait2Test) {
- auto const& param = GetParam();
- TestAddress const& listener = param.listener;
- TestAddress const& connector = param.connector;
-
- // Create the listening socket.
- const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP));
- sockaddr_storage listen_addr = listener.addr;
- ASSERT_THAT(
- bind(listen_fd.get(), AsSockAddr(&listen_addr), listener.addr_len),
- SyscallSucceeds());
- ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds());
-
- // Get the port bound by the listening socket.
- socklen_t addrlen = listener.addr_len;
- ASSERT_THAT(getsockname(listen_fd.get(), AsSockAddr(&listen_addr), &addrlen),
- SyscallSucceeds());
-
- uint16_t const port =
- ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
-
- // Connect to the listening socket.
- FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
-
- // Lower FIN_WAIT2 state to 5 seconds for test.
- constexpr int kTCPLingerTimeout = 5;
- EXPECT_THAT(setsockopt(conn_fd.get(), IPPROTO_TCP, TCP_LINGER2,
- &kTCPLingerTimeout, sizeof(kTCPLingerTimeout)),
- SyscallSucceedsWithValue(0));
-
- sockaddr_storage conn_addr = connector.addr;
- ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
- ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), AsSockAddr(&conn_addr),
- connector.addr_len),
- SyscallSucceeds());
-
- // Accept the connection.
- auto accepted =
- ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr));
-
- // Get the address/port bound by the connecting socket.
- sockaddr_storage conn_bound_addr;
- socklen_t conn_addrlen = connector.addr_len;
- ASSERT_THAT(
- getsockname(conn_fd.get(), AsSockAddr(&conn_bound_addr), &conn_addrlen),
- SyscallSucceeds());
-
- // close the connecting FD to trigger FIN_WAIT2 on the connected fd.
- conn_fd.reset();
-
- // Now bind and connect a new socket.
- const FileDescriptor conn_fd2 = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
-
- // Disable cooperative saves after this point. As a save between the first
- // bind/connect and the second one can cause the linger timeout timer to
- // be restarted causing the final bind/connect to fail.
- DisableSave ds;
-
- ASSERT_THAT(bind(conn_fd2.get(), AsSockAddr(&conn_bound_addr), conn_addrlen),
- SyscallFailsWithErrno(EADDRINUSE));
-
- // Sleep for a little over the linger timeout to reduce flakiness in
- // save/restore tests.
- absl::SleepFor(absl::Seconds(kTCPLingerTimeout + 2));
-
- ds.reset();
-
- ASSERT_THAT(
- RetryEINTR(connect)(conn_fd2.get(), AsSockAddr(&conn_addr), conn_addrlen),
- SyscallSucceeds());
-}
-
-// TCPLinger2TimeoutAfterClose creates a pair of connected sockets
-// then closes one end to trigger FIN_WAIT2 state for the closed endpont.
-// It then sleeps for the TCP_LINGER2 timeout and verifies that bind/
-// connecting the same address succeeds.
-//
-// TCP timers are not S/R today, this can cause this test to be flaky when run
-// under random S/R due to timer being reset on a restore.
-TEST_P(SocketInetLoopbackTest, TCPLinger2TimeoutAfterClose) {
- auto const& param = GetParam();
- TestAddress const& listener = param.listener;
- TestAddress const& connector = param.connector;
-
- // Create the listening socket.
- const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP));
- sockaddr_storage listen_addr = listener.addr;
- ASSERT_THAT(
- bind(listen_fd.get(), AsSockAddr(&listen_addr), listener.addr_len),
- SyscallSucceeds());
- ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds());
-
- // Get the port bound by the listening socket.
- socklen_t addrlen = listener.addr_len;
- ASSERT_THAT(getsockname(listen_fd.get(), AsSockAddr(&listen_addr), &addrlen),
- SyscallSucceeds());
-
- uint16_t const port =
- ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
-
- // Connect to the listening socket.
- FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
-
- sockaddr_storage conn_addr = connector.addr;
- ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
- ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), AsSockAddr(&conn_addr),
- connector.addr_len),
- SyscallSucceeds());
-
- // Accept the connection.
- auto accepted =
- ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr));
-
- // Get the address/port bound by the connecting socket.
- sockaddr_storage conn_bound_addr;
- socklen_t conn_addrlen = connector.addr_len;
- ASSERT_THAT(
- getsockname(conn_fd.get(), AsSockAddr(&conn_bound_addr), &conn_addrlen),
- SyscallSucceeds());
-
- // Disable cooperative saves after this point as TCP timers are not restored
- // across a S/R.
- {
- DisableSave ds;
- constexpr int kTCPLingerTimeout = 5;
- EXPECT_THAT(setsockopt(conn_fd.get(), IPPROTO_TCP, TCP_LINGER2,
- &kTCPLingerTimeout, sizeof(kTCPLingerTimeout)),
- SyscallSucceedsWithValue(0));
-
- // close the connecting FD to trigger FIN_WAIT2 on the connected fd.
- conn_fd.reset();
-
- absl::SleepFor(absl::Seconds(kTCPLingerTimeout + 1));
-
- // ds going out of scope will Re-enable S/R's since at this point the timer
- // must have fired and cleaned up the endpoint.
- }
-
- // Now bind and connect a new socket and verify that we can immediately
- // rebind the address bound by the conn_fd as it never entered TIME_WAIT.
- const FileDescriptor conn_fd2 = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
-
- ASSERT_THAT(bind(conn_fd2.get(), AsSockAddr(&conn_bound_addr), conn_addrlen),
- SyscallSucceeds());
- ASSERT_THAT(
- RetryEINTR(connect)(conn_fd2.get(), AsSockAddr(&conn_addr), conn_addrlen),
- SyscallSucceeds());
-}
-
// TCPResetAfterClose creates a pair of connected sockets then closes
// one end to trigger FIN_WAIT2 state for the closed endpoint verifies
// that we generate RSTs for any new data after the socket is fully
// closed.
TEST_P(SocketInetLoopbackTest, TCPResetAfterClose) {
- auto const& param = GetParam();
+ SocketInetTestParam const& param = GetParam();
TestAddress const& listener = param.listener;
TestAddress const& connector = param.connector;
@@ -1078,198 +1052,8 @@ TEST_P(SocketInetLoopbackTest, TCPResetAfterClose) {
SyscallSucceedsWithValue(0));
}
-// setupTimeWaitClose sets up a socket endpoint in TIME_WAIT state.
-// Callers can choose to perform active close on either ends of the connection
-// and also specify if they want to enabled SO_REUSEADDR.
-void setupTimeWaitClose(const TestAddress* listener,
- const TestAddress* connector, bool reuse,
- bool accept_close, sockaddr_storage* listen_addr,
- sockaddr_storage* conn_bound_addr) {
- // Create the listening socket.
- FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(listener->family(), SOCK_STREAM, IPPROTO_TCP));
- if (reuse) {
- ASSERT_THAT(setsockopt(listen_fd.get(), SOL_SOCKET, SO_REUSEADDR,
- &kSockOptOn, sizeof(kSockOptOn)),
- SyscallSucceeds());
- }
- ASSERT_THAT(
- bind(listen_fd.get(), AsSockAddr(listen_addr), listener->addr_len),
- SyscallSucceeds());
- ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds());
-
- // Get the port bound by the listening socket.
- socklen_t addrlen = listener->addr_len;
- ASSERT_THAT(getsockname(listen_fd.get(), AsSockAddr(listen_addr), &addrlen),
- SyscallSucceeds());
-
- uint16_t const port =
- ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener->family(), *listen_addr));
-
- // Connect to the listening socket.
- FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(connector->family(), SOCK_STREAM, IPPROTO_TCP));
-
- // We disable saves after this point as a S/R causes the netstack seed
- // to be regenerated which changes what ports/ISN is picked for a given
- // tuple (src ip,src port, dst ip, dst port). This can cause the final
- // SYN to use a sequence number that looks like one from the current
- // connection in TIME_WAIT and will not be accepted causing the test
- // to timeout.
- //
- // TODO(gvisor.dev/issue/940): S/R portSeed/portHint
- DisableSave ds;
-
- sockaddr_storage conn_addr = connector->addr;
- ASSERT_NO_ERRNO(SetAddrPort(connector->family(), &conn_addr, port));
- ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), AsSockAddr(&conn_addr),
- connector->addr_len),
- SyscallSucceeds());
-
- // Accept the connection.
- auto accepted =
- ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr));
-
- // Get the address/port bound by the connecting socket.
- socklen_t conn_addrlen = connector->addr_len;
- ASSERT_THAT(
- getsockname(conn_fd.get(), AsSockAddr(conn_bound_addr), &conn_addrlen),
- SyscallSucceeds());
-
- FileDescriptor active_closefd, passive_closefd;
- if (accept_close) {
- active_closefd = std::move(accepted);
- passive_closefd = std::move(conn_fd);
- } else {
- active_closefd = std::move(conn_fd);
- passive_closefd = std::move(accepted);
- }
-
- // shutdown to trigger TIME_WAIT.
- ASSERT_THAT(shutdown(active_closefd.get(), SHUT_WR), SyscallSucceeds());
- {
- constexpr int kTimeout = 10000;
- pollfd pfd = {
- .fd = passive_closefd.get(),
- .events = POLLIN,
- };
- ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1));
- ASSERT_EQ(pfd.revents, POLLIN);
- }
- ASSERT_THAT(shutdown(passive_closefd.get(), SHUT_WR), SyscallSucceeds());
- {
- constexpr int kTimeout = 10000;
- constexpr int16_t want_events = POLLHUP;
- pollfd pfd = {
- .fd = active_closefd.get(),
- .events = want_events,
- };
- ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1));
- }
-
- // 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));
-}
-
-// These tests are disabled under random save as the the restore run
-// results in the stack.Seed() being different which can cause
-// sequence number of final connect to be one that is considered
-// old and can cause the test to be flaky.
-//
-// Test re-binding of client and server bound addresses when the older
-// connection is in TIME_WAIT.
-TEST_P(SocketInetLoopbackTest, TCPPassiveCloseNoTimeWaitTest) {
- auto const& param = GetParam();
- sockaddr_storage listen_addr, conn_bound_addr;
- listen_addr = param.listener.addr;
- setupTimeWaitClose(&param.listener, &param.connector, false /*reuse*/,
- true /*accept_close*/, &listen_addr, &conn_bound_addr);
-
- // Now bind a new socket and verify that we can immediately rebind the address
- // bound by the conn_fd as it never entered TIME_WAIT.
- const FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP));
- ASSERT_THAT(bind(conn_fd.get(), AsSockAddr(&conn_bound_addr),
- param.connector.addr_len),
- SyscallSucceeds());
-
- FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(param.listener.family(), SOCK_STREAM, IPPROTO_TCP));
- ASSERT_THAT(
- bind(listen_fd.get(), AsSockAddr(&listen_addr), param.listener.addr_len),
- SyscallFailsWithErrno(EADDRINUSE));
-}
-
-TEST_P(SocketInetLoopbackTest, TCPPassiveCloseNoTimeWaitReuseTest) {
- auto const& param = GetParam();
- sockaddr_storage listen_addr, conn_bound_addr;
- listen_addr = param.listener.addr;
- setupTimeWaitClose(&param.listener, &param.connector, true /*reuse*/,
- true /*accept_close*/, &listen_addr, &conn_bound_addr);
-
- FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(param.listener.family(), SOCK_STREAM, IPPROTO_TCP));
- ASSERT_THAT(setsockopt(listen_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
- sizeof(kSockOptOn)),
- SyscallSucceeds());
- ASSERT_THAT(
- bind(listen_fd.get(), AsSockAddr(&listen_addr), param.listener.addr_len),
- SyscallSucceeds());
- ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds());
-
- // Now bind and connect new socket and verify that we can immediately rebind
- // the address bound by the conn_fd as it never entered TIME_WAIT.
- const FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP));
- ASSERT_THAT(setsockopt(conn_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
- sizeof(kSockOptOn)),
- SyscallSucceeds());
- ASSERT_THAT(bind(conn_fd.get(), AsSockAddr(&conn_bound_addr),
- param.connector.addr_len),
- SyscallSucceeds());
-
- uint16_t const port =
- ASSERT_NO_ERRNO_AND_VALUE(AddrPort(param.listener.family(), listen_addr));
- sockaddr_storage conn_addr = param.connector.addr;
- ASSERT_NO_ERRNO(SetAddrPort(param.connector.family(), &conn_addr, port));
- ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), AsSockAddr(&conn_addr),
- param.connector.addr_len),
- SyscallSucceeds());
-}
-
-TEST_P(SocketInetLoopbackTest, TCPActiveCloseTimeWaitTest) {
- auto const& param = GetParam();
- sockaddr_storage listen_addr, conn_bound_addr;
- listen_addr = param.listener.addr;
- setupTimeWaitClose(&param.listener, &param.connector, false /*reuse*/,
- false /*accept_close*/, &listen_addr, &conn_bound_addr);
- FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP));
-
- ASSERT_THAT(bind(conn_fd.get(), AsSockAddr(&conn_bound_addr),
- param.connector.addr_len),
- SyscallFailsWithErrno(EADDRINUSE));
-}
-
-TEST_P(SocketInetLoopbackTest, TCPActiveCloseTimeWaitReuseTest) {
- auto const& param = GetParam();
- sockaddr_storage listen_addr, conn_bound_addr;
- listen_addr = param.listener.addr;
- setupTimeWaitClose(&param.listener, &param.connector, true /*reuse*/,
- false /*accept_close*/, &listen_addr, &conn_bound_addr);
- FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
- Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP));
- ASSERT_THAT(setsockopt(conn_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
- sizeof(kSockOptOn)),
- SyscallSucceeds());
- ASSERT_THAT(bind(conn_fd.get(), AsSockAddr(&conn_bound_addr),
- param.connector.addr_len),
- SyscallFailsWithErrno(EADDRINUSE));
-}
-
TEST_P(SocketInetLoopbackTest, AcceptedInheritsTCPUserTimeout) {
- auto const& param = GetParam();
+ SocketInetTestParam const& param = GetParam();
TestAddress const& listener = param.listener;
TestAddress const& connector = param.connector;
@@ -1321,7 +1105,7 @@ TEST_P(SocketInetLoopbackTest, AcceptedInheritsTCPUserTimeout) {
}
TEST_P(SocketInetLoopbackTest, TCPAcceptAfterReset) {
- auto const& param = GetParam();
+ SocketInetTestParam const& param = GetParam();
TestAddress const& listener = param.listener;
TestAddress const& connector = param.connector;
@@ -1432,7 +1216,7 @@ TEST_P(SocketInetLoopbackTest, TCPDeferAccept) {
// saved. Enable S/R issue is fixed.
DisableSave ds;
- auto const& param = GetParam();
+ SocketInetTestParam const& param = GetParam();
TestAddress const& listener = param.listener;
TestAddress const& connector = param.connector;
@@ -1512,7 +1296,7 @@ TEST_P(SocketInetLoopbackTest, TCPDeferAcceptTimeout) {
// saved. Enable S/R once issue is fixed.
DisableSave ds;
- auto const& param = GetParam();
+ SocketInetTestParam const& param = GetParam();
TestAddress const& listener = param.listener;
TestAddress const& connector = param.connector;
@@ -1579,42 +1363,16 @@ TEST_P(SocketInetLoopbackTest, TCPDeferAcceptTimeout) {
ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr));
}
-INSTANTIATE_TEST_SUITE_P(
- All, SocketInetLoopbackTest,
- ::testing::Values(
- // Listeners bound to IPv4 addresses refuse connections using IPv6
- // addresses.
- TestParam{V4Any(), V4Any()}, TestParam{V4Any(), V4Loopback()},
- TestParam{V4Any(), V4MappedAny()},
- TestParam{V4Any(), V4MappedLoopback()},
- TestParam{V4Loopback(), V4Any()}, TestParam{V4Loopback(), V4Loopback()},
- TestParam{V4Loopback(), V4MappedLoopback()},
- TestParam{V4MappedAny(), V4Any()},
- TestParam{V4MappedAny(), V4Loopback()},
- TestParam{V4MappedAny(), V4MappedAny()},
- TestParam{V4MappedAny(), V4MappedLoopback()},
- TestParam{V4MappedLoopback(), V4Any()},
- TestParam{V4MappedLoopback(), V4Loopback()},
- TestParam{V4MappedLoopback(), V4MappedLoopback()},
-
- // Listeners bound to IN6ADDR_ANY accept all connections.
- TestParam{V6Any(), V4Any()}, TestParam{V6Any(), V4Loopback()},
- TestParam{V6Any(), V4MappedAny()},
- TestParam{V6Any(), V4MappedLoopback()}, TestParam{V6Any(), V6Any()},
- TestParam{V6Any(), V6Loopback()},
-
- // Listeners bound to IN6ADDR_LOOPBACK refuse connections using IPv4
- // addresses.
- TestParam{V6Loopback(), V6Any()},
- TestParam{V6Loopback(), V6Loopback()}),
- DescribeTestParam);
+INSTANTIATE_TEST_SUITE_P(All, SocketInetLoopbackTest,
+ SocketInetLoopbackTestValues(),
+ DescribeSocketInetTestParam);
-using SocketInetReusePortTest = ::testing::TestWithParam<TestParam>;
+using SocketInetReusePortTest = ::testing::TestWithParam<SocketInetTestParam>;
// TODO(gvisor.dev/issue/940): Remove when portHint/stack.Seed is
// saved/restored.
TEST_P(SocketInetReusePortTest, TcpPortReuseMultiThread) {
- auto const& param = GetParam();
+ SocketInetTestParam const& param = GetParam();
TestAddress const& listener = param.listener;
TestAddress const& connector = param.connector;
@@ -1724,7 +1482,7 @@ TEST_P(SocketInetReusePortTest, TcpPortReuseMultiThread) {
}
TEST_P(SocketInetReusePortTest, UdpPortReuseMultiThread) {
- auto const& param = GetParam();
+ SocketInetTestParam const& param = GetParam();
TestAddress const& listener = param.listener;
TestAddress const& connector = param.connector;
@@ -1835,7 +1593,7 @@ TEST_P(SocketInetReusePortTest, UdpPortReuseMultiThread) {
}
TEST_P(SocketInetReusePortTest, UdpPortReuseMultiThreadShort) {
- auto const& param = GetParam();
+ SocketInetTestParam const& param = GetParam();
TestAddress const& listener = param.listener;
TestAddress const& connector = param.connector;
@@ -1943,32 +1701,23 @@ INSTANTIATE_TEST_SUITE_P(
::testing::Values(
// Listeners bound to IPv4 addresses refuse connections using IPv6
// addresses.
- TestParam{V4Any(), V4Loopback()},
- TestParam{V4Loopback(), V4MappedLoopback()},
+ SocketInetTestParam{V4Any(), V4Loopback()},
+ SocketInetTestParam{V4Loopback(), V4MappedLoopback()},
// Listeners bound to IN6ADDR_ANY accept all connections.
- TestParam{V6Any(), V4Loopback()}, TestParam{V6Any(), V6Loopback()},
+ SocketInetTestParam{V6Any(), V4Loopback()},
+ SocketInetTestParam{V6Any(), V6Loopback()},
// Listeners bound to IN6ADDR_LOOPBACK refuse connections using IPv4
// addresses.
- TestParam{V6Loopback(), V6Loopback()}),
- DescribeTestParam);
-
-struct ProtocolTestParam {
- std::string description;
- int type;
-};
-
-std::string DescribeProtocolTestParam(
- ::testing::TestParamInfo<ProtocolTestParam> const& info) {
- return info.param.description;
-}
+ SocketInetTestParam{V6Loopback(), V6Loopback()}),
+ DescribeSocketInetTestParam);
using SocketMultiProtocolInetLoopbackTest =
::testing::TestWithParam<ProtocolTestParam>;
TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedLoopbackOnlyReservesV4) {
- auto const& param = GetParam();
+ ProtocolTestParam const& param = GetParam();
for (int i = 0; true; i++) {
// Bind the v4 loopback on a dual stack socket.
@@ -2017,7 +1766,7 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedLoopbackOnlyReservesV4) {
}
TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedAnyOnlyReservesV4) {
- auto const& param = GetParam();
+ ProtocolTestParam const& param = GetParam();
for (int i = 0; true; i++) {
// Bind the v4 any on a dual stack socket.
@@ -2066,7 +1815,7 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedAnyOnlyReservesV4) {
}
TEST_P(SocketMultiProtocolInetLoopbackTest, DualStackV6AnyReservesEverything) {
- auto const& param = GetParam();
+ ProtocolTestParam const& param = GetParam();
// Bind the v6 any on a dual stack socket.
TestAddress const& test_addr_dual = V6Any();
@@ -2129,7 +1878,7 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, DualStackV6AnyReservesEverything) {
TEST_P(SocketMultiProtocolInetLoopbackTest,
DualStackV6AnyReuseAddrDoesNotReserveV4Any) {
- auto const& param = GetParam();
+ ProtocolTestParam const& param = GetParam();
// Bind the v6 any on a dual stack socket.
TestAddress const& test_addr_dual = V6Any();
@@ -2166,7 +1915,7 @@ TEST_P(SocketMultiProtocolInetLoopbackTest,
TEST_P(SocketMultiProtocolInetLoopbackTest,
DualStackV6AnyReuseAddrListenReservesV4Any) {
- auto const& param = GetParam();
+ ProtocolTestParam const& param = GetParam();
// Only TCP sockets are supported.
SKIP_IF((param.type & SOCK_STREAM) == 0);
@@ -2209,7 +1958,7 @@ TEST_P(SocketMultiProtocolInetLoopbackTest,
TEST_P(SocketMultiProtocolInetLoopbackTest,
DualStackV6AnyWithListenReservesEverything) {
- auto const& param = GetParam();
+ ProtocolTestParam const& param = GetParam();
// Only TCP sockets are supported.
SKIP_IF((param.type & SOCK_STREAM) == 0);
@@ -2276,7 +2025,7 @@ TEST_P(SocketMultiProtocolInetLoopbackTest,
}
TEST_P(SocketMultiProtocolInetLoopbackTest, V6OnlyV6AnyReservesV6) {
- auto const& param = GetParam();
+ ProtocolTestParam const& param = GetParam();
for (int i = 0; true; i++) {
// Bind the v6 any on a v6-only socket.
@@ -2329,7 +2078,7 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, V6OnlyV6AnyReservesV6) {
}
TEST_P(SocketMultiProtocolInetLoopbackTest, V6EphemeralPortReserved) {
- auto const& param = GetParam();
+ ProtocolTestParam const& param = GetParam();
for (int i = 0; true; i++) {
// Bind the v6 loopback on a dual stack socket.
@@ -2409,66 +2158,8 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, V6EphemeralPortReserved) {
}
}
-TEST_P(SocketMultiProtocolInetLoopbackTest, V6EphemeralPortReservedReuseAddr) {
- auto const& param = GetParam();
-
- // Bind the v6 loopback on a dual stack socket.
- TestAddress const& test_addr = V6Loopback();
- sockaddr_storage bound_addr = test_addr.addr;
- const FileDescriptor bound_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- ASSERT_THAT(bind(bound_fd.get(), AsSockAddr(&bound_addr), test_addr.addr_len),
- SyscallSucceeds());
- ASSERT_THAT(setsockopt(bound_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
- sizeof(kSockOptOn)),
- SyscallSucceeds());
-
- // Listen iff TCP.
- if (param.type == SOCK_STREAM) {
- ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds());
- }
-
- // Get the port that we bound.
- socklen_t bound_addr_len = test_addr.addr_len;
- ASSERT_THAT(
- getsockname(bound_fd.get(), AsSockAddr(&bound_addr), &bound_addr_len),
- SyscallSucceeds());
-
- // Connect to bind an ephemeral port.
- const FileDescriptor connected_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- ASSERT_THAT(setsockopt(connected_fd.get(), SOL_SOCKET, SO_REUSEADDR,
- &kSockOptOn, sizeof(kSockOptOn)),
- SyscallSucceeds());
- ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(), AsSockAddr(&bound_addr),
- bound_addr_len),
- SyscallSucceeds());
-
- // Get the ephemeral port.
- sockaddr_storage connected_addr = {};
- socklen_t connected_addr_len = sizeof(connected_addr);
- ASSERT_THAT(getsockname(connected_fd.get(), AsSockAddr(&connected_addr),
- &connected_addr_len),
- SyscallSucceeds());
- uint16_t const ephemeral_port =
- ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr));
-
- // Verify that we actually got an ephemeral port.
- ASSERT_NE(ephemeral_port, 0);
-
- // Verify that the ephemeral port is not reserved.
- const FileDescriptor checking_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- ASSERT_THAT(setsockopt(checking_fd.get(), SOL_SOCKET, SO_REUSEADDR,
- &kSockOptOn, sizeof(kSockOptOn)),
- SyscallSucceeds());
- EXPECT_THAT(
- bind(checking_fd.get(), AsSockAddr(&connected_addr), connected_addr_len),
- SyscallSucceeds());
-}
-
TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedEphemeralPortReserved) {
- auto const& param = GetParam();
+ ProtocolTestParam const& param = GetParam();
for (int i = 0; true; i++) {
// Bind the v4 loopback on a dual stack socket.
@@ -2580,68 +2271,8 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedEphemeralPortReserved) {
}
}
-TEST_P(SocketMultiProtocolInetLoopbackTest,
- V4MappedEphemeralPortReservedResueAddr) {
- auto const& param = GetParam();
-
- // Bind the v4 loopback on a dual stack socket.
- TestAddress const& test_addr = V4MappedLoopback();
- sockaddr_storage bound_addr = test_addr.addr;
- const FileDescriptor bound_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- ASSERT_THAT(bind(bound_fd.get(), AsSockAddr(&bound_addr), test_addr.addr_len),
- SyscallSucceeds());
-
- ASSERT_THAT(setsockopt(bound_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
- sizeof(kSockOptOn)),
- SyscallSucceeds());
-
- // Listen iff TCP.
- if (param.type == SOCK_STREAM) {
- ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds());
- }
-
- // Get the port that we bound.
- socklen_t bound_addr_len = test_addr.addr_len;
- ASSERT_THAT(
- getsockname(bound_fd.get(), AsSockAddr(&bound_addr), &bound_addr_len),
- SyscallSucceeds());
-
- // Connect to bind an ephemeral port.
- const FileDescriptor connected_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- ASSERT_THAT(setsockopt(connected_fd.get(), SOL_SOCKET, SO_REUSEADDR,
- &kSockOptOn, sizeof(kSockOptOn)),
- SyscallSucceeds());
- ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(), AsSockAddr(&bound_addr),
- bound_addr_len),
- SyscallSucceeds());
-
- // Get the ephemeral port.
- sockaddr_storage connected_addr = {};
- socklen_t connected_addr_len = sizeof(connected_addr);
- ASSERT_THAT(getsockname(connected_fd.get(), AsSockAddr(&connected_addr),
- &connected_addr_len),
- SyscallSucceeds());
- uint16_t const ephemeral_port =
- ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr));
-
- // Verify that we actually got an ephemeral port.
- ASSERT_NE(ephemeral_port, 0);
-
- // Verify that the ephemeral port is not reserved.
- const FileDescriptor checking_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- ASSERT_THAT(setsockopt(checking_fd.get(), SOL_SOCKET, SO_REUSEADDR,
- &kSockOptOn, sizeof(kSockOptOn)),
- SyscallSucceeds());
- EXPECT_THAT(
- bind(checking_fd.get(), AsSockAddr(&connected_addr), connected_addr_len),
- SyscallSucceeds());
-}
-
TEST_P(SocketMultiProtocolInetLoopbackTest, V4EphemeralPortReserved) {
- auto const& param = GetParam();
+ ProtocolTestParam const& param = GetParam();
for (int i = 0; true; i++) {
// Bind the v4 loopback on a v4 socket.
@@ -2754,71 +2385,9 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, V4EphemeralPortReserved) {
}
}
-TEST_P(SocketMultiProtocolInetLoopbackTest, V4EphemeralPortReservedReuseAddr) {
- auto const& param = GetParam();
-
- // Bind the v4 loopback on a v4 socket.
- TestAddress const& test_addr = V4Loopback();
- sockaddr_storage bound_addr = test_addr.addr;
- const FileDescriptor bound_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
-
- ASSERT_THAT(setsockopt(bound_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
- sizeof(kSockOptOn)),
- SyscallSucceeds());
-
- ASSERT_THAT(bind(bound_fd.get(), AsSockAddr(&bound_addr), test_addr.addr_len),
- SyscallSucceeds());
-
- // Listen iff TCP.
- if (param.type == SOCK_STREAM) {
- ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds());
- }
-
- // Get the port that we bound.
- socklen_t bound_addr_len = test_addr.addr_len;
- ASSERT_THAT(
- getsockname(bound_fd.get(), AsSockAddr(&bound_addr), &bound_addr_len),
- SyscallSucceeds());
-
- // Connect to bind an ephemeral port.
- const FileDescriptor connected_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
-
- ASSERT_THAT(setsockopt(connected_fd.get(), SOL_SOCKET, SO_REUSEADDR,
- &kSockOptOn, sizeof(kSockOptOn)),
- SyscallSucceeds());
-
- ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(), AsSockAddr(&bound_addr),
- bound_addr_len),
- SyscallSucceeds());
-
- // Get the ephemeral port.
- sockaddr_storage connected_addr = {};
- socklen_t connected_addr_len = sizeof(connected_addr);
- ASSERT_THAT(getsockname(connected_fd.get(), AsSockAddr(&connected_addr),
- &connected_addr_len),
- SyscallSucceeds());
- uint16_t const ephemeral_port =
- ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr));
-
- // Verify that we actually got an ephemeral port.
- ASSERT_NE(ephemeral_port, 0);
-
- // Verify that the ephemeral port is not reserved.
- const FileDescriptor checking_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- ASSERT_THAT(setsockopt(checking_fd.get(), SOL_SOCKET, SO_REUSEADDR,
- &kSockOptOn, sizeof(kSockOptOn)),
- SyscallSucceeds());
- EXPECT_THAT(
- bind(checking_fd.get(), AsSockAddr(&connected_addr), connected_addr_len),
- SyscallSucceeds());
-}
-
TEST_P(SocketMultiProtocolInetLoopbackTest,
MultipleBindsAllowedNoListeningReuseAddr) {
- const auto& param = GetParam();
+ ProtocolTestParam const& param = GetParam();
// UDP sockets are allowed to bind/listen on the port w/ SO_REUSEADDR, for TCP
// this is only permitted if there is no other listening socket.
SKIP_IF(param.type != SOCK_STREAM);
@@ -2853,7 +2422,7 @@ TEST_P(SocketMultiProtocolInetLoopbackTest,
}
TEST_P(SocketMultiProtocolInetLoopbackTest, PortReuseTwoSockets) {
- auto const& param = GetParam();
+ ProtocolTestParam const& param = GetParam();
TestAddress const& test_addr = V4Loopback();
sockaddr_storage addr = test_addr.addr;
@@ -2906,7 +2475,7 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, PortReuseTwoSockets) {
// closed, we can bind a different socket to the same address without needing
// REUSEPORT.
TEST_P(SocketMultiProtocolInetLoopbackTest, NoReusePortFollowingReusePort) {
- auto const& param = GetParam();
+ ProtocolTestParam const& param = GetParam();
TestAddress const& test_addr = V4Loopback();
sockaddr_storage addr = test_addr.addr;
@@ -2933,11 +2502,8 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, NoReusePortFollowingReusePort) {
ASSERT_THAT(bind(fd, AsSockAddr(&addr), addrlen), SyscallSucceeds());
}
-INSTANTIATE_TEST_SUITE_P(
- AllFamilies, SocketMultiProtocolInetLoopbackTest,
- ::testing::Values(ProtocolTestParam{"TCP", SOCK_STREAM},
- ProtocolTestParam{"UDP", SOCK_DGRAM}),
- DescribeProtocolTestParam);
+INSTANTIATE_TEST_SUITE_P(AllFamilies, SocketMultiProtocolInetLoopbackTest,
+ ProtocolTestValues(), DescribeProtocolTestParam);
} // namespace
diff --git a/test/syscalls/linux/socket_inet_loopback_isolated.cc b/test/syscalls/linux/socket_inet_loopback_isolated.cc
new file mode 100644
index 000000000..ab2259b55
--- /dev/null
+++ b/test/syscalls/linux/socket_inet_loopback_isolated.cc
@@ -0,0 +1,489 @@
+// Copyright 2018 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <netinet/tcp.h>
+
+#include "gtest/gtest.h"
+#include "absl/time/clock.h"
+#include "absl/time/time.h"
+#include "test/syscalls/linux/socket_inet_loopback_test_params.h"
+#include "test/syscalls/linux/socket_test_util.h"
+#include "test/util/test_util.h"
+
+// Unit tests in this file will run in their own network namespace.
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+using SocketInetLoopbackIsolatedTest =
+ ::testing::TestWithParam<SocketInetTestParam>;
+
+TEST_P(SocketInetLoopbackIsolatedTest, TCPActiveCloseTimeWaitTest) {
+ SocketInetTestParam const& param = GetParam();
+ sockaddr_storage listen_addr, conn_bound_addr;
+ listen_addr = param.listener.addr;
+ SetupTimeWaitClose(&param.listener, &param.connector, false /*reuse*/,
+ false /*accept_close*/, &listen_addr, &conn_bound_addr);
+ FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP));
+
+ ASSERT_THAT(bind(conn_fd.get(), AsSockAddr(&conn_bound_addr),
+ param.connector.addr_len),
+ SyscallFailsWithErrno(EADDRINUSE));
+}
+
+TEST_P(SocketInetLoopbackIsolatedTest, TCPActiveCloseTimeWaitReuseTest) {
+ SocketInetTestParam const& param = GetParam();
+ sockaddr_storage listen_addr, conn_bound_addr;
+ listen_addr = param.listener.addr;
+ SetupTimeWaitClose(&param.listener, &param.connector, true /*reuse*/,
+ false /*accept_close*/, &listen_addr, &conn_bound_addr);
+ FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP));
+ ASSERT_THAT(setsockopt(conn_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
+ sizeof(kSockOptOn)),
+ SyscallSucceeds());
+ ASSERT_THAT(bind(conn_fd.get(), AsSockAddr(&conn_bound_addr),
+ param.connector.addr_len),
+ SyscallFailsWithErrno(EADDRINUSE));
+}
+
+// These tests are disabled under random save as the restore run
+// results in the stack.Seed() being different which can cause
+// sequence number of final connect to be one that is considered
+// old and can cause the test to be flaky.
+//
+// Test re-binding of client and server bound addresses when the older
+// connection is in TIME_WAIT.
+TEST_P(SocketInetLoopbackIsolatedTest, TCPPassiveCloseNoTimeWaitTest) {
+ SocketInetTestParam const& param = GetParam();
+ sockaddr_storage listen_addr, conn_bound_addr;
+ listen_addr = param.listener.addr;
+ SetupTimeWaitClose(&param.listener, &param.connector, false /*reuse*/,
+ true /*accept_close*/, &listen_addr, &conn_bound_addr);
+
+ // Now bind a new socket and verify that we can immediately rebind the address
+ // bound by the conn_fd as it never entered TIME_WAIT.
+ const FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP));
+ ASSERT_THAT(bind(conn_fd.get(), AsSockAddr(&conn_bound_addr),
+ param.connector.addr_len),
+ SyscallSucceeds());
+
+ FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(param.listener.family(), SOCK_STREAM, IPPROTO_TCP));
+ ASSERT_THAT(
+ bind(listen_fd.get(), AsSockAddr(&listen_addr), param.listener.addr_len),
+ SyscallFailsWithErrno(EADDRINUSE));
+}
+
+TEST_P(SocketInetLoopbackIsolatedTest, TCPPassiveCloseNoTimeWaitReuseTest) {
+ SocketInetTestParam const& param = GetParam();
+ sockaddr_storage listen_addr, conn_bound_addr;
+ listen_addr = param.listener.addr;
+ SetupTimeWaitClose(&param.listener, &param.connector, true /*reuse*/,
+ true /*accept_close*/, &listen_addr, &conn_bound_addr);
+
+ FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(param.listener.family(), SOCK_STREAM, IPPROTO_TCP));
+ ASSERT_THAT(setsockopt(listen_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
+ sizeof(kSockOptOn)),
+ SyscallSucceeds());
+ ASSERT_THAT(
+ bind(listen_fd.get(), AsSockAddr(&listen_addr), param.listener.addr_len),
+ SyscallSucceeds());
+ ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds());
+
+ // Now bind and connect new socket and verify that we can immediately rebind
+ // the address bound by the conn_fd as it never entered TIME_WAIT.
+ const FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(param.connector.family(), SOCK_STREAM, IPPROTO_TCP));
+ ASSERT_THAT(setsockopt(conn_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
+ sizeof(kSockOptOn)),
+ SyscallSucceeds());
+ ASSERT_THAT(bind(conn_fd.get(), AsSockAddr(&conn_bound_addr),
+ param.connector.addr_len),
+ SyscallSucceeds());
+
+ uint16_t const port =
+ ASSERT_NO_ERRNO_AND_VALUE(AddrPort(param.listener.family(), listen_addr));
+ sockaddr_storage conn_addr = param.connector.addr;
+ ASSERT_NO_ERRNO(SetAddrPort(param.connector.family(), &conn_addr, port));
+ ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), AsSockAddr(&conn_addr),
+ param.connector.addr_len),
+ SyscallSucceeds());
+}
+
+// TCPFinWait2Test creates a pair of connected sockets then closes one end to
+// trigger FIN_WAIT2 state for the closed endpoint. Then it binds the same local
+// IP/port on a new socket and tries to connect. The connect should fail w/
+// an EADDRINUSE. Then we wait till the FIN_WAIT2 timeout is over and try the
+// connect again with a new socket and this time it should succeed.
+//
+// TCP timers are not S/R today, this can cause this test to be flaky when run
+// under random S/R due to timer being reset on a restore.
+TEST_P(SocketInetLoopbackIsolatedTest, TCPFinWait2Test) {
+ SocketInetTestParam const& param = GetParam();
+ TestAddress const& listener = param.listener;
+ TestAddress const& connector = param.connector;
+
+ // Create the listening socket.
+ const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP));
+ sockaddr_storage listen_addr = listener.addr;
+ ASSERT_THAT(
+ bind(listen_fd.get(), AsSockAddr(&listen_addr), listener.addr_len),
+ SyscallSucceeds());
+ ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds());
+
+ // Get the port bound by the listening socket.
+ socklen_t addrlen = listener.addr_len;
+ ASSERT_THAT(getsockname(listen_fd.get(), AsSockAddr(&listen_addr), &addrlen),
+ SyscallSucceeds());
+
+ uint16_t const port =
+ ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
+
+ // Connect to the listening socket.
+ FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
+
+ // Lower FIN_WAIT2 state to 5 seconds for test.
+ constexpr int kTCPLingerTimeout = 5;
+ EXPECT_THAT(setsockopt(conn_fd.get(), IPPROTO_TCP, TCP_LINGER2,
+ &kTCPLingerTimeout, sizeof(kTCPLingerTimeout)),
+ SyscallSucceedsWithValue(0));
+
+ sockaddr_storage conn_addr = connector.addr;
+ ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
+ ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), AsSockAddr(&conn_addr),
+ connector.addr_len),
+ SyscallSucceeds());
+
+ // Accept the connection.
+ auto accepted =
+ ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr));
+
+ // Get the address/port bound by the connecting socket.
+ sockaddr_storage conn_bound_addr;
+ socklen_t conn_addrlen = connector.addr_len;
+ ASSERT_THAT(
+ getsockname(conn_fd.get(), AsSockAddr(&conn_bound_addr), &conn_addrlen),
+ SyscallSucceeds());
+
+ // close the connecting FD to trigger FIN_WAIT2 on the connected fd.
+ conn_fd.reset();
+
+ // Now bind and connect a new socket.
+ const FileDescriptor conn_fd2 = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
+
+ // Disable cooperative saves after this point. As a save between the first
+ // bind/connect and the second one can cause the linger timeout timer to
+ // be restarted causing the final bind/connect to fail.
+ DisableSave ds;
+
+ ASSERT_THAT(bind(conn_fd2.get(), AsSockAddr(&conn_bound_addr), conn_addrlen),
+ SyscallFailsWithErrno(EADDRINUSE));
+
+ // Sleep for a little over the linger timeout to reduce flakiness in
+ // save/restore tests.
+ absl::SleepFor(absl::Seconds(kTCPLingerTimeout + 2));
+
+ ds.reset();
+
+ ASSERT_THAT(
+ RetryEINTR(connect)(conn_fd2.get(), AsSockAddr(&conn_addr), conn_addrlen),
+ SyscallSucceeds());
+}
+
+// TCPLinger2TimeoutAfterClose creates a pair of connected sockets
+// then closes one end to trigger FIN_WAIT2 state for the closed endpoint.
+// It then sleeps for the TCP_LINGER2 timeout and verifies that bind/
+// connecting the same address succeeds.
+//
+// TCP timers are not S/R today, this can cause this test to be flaky when run
+// under random S/R due to timer being reset on a restore.
+TEST_P(SocketInetLoopbackIsolatedTest, TCPLinger2TimeoutAfterClose) {
+ SocketInetTestParam const& param = GetParam();
+ TestAddress const& listener = param.listener;
+ TestAddress const& connector = param.connector;
+
+ // Create the listening socket.
+ const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP));
+ sockaddr_storage listen_addr = listener.addr;
+ ASSERT_THAT(
+ bind(listen_fd.get(), AsSockAddr(&listen_addr), listener.addr_len),
+ SyscallSucceeds());
+ ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds());
+
+ // Get the port bound by the listening socket.
+ socklen_t addrlen = listener.addr_len;
+ ASSERT_THAT(getsockname(listen_fd.get(), AsSockAddr(&listen_addr), &addrlen),
+ SyscallSucceeds());
+
+ uint16_t const port =
+ ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
+
+ // Connect to the listening socket.
+ FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
+
+ sockaddr_storage conn_addr = connector.addr;
+ ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
+ ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), AsSockAddr(&conn_addr),
+ connector.addr_len),
+ SyscallSucceeds());
+
+ // Accept the connection.
+ auto accepted =
+ ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr));
+
+ // Get the address/port bound by the connecting socket.
+ sockaddr_storage conn_bound_addr;
+ socklen_t conn_addrlen = connector.addr_len;
+ ASSERT_THAT(
+ getsockname(conn_fd.get(), AsSockAddr(&conn_bound_addr), &conn_addrlen),
+ SyscallSucceeds());
+
+ // Disable cooperative saves after this point as TCP timers are not restored
+ // across a S/R.
+ {
+ DisableSave ds;
+ constexpr int kTCPLingerTimeout = 5;
+ EXPECT_THAT(setsockopt(conn_fd.get(), IPPROTO_TCP, TCP_LINGER2,
+ &kTCPLingerTimeout, sizeof(kTCPLingerTimeout)),
+ SyscallSucceedsWithValue(0));
+
+ // close the connecting FD to trigger FIN_WAIT2 on the connected fd.
+ conn_fd.reset();
+
+ absl::SleepFor(absl::Seconds(kTCPLingerTimeout + 1));
+
+ // ds going out of scope will Re-enable S/R's since at this point the timer
+ // must have fired and cleaned up the endpoint.
+ }
+
+ // Now bind and connect a new socket and verify that we can immediately
+ // rebind the address bound by the conn_fd as it never entered TIME_WAIT.
+ const FileDescriptor conn_fd2 = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
+
+ ASSERT_THAT(bind(conn_fd2.get(), AsSockAddr(&conn_bound_addr), conn_addrlen),
+ SyscallSucceeds());
+ ASSERT_THAT(
+ RetryEINTR(connect)(conn_fd2.get(), AsSockAddr(&conn_addr), conn_addrlen),
+ SyscallSucceeds());
+}
+
+INSTANTIATE_TEST_SUITE_P(All, SocketInetLoopbackIsolatedTest,
+ SocketInetLoopbackTestValues(),
+ DescribeSocketInetTestParam);
+
+using SocketMultiProtocolInetLoopbackIsolatedTest =
+ ::testing::TestWithParam<ProtocolTestParam>;
+
+TEST_P(SocketMultiProtocolInetLoopbackIsolatedTest,
+ V4EphemeralPortReservedReuseAddr) {
+ ProtocolTestParam const& param = GetParam();
+
+ // Bind the v4 loopback on a v4 socket.
+ TestAddress const& test_addr = V4Loopback();
+ sockaddr_storage bound_addr = test_addr.addr;
+ const FileDescriptor bound_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
+
+ ASSERT_THAT(setsockopt(bound_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
+ sizeof(kSockOptOn)),
+ SyscallSucceeds());
+
+ ASSERT_THAT(bind(bound_fd.get(), AsSockAddr(&bound_addr), test_addr.addr_len),
+ SyscallSucceeds());
+
+ // Listen iff TCP.
+ if (param.type == SOCK_STREAM) {
+ ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds());
+ }
+
+ // Get the port that we bound.
+ socklen_t bound_addr_len = test_addr.addr_len;
+ ASSERT_THAT(
+ getsockname(bound_fd.get(), AsSockAddr(&bound_addr), &bound_addr_len),
+ SyscallSucceeds());
+
+ // Connect to bind an ephemeral port.
+ const FileDescriptor connected_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
+
+ ASSERT_THAT(setsockopt(connected_fd.get(), SOL_SOCKET, SO_REUSEADDR,
+ &kSockOptOn, sizeof(kSockOptOn)),
+ SyscallSucceeds());
+
+ ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(), AsSockAddr(&bound_addr),
+ bound_addr_len),
+ SyscallSucceeds());
+
+ // Get the ephemeral port.
+ sockaddr_storage connected_addr = {};
+ socklen_t connected_addr_len = sizeof(connected_addr);
+ ASSERT_THAT(getsockname(connected_fd.get(), AsSockAddr(&connected_addr),
+ &connected_addr_len),
+ SyscallSucceeds());
+ uint16_t const ephemeral_port =
+ ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr));
+
+ // Verify that we actually got an ephemeral port.
+ ASSERT_NE(ephemeral_port, 0);
+
+ // Verify that the ephemeral port is not reserved.
+ const FileDescriptor checking_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
+ ASSERT_THAT(setsockopt(checking_fd.get(), SOL_SOCKET, SO_REUSEADDR,
+ &kSockOptOn, sizeof(kSockOptOn)),
+ SyscallSucceeds());
+ EXPECT_THAT(
+ bind(checking_fd.get(), AsSockAddr(&connected_addr), connected_addr_len),
+ SyscallSucceeds());
+}
+
+TEST_P(SocketMultiProtocolInetLoopbackIsolatedTest,
+ V4MappedEphemeralPortReservedReuseAddr) {
+ ProtocolTestParam const& param = GetParam();
+
+ // Bind the v4 loopback on a dual stack socket.
+ TestAddress const& test_addr = V4MappedLoopback();
+ sockaddr_storage bound_addr = test_addr.addr;
+ const FileDescriptor bound_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
+ ASSERT_THAT(bind(bound_fd.get(), AsSockAddr(&bound_addr), test_addr.addr_len),
+ SyscallSucceeds());
+
+ ASSERT_THAT(setsockopt(bound_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
+ sizeof(kSockOptOn)),
+ SyscallSucceeds());
+
+ // Listen iff TCP.
+ if (param.type == SOCK_STREAM) {
+ ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds());
+ }
+
+ // Get the port that we bound.
+ socklen_t bound_addr_len = test_addr.addr_len;
+ ASSERT_THAT(
+ getsockname(bound_fd.get(), AsSockAddr(&bound_addr), &bound_addr_len),
+ SyscallSucceeds());
+
+ // Connect to bind an ephemeral port.
+ const FileDescriptor connected_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
+ ASSERT_THAT(setsockopt(connected_fd.get(), SOL_SOCKET, SO_REUSEADDR,
+ &kSockOptOn, sizeof(kSockOptOn)),
+ SyscallSucceeds());
+ ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(), AsSockAddr(&bound_addr),
+ bound_addr_len),
+ SyscallSucceeds());
+
+ // Get the ephemeral port.
+ sockaddr_storage connected_addr = {};
+ socklen_t connected_addr_len = sizeof(connected_addr);
+ ASSERT_THAT(getsockname(connected_fd.get(), AsSockAddr(&connected_addr),
+ &connected_addr_len),
+ SyscallSucceeds());
+ uint16_t const ephemeral_port =
+ ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr));
+
+ // Verify that we actually got an ephemeral port.
+ ASSERT_NE(ephemeral_port, 0);
+
+ // Verify that the ephemeral port is not reserved.
+ const FileDescriptor checking_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
+ ASSERT_THAT(setsockopt(checking_fd.get(), SOL_SOCKET, SO_REUSEADDR,
+ &kSockOptOn, sizeof(kSockOptOn)),
+ SyscallSucceeds());
+ EXPECT_THAT(
+ bind(checking_fd.get(), AsSockAddr(&connected_addr), connected_addr_len),
+ SyscallSucceeds());
+}
+
+TEST_P(SocketMultiProtocolInetLoopbackIsolatedTest,
+ V6EphemeralPortReservedReuseAddr) {
+ ProtocolTestParam const& param = GetParam();
+
+ // Bind the v6 loopback on a dual stack socket.
+ TestAddress const& test_addr = V6Loopback();
+ sockaddr_storage bound_addr = test_addr.addr;
+ const FileDescriptor bound_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
+ ASSERT_THAT(bind(bound_fd.get(), AsSockAddr(&bound_addr), test_addr.addr_len),
+ SyscallSucceeds());
+ ASSERT_THAT(setsockopt(bound_fd.get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
+ sizeof(kSockOptOn)),
+ SyscallSucceeds());
+
+ // Listen iff TCP.
+ if (param.type == SOCK_STREAM) {
+ ASSERT_THAT(listen(bound_fd.get(), SOMAXCONN), SyscallSucceeds());
+ }
+
+ // Get the port that we bound.
+ socklen_t bound_addr_len = test_addr.addr_len;
+ ASSERT_THAT(
+ getsockname(bound_fd.get(), AsSockAddr(&bound_addr), &bound_addr_len),
+ SyscallSucceeds());
+
+ // Connect to bind an ephemeral port.
+ const FileDescriptor connected_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
+ ASSERT_THAT(setsockopt(connected_fd.get(), SOL_SOCKET, SO_REUSEADDR,
+ &kSockOptOn, sizeof(kSockOptOn)),
+ SyscallSucceeds());
+ ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(), AsSockAddr(&bound_addr),
+ bound_addr_len),
+ SyscallSucceeds());
+
+ // Get the ephemeral port.
+ sockaddr_storage connected_addr = {};
+ socklen_t connected_addr_len = sizeof(connected_addr);
+ ASSERT_THAT(getsockname(connected_fd.get(), AsSockAddr(&connected_addr),
+ &connected_addr_len),
+ SyscallSucceeds());
+ uint16_t const ephemeral_port =
+ ASSERT_NO_ERRNO_AND_VALUE(AddrPort(test_addr.family(), connected_addr));
+
+ // Verify that we actually got an ephemeral port.
+ ASSERT_NE(ephemeral_port, 0);
+
+ // Verify that the ephemeral port is not reserved.
+ const FileDescriptor checking_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
+ ASSERT_THAT(setsockopt(checking_fd.get(), SOL_SOCKET, SO_REUSEADDR,
+ &kSockOptOn, sizeof(kSockOptOn)),
+ SyscallSucceeds());
+ EXPECT_THAT(
+ bind(checking_fd.get(), AsSockAddr(&connected_addr), connected_addr_len),
+ SyscallSucceeds());
+}
+
+INSTANTIATE_TEST_SUITE_P(AllFamilies,
+ SocketMultiProtocolInetLoopbackIsolatedTest,
+ ProtocolTestValues(), DescribeProtocolTestParam);
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/syscalls/linux/socket_inet_loopback_nogotsan.cc b/test/syscalls/linux/socket_inet_loopback_nogotsan.cc
index 601ae107b..b131213d4 100644
--- a/test/syscalls/linux/socket_inet_loopback_nogotsan.cc
+++ b/test/syscalls/linux/socket_inet_loopback_nogotsan.cc
@@ -27,6 +27,7 @@
#include "gtest/gtest.h"
#include "absl/strings/str_cat.h"
#include "test/syscalls/linux/ip_socket_test_util.h"
+#include "test/syscalls/linux/socket_inet_loopback_test_params.h"
#include "test/syscalls/linux/socket_test_util.h"
#include "test/util/file_descriptor.h"
#include "test/util/posix_error.h"
@@ -38,47 +39,7 @@ namespace testing {
namespace {
-using ::testing::Gt;
-
-PosixErrorOr<uint16_t> AddrPort(int family, sockaddr_storage const& addr) {
- switch (family) {
- case AF_INET:
- return static_cast<uint16_t>(
- reinterpret_cast<sockaddr_in const*>(&addr)->sin_port);
- case AF_INET6:
- return static_cast<uint16_t>(
- reinterpret_cast<sockaddr_in6 const*>(&addr)->sin6_port);
- default:
- return PosixError(EINVAL,
- absl::StrCat("unknown socket family: ", family));
- }
-}
-
-PosixError SetAddrPort(int family, sockaddr_storage* addr, uint16_t port) {
- switch (family) {
- case AF_INET:
- reinterpret_cast<sockaddr_in*>(addr)->sin_port = port;
- return NoError();
- case AF_INET6:
- reinterpret_cast<sockaddr_in6*>(addr)->sin6_port = port;
- return NoError();
- default:
- return PosixError(EINVAL,
- absl::StrCat("unknown socket family: ", family));
- }
-}
-
-struct TestParam {
- TestAddress listener;
- TestAddress connector;
-};
-
-std::string DescribeTestParam(::testing::TestParamInfo<TestParam> const& info) {
- return absl::StrCat("Listen", info.param.listener.description, "_Connect",
- info.param.connector.description);
-}
-
-using SocketInetLoopbackTest = ::testing::TestWithParam<TestParam>;
+using SocketInetLoopbackTest = ::testing::TestWithParam<SocketInetTestParam>;
// This test verifies that connect returns EADDRNOTAVAIL if all local ephemeral
// ports are already in use for a given destination ip/port.
@@ -87,7 +48,7 @@ using SocketInetLoopbackTest = ::testing::TestWithParam<TestParam>;
//
// FIXME(b/162475855): This test is failing reliably.
TEST_P(SocketInetLoopbackTest, DISABLED_TestTCPPortExhaustion) {
- auto const& param = GetParam();
+ SocketInetTestParam const& param = GetParam();
TestAddress const& listener = param.listener;
TestAddress const& connector = param.connector;
@@ -136,51 +97,15 @@ TEST_P(SocketInetLoopbackTest, DISABLED_TestTCPPortExhaustion) {
}
}
-INSTANTIATE_TEST_SUITE_P(
- All, SocketInetLoopbackTest,
- ::testing::Values(
- // Listeners bound to IPv4 addresses refuse connections using IPv6
- // addresses.
- TestParam{V4Any(), V4Any()}, TestParam{V4Any(), V4Loopback()},
- TestParam{V4Any(), V4MappedAny()},
- TestParam{V4Any(), V4MappedLoopback()},
- TestParam{V4Loopback(), V4Any()}, TestParam{V4Loopback(), V4Loopback()},
- TestParam{V4Loopback(), V4MappedLoopback()},
- TestParam{V4MappedAny(), V4Any()},
- TestParam{V4MappedAny(), V4Loopback()},
- TestParam{V4MappedAny(), V4MappedAny()},
- TestParam{V4MappedAny(), V4MappedLoopback()},
- TestParam{V4MappedLoopback(), V4Any()},
- TestParam{V4MappedLoopback(), V4Loopback()},
- TestParam{V4MappedLoopback(), V4MappedLoopback()},
-
- // Listeners bound to IN6ADDR_ANY accept all connections.
- TestParam{V6Any(), V4Any()}, TestParam{V6Any(), V4Loopback()},
- TestParam{V6Any(), V4MappedAny()},
- TestParam{V6Any(), V4MappedLoopback()}, TestParam{V6Any(), V6Any()},
- TestParam{V6Any(), V6Loopback()},
-
- // Listeners bound to IN6ADDR_LOOPBACK refuse connections using IPv4
- // addresses.
- TestParam{V6Loopback(), V6Any()},
- TestParam{V6Loopback(), V6Loopback()}),
- DescribeTestParam);
-
-struct ProtocolTestParam {
- std::string description;
- int type;
-};
-
-std::string DescribeProtocolTestParam(
- ::testing::TestParamInfo<ProtocolTestParam> const& info) {
- return info.param.description;
-}
+INSTANTIATE_TEST_SUITE_P(All, SocketInetLoopbackTest,
+ SocketInetLoopbackTestValues(),
+ DescribeSocketInetTestParam);
using SocketMultiProtocolInetLoopbackTest =
::testing::TestWithParam<ProtocolTestParam>;
TEST_P(SocketMultiProtocolInetLoopbackTest, BindAvoidsListeningPortsReuseAddr) {
- const auto& param = GetParam();
+ ProtocolTestParam const& param = GetParam();
// UDP sockets are allowed to bind/listen on the port w/ SO_REUSEADDR, for TCP
// this is only permitted if there is no other listening socket.
SKIP_IF(param.type != SOCK_STREAM);
@@ -222,11 +147,8 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, BindAvoidsListeningPortsReuseAddr) {
}
}
-INSTANTIATE_TEST_SUITE_P(
- AllFamilies, SocketMultiProtocolInetLoopbackTest,
- ::testing::Values(ProtocolTestParam{"TCP", SOCK_STREAM},
- ProtocolTestParam{"UDP", SOCK_DGRAM}),
- DescribeProtocolTestParam);
+INSTANTIATE_TEST_SUITE_P(AllFamilies, SocketMultiProtocolInetLoopbackTest,
+ ProtocolTestValues(), DescribeProtocolTestParam);
} // namespace
diff --git a/test/syscalls/linux/socket_inet_loopback_test_params.h b/test/syscalls/linux/socket_inet_loopback_test_params.h
new file mode 100644
index 000000000..42b48eb8a
--- /dev/null
+++ b/test/syscalls/linux/socket_inet_loopback_test_params.h
@@ -0,0 +1,86 @@
+// Copyright 2018 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_INET_LOOPBACK_TEST_PARAMS_H_
+#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_INET_LOOPBACK_TEST_PARAMS_H_
+
+#include "gtest/gtest.h"
+#include "test/syscalls/linux/socket_test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+struct SocketInetTestParam {
+ TestAddress listener;
+ TestAddress connector;
+};
+
+inline std::string DescribeSocketInetTestParam(
+ ::testing::TestParamInfo<SocketInetTestParam> const& info) {
+ return absl::StrCat("Listen", info.param.listener.description, "_Connect",
+ info.param.connector.description);
+}
+
+inline auto SocketInetLoopbackTestValues() {
+ return ::testing::Values(
+ // Listeners bound to IPv4 addresses refuse connections using IPv6
+ // addresses.
+ SocketInetTestParam{V4Any(), V4Any()},
+ SocketInetTestParam{V4Any(), V4Loopback()},
+ SocketInetTestParam{V4Any(), V4MappedAny()},
+ SocketInetTestParam{V4Any(), V4MappedLoopback()},
+ SocketInetTestParam{V4Loopback(), V4Any()},
+ SocketInetTestParam{V4Loopback(), V4Loopback()},
+ SocketInetTestParam{V4Loopback(), V4MappedLoopback()},
+ SocketInetTestParam{V4MappedAny(), V4Any()},
+ SocketInetTestParam{V4MappedAny(), V4Loopback()},
+ SocketInetTestParam{V4MappedAny(), V4MappedAny()},
+ SocketInetTestParam{V4MappedAny(), V4MappedLoopback()},
+ SocketInetTestParam{V4MappedLoopback(), V4Any()},
+ SocketInetTestParam{V4MappedLoopback(), V4Loopback()},
+ SocketInetTestParam{V4MappedLoopback(), V4MappedLoopback()},
+
+ // Listeners bound to IN6ADDR_ANY accept all connections.
+ SocketInetTestParam{V6Any(), V4Any()},
+ SocketInetTestParam{V6Any(), V4Loopback()},
+ SocketInetTestParam{V6Any(), V4MappedAny()},
+ SocketInetTestParam{V6Any(), V4MappedLoopback()},
+ SocketInetTestParam{V6Any(), V6Any()},
+ SocketInetTestParam{V6Any(), V6Loopback()},
+
+ // Listeners bound to IN6ADDR_LOOPBACK refuse connections using IPv4
+ // addresses.
+ SocketInetTestParam{V6Loopback(), V6Any()},
+ SocketInetTestParam{V6Loopback(), V6Loopback()});
+}
+
+struct ProtocolTestParam {
+ std::string description;
+ int type;
+};
+
+inline std::string DescribeProtocolTestParam(
+ ::testing::TestParamInfo<ProtocolTestParam> const& info) {
+ return info.param.description;
+}
+
+inline auto ProtocolTestValues() {
+ return ::testing::Values(ProtocolTestParam{"TCP", SOCK_STREAM},
+ ProtocolTestParam{"UDP", SOCK_DGRAM});
+}
+
+} // namespace testing
+} // namespace gvisor
+
+#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_INET_LOOPBACK_TEST_PARAMS_H_
diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc b/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc
index 8390f7c3b..09f070797 100644
--- a/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc
+++ b/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc
@@ -38,8 +38,8 @@ TEST_P(IPv6UDPUnboundExternalNetworkingSocketTest, TestJoinLeaveMulticast) {
ipv6_mreq group_req = {
.ipv6mr_multiaddr =
reinterpret_cast<sockaddr_in6*>(&multicast_addr.addr)->sin6_addr,
- .ipv6mr_interface =
- (unsigned int)ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")),
+ .ipv6mr_interface = static_cast<decltype(ipv6_mreq::ipv6mr_interface)>(
+ ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"))),
};
ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP,
&group_req, sizeof(group_req)),
diff --git a/test/syscalls/linux/socket_netdevice.cc b/test/syscalls/linux/socket_netdevice.cc
index 5f8d7f981..8d214a2b7 100644
--- a/test/syscalls/linux/socket_netdevice.cc
+++ b/test/syscalls/linux/socket_netdevice.cc
@@ -37,6 +37,7 @@ using ::testing::AnyOf;
using ::testing::Eq;
TEST(NetdeviceTest, Loopback) {
+ SKIP_IF(IsRunningWithHostinet());
FileDescriptor sock =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
@@ -60,6 +61,7 @@ TEST(NetdeviceTest, Loopback) {
}
TEST(NetdeviceTest, Netmask) {
+ SKIP_IF(IsRunningWithHostinet());
// We need an interface index to identify the loopback device.
FileDescriptor sock =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
@@ -135,6 +137,7 @@ TEST(NetdeviceTest, Netmask) {
}
TEST(NetdeviceTest, InterfaceName) {
+ SKIP_IF(IsRunningWithHostinet());
FileDescriptor sock =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
@@ -168,6 +171,7 @@ TEST(NetdeviceTest, InterfaceFlags) {
}
TEST(NetdeviceTest, InterfaceMTU) {
+ SKIP_IF(IsRunningWithHostinet());
FileDescriptor sock =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
@@ -181,6 +185,7 @@ TEST(NetdeviceTest, InterfaceMTU) {
}
TEST(NetdeviceTest, EthtoolGetTSInfo) {
+ SKIP_IF(IsRunningWithHostinet());
FileDescriptor sock =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
diff --git a/test/syscalls/linux/socket_test_util.cc b/test/syscalls/linux/socket_test_util.cc
index 9e3a129cf..5e36472b4 100644
--- a/test/syscalls/linux/socket_test_util.cc
+++ b/test/syscalls/linux/socket_test_util.cc
@@ -15,6 +15,7 @@
#include "test/syscalls/linux/socket_test_util.h"
#include <arpa/inet.h>
+#include <netinet/in.h>
#include <poll.h>
#include <sys/socket.h>
@@ -84,7 +85,8 @@ Creator<SocketPair> AcceptBindSocketPairCreator(bool abstract, int domain,
RETURN_ERROR_IF_SYSCALL_FAIL(
bind(bound, AsSockAddr(&bind_addr), sizeof(bind_addr)));
MaybeSave(); // Successful bind.
- RETURN_ERROR_IF_SYSCALL_FAIL(listen(bound, /* backlog = */ 5));
+ RETURN_ERROR_IF_SYSCALL_FAIL(
+ listen(bound, /* backlog = */ 5)); // NOLINT(bugprone-argument-comment)
MaybeSave(); // Successful listen.
int connected;
@@ -315,7 +317,8 @@ PosixErrorOr<T> BindIP(int fd, bool dual_stack) {
template <typename T>
PosixErrorOr<T> TCPBindAndListen(int fd, bool dual_stack) {
ASSIGN_OR_RETURN_ERRNO(T addr, BindIP<T>(fd, dual_stack));
- RETURN_ERROR_IF_SYSCALL_FAIL(listen(fd, /* backlog = */ 5));
+ RETURN_ERROR_IF_SYSCALL_FAIL(
+ listen(fd, /* backlog = */ 5)); // NOLINT(bugprone-argument-comment)
return addr;
}
@@ -798,84 +801,82 @@ TestAddress TestAddress::WithPort(uint16_t port) const {
return addr;
}
-TestAddress V4Any() {
- TestAddress t("V4Any");
- t.addr.ss_family = AF_INET;
- t.addr_len = sizeof(sockaddr_in);
- reinterpret_cast<sockaddr_in*>(&t.addr)->sin_addr.s_addr = htonl(INADDR_ANY);
- return t;
-}
+namespace {
-TestAddress V4Loopback() {
- TestAddress t("V4Loopback");
+TestAddress V4Addr(std::string description, in_addr_t addr) {
+ TestAddress t(std::move(description));
t.addr.ss_family = AF_INET;
t.addr_len = sizeof(sockaddr_in);
- reinterpret_cast<sockaddr_in*>(&t.addr)->sin_addr.s_addr =
- htonl(INADDR_LOOPBACK);
+ reinterpret_cast<sockaddr_in*>(&t.addr)->sin_addr.s_addr = addr;
return t;
}
-TestAddress V4MappedAny() {
- TestAddress t("V4MappedAny");
+TestAddress V6Addr(std::string description, const in6_addr& addr) {
+ TestAddress t(std::move(description));
t.addr.ss_family = AF_INET6;
t.addr_len = sizeof(sockaddr_in6);
- inet_pton(AF_INET6, "::ffff:0.0.0.0",
- reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr.s6_addr);
+ reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr = addr;
return t;
}
+} // namespace
+
+TestAddress V4AddrStr(std::string description, const char* addr) {
+ in_addr_t s_addr;
+ inet_pton(AF_INET, addr, &s_addr);
+ return V4Addr(description, s_addr);
+}
+
+TestAddress V6AddrStr(std::string description, const char* addr) {
+ struct in6_addr s_addr;
+ inet_pton(AF_INET6, addr, &s_addr);
+ return V6Addr(description, s_addr);
+}
+
+TestAddress V4Any() { return V4Addr("V4Any", htonl(INADDR_ANY)); }
+
+TestAddress V4Broadcast() {
+ return V4Addr("V4Broadcast", htonl(INADDR_BROADCAST));
+}
+
+TestAddress V4Loopback() {
+ return V4Addr("V4Loopback", htonl(INADDR_LOOPBACK));
+}
+
+TestAddress V4LoopbackSubnetBroadcast() {
+ return V4AddrStr("V4LoopbackSubnetBroadcast", "127.255.255.255");
+}
+
+TestAddress V4MappedAny() { return V6AddrStr("V4MappedAny", "::ffff:0.0.0.0"); }
+
TestAddress V4MappedLoopback() {
- TestAddress t("V4MappedLoopback");
- t.addr.ss_family = AF_INET6;
- t.addr_len = sizeof(sockaddr_in6);
- inet_pton(AF_INET6, "::ffff:127.0.0.1",
- reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr.s6_addr);
- return t;
+ return V6AddrStr("V4MappedLoopback", "::ffff:127.0.0.1");
}
TestAddress V4Multicast() {
- TestAddress t("V4Multicast");
- t.addr.ss_family = AF_INET;
- t.addr_len = sizeof(sockaddr_in);
- reinterpret_cast<sockaddr_in*>(&t.addr)->sin_addr.s_addr =
- inet_addr(kMulticastAddress);
- return t;
+ return V4Addr("V4Multicast", inet_addr(kMulticastAddress));
}
-TestAddress V4Broadcast() {
- TestAddress t("V4Broadcast");
- t.addr.ss_family = AF_INET;
- t.addr_len = sizeof(sockaddr_in);
- reinterpret_cast<sockaddr_in*>(&t.addr)->sin_addr.s_addr =
- htonl(INADDR_BROADCAST);
- return t;
+TestAddress V4MulticastAllHosts() {
+ return V4Addr("V4MulticastAllHosts", htonl(INADDR_ALLHOSTS_GROUP));
}
-TestAddress V6Any() {
- TestAddress t("V6Any");
- t.addr.ss_family = AF_INET6;
- t.addr_len = sizeof(sockaddr_in6);
- reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr = in6addr_any;
- return t;
+TestAddress V6Any() { return V6Addr("V6Any", in6addr_any); }
+
+TestAddress V6Loopback() { return V6Addr("V6Loopback", in6addr_loopback); }
+
+TestAddress V6Multicast() { return V6AddrStr("V6Multicast", "ff05::1234"); }
+
+TestAddress V6MulticastInterfaceLocalAllNodes() {
+ return V6AddrStr("V6MulticastInterfaceLocalAllNodes", "ff01::1");
}
-TestAddress V6Loopback() {
- TestAddress t("V6Loopback");
- t.addr.ss_family = AF_INET6;
- t.addr_len = sizeof(sockaddr_in6);
- reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr = in6addr_loopback;
- return t;
+TestAddress V6MulticastLinkLocalAllNodes() {
+ return V6AddrStr("V6MulticastLinkLocalAllNodes", "ff02::1");
}
-TestAddress V6Multicast() {
- TestAddress t("V6Multicast");
- t.addr.ss_family = AF_INET6;
- t.addr_len = sizeof(sockaddr_in6);
- EXPECT_EQ(
- 1,
- inet_pton(AF_INET6, "ff05::1234",
- reinterpret_cast<sockaddr_in6*>(&t.addr)->sin6_addr.s6_addr));
- return t;
+TestAddress V6MulticastLinkLocalAllRouters() {
+ return V6AddrStr("V6MulticastLinkLocalAllRouters", "ff02::2");
}
// Checksum computes the internet checksum of a buffer.
@@ -947,5 +948,124 @@ uint16_t ICMPChecksum(struct icmphdr icmphdr, const char* payload,
return csum;
}
+PosixErrorOr<uint16_t> AddrPort(int family, sockaddr_storage const& addr) {
+ switch (family) {
+ case AF_INET:
+ return static_cast<uint16_t>(
+ reinterpret_cast<sockaddr_in const*>(&addr)->sin_port);
+ case AF_INET6:
+ return static_cast<uint16_t>(
+ reinterpret_cast<sockaddr_in6 const*>(&addr)->sin6_port);
+ default:
+ return PosixError(EINVAL,
+ absl::StrCat("unknown socket family: ", family));
+ }
+}
+
+PosixError SetAddrPort(int family, sockaddr_storage* addr, uint16_t port) {
+ switch (family) {
+ case AF_INET:
+ reinterpret_cast<sockaddr_in*>(addr)->sin_port = port;
+ return NoError();
+ case AF_INET6:
+ reinterpret_cast<sockaddr_in6*>(addr)->sin6_port = port;
+ return NoError();
+ default:
+ return PosixError(EINVAL,
+ absl::StrCat("unknown socket family: ", family));
+ }
+}
+
+void SetupTimeWaitClose(const TestAddress* listener,
+ const TestAddress* connector, bool reuse,
+ bool accept_close, sockaddr_storage* listen_addr,
+ sockaddr_storage* conn_bound_addr) {
+ // Create the listening socket.
+ FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(listener->family(), SOCK_STREAM, IPPROTO_TCP));
+ if (reuse) {
+ ASSERT_THAT(setsockopt(listen_fd.get(), SOL_SOCKET, SO_REUSEADDR,
+ &kSockOptOn, sizeof(kSockOptOn)),
+ SyscallSucceeds());
+ }
+ ASSERT_THAT(
+ bind(listen_fd.get(), AsSockAddr(listen_addr), listener->addr_len),
+ SyscallSucceeds());
+ ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds());
+
+ // Get the port bound by the listening socket.
+ socklen_t addrlen = listener->addr_len;
+ ASSERT_THAT(getsockname(listen_fd.get(), AsSockAddr(listen_addr), &addrlen),
+ SyscallSucceeds());
+
+ uint16_t const port =
+ ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener->family(), *listen_addr));
+
+ // Connect to the listening socket.
+ FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(connector->family(), SOCK_STREAM, IPPROTO_TCP));
+
+ // We disable saves after this point as a S/R causes the netstack seed
+ // to be regenerated which changes what ports/ISN is picked for a given
+ // tuple (src ip,src port, dst ip, dst port). This can cause the final
+ // SYN to use a sequence number that looks like one from the current
+ // connection in TIME_WAIT and will not be accepted causing the test
+ // to timeout.
+ //
+ // TODO(gvisor.dev/issue/940): S/R portSeed/portHint
+ DisableSave ds;
+
+ sockaddr_storage conn_addr = connector->addr;
+ ASSERT_NO_ERRNO(SetAddrPort(connector->family(), &conn_addr, port));
+ ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), AsSockAddr(&conn_addr),
+ connector->addr_len),
+ SyscallSucceeds());
+
+ // Accept the connection.
+ auto accepted =
+ ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr));
+
+ // Get the address/port bound by the connecting socket.
+ socklen_t conn_addrlen = connector->addr_len;
+ ASSERT_THAT(
+ getsockname(conn_fd.get(), AsSockAddr(conn_bound_addr), &conn_addrlen),
+ SyscallSucceeds());
+
+ FileDescriptor active_closefd, passive_closefd;
+ if (accept_close) {
+ active_closefd = std::move(accepted);
+ passive_closefd = std::move(conn_fd);
+ } else {
+ active_closefd = std::move(conn_fd);
+ passive_closefd = std::move(accepted);
+ }
+
+ // shutdown to trigger TIME_WAIT.
+ ASSERT_THAT(shutdown(active_closefd.get(), SHUT_WR), SyscallSucceeds());
+ {
+ constexpr int kTimeout = 10000;
+ pollfd pfd = {
+ .fd = passive_closefd.get(),
+ .events = POLLIN,
+ };
+ ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1));
+ ASSERT_EQ(pfd.revents, POLLIN);
+ }
+ ASSERT_THAT(shutdown(passive_closefd.get(), SHUT_WR), SyscallSucceeds());
+ {
+ constexpr int kTimeout = 10000;
+ constexpr int16_t want_events = POLLHUP;
+ pollfd pfd = {
+ .fd = active_closefd.get(),
+ .events = want_events,
+ };
+ ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1));
+ }
+
+ // 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));
+}
+
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/socket_test_util.h b/test/syscalls/linux/socket_test_util.h
index f7ba90130..df4c26f26 100644
--- a/test/syscalls/linux/socket_test_util.h
+++ b/test/syscalls/linux/socket_test_util.h
@@ -499,15 +499,45 @@ struct TestAddress {
constexpr char kMulticastAddress[] = "224.0.2.1";
constexpr char kBroadcastAddress[] = "255.255.255.255";
+// Returns a TestAddress with `addr` parsed as an IPv4 address described by
+// `description`.
+TestAddress V4AddrStr(std::string description, const char* addr);
+// Returns a TestAddress with `addr` parsed as an IPv6 address described by
+// `description`.
+TestAddress V6AddrStr(std::string description, const char* addr);
+
+// Returns a TestAddress for the IPv4 any address.
TestAddress V4Any();
+// Returns a TestAddress for the IPv4 limited broadcast address.
TestAddress V4Broadcast();
+// Returns a TestAddress for the IPv4 loopback address.
TestAddress V4Loopback();
+// Returns a TestAddress for the subnet broadcast of the IPv4 loopback address.
+TestAddress V4LoopbackSubnetBroadcast();
+// Returns a TestAddress for the IPv4-mapped IPv6 any address.
TestAddress V4MappedAny();
+// Returns a TestAddress for the IPv4-mapped IPv6 loopback address.
TestAddress V4MappedLoopback();
+// Returns a TestAddress for a IPv4 multicast address.
TestAddress V4Multicast();
+// Returns a TestAddress for the IPv4 all-hosts multicast group address.
+TestAddress V4MulticastAllHosts();
+
+// Returns a TestAddress for the IPv6 any address.
TestAddress V6Any();
+// Returns a TestAddress for the IPv6 loopback address.
TestAddress V6Loopback();
+// Returns a TestAddress for a IPv6 multicast address.
TestAddress V6Multicast();
+// Returns a TestAddress for the IPv6 interface-local all-nodes multicast group
+// address.
+TestAddress V6MulticastInterfaceLocalAllNodes();
+// Returns a TestAddress for the IPv6 link-local all-nodes multicast group
+// address.
+TestAddress V6MulticastLinkLocalAllNodes();
+// Returns a TestAddress for the IPv6 link-local all-routers multicast group
+// address.
+TestAddress V6MulticastLinkLocalAllRouters();
// Compute the internet checksum of an IP header.
uint16_t IPChecksum(struct iphdr ip);
@@ -534,6 +564,18 @@ inline sockaddr* AsSockAddr(sockaddr_un* s) {
return reinterpret_cast<sockaddr*>(s);
}
+PosixErrorOr<uint16_t> AddrPort(int family, sockaddr_storage const& addr);
+
+PosixError SetAddrPort(int family, sockaddr_storage* addr, uint16_t port);
+
+// setupTimeWaitClose sets up a socket endpoint in TIME_WAIT state.
+// Callers can choose to perform active close on either ends of the connection
+// and also specify if they want to enabled SO_REUSEADDR.
+void SetupTimeWaitClose(const TestAddress* listener,
+ const TestAddress* connector, bool reuse,
+ bool accept_close, sockaddr_storage* listen_addr,
+ sockaddr_storage* conn_bound_addr);
+
namespace internal {
PosixErrorOr<int> TryPortAvailable(int port, AddressFamily family,
SocketType type, bool reuse_addr);
diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc
index 5bfdecc79..183819faf 100644
--- a/test/syscalls/linux/tcp_socket.cc
+++ b/test/syscalls/linux/tcp_socket.cc
@@ -1182,6 +1182,62 @@ TEST_P(SimpleTcpSocketTest, SelfConnectSend) {
EXPECT_THAT(shutdown(s.get(), SHUT_WR), SyscallSucceedsWithValue(0));
}
+TEST_P(SimpleTcpSocketTest, SelfConnectSendShutdownWrite) {
+ // Initialize address to the loopback one.
+ sockaddr_storage addr =
+ ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam()));
+ socklen_t addrlen = sizeof(addr);
+
+ const FileDescriptor s =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
+
+ ASSERT_THAT(bind(s.get(), AsSockAddr(&addr), addrlen), SyscallSucceeds());
+ // Get the bound port.
+ ASSERT_THAT(getsockname(s.get(), AsSockAddr(&addr), &addrlen),
+ SyscallSucceeds());
+ ASSERT_THAT(RetryEINTR(connect)(s.get(), AsSockAddr(&addr), addrlen),
+ SyscallSucceeds());
+
+ // Write enough data to fill send and receive buffers.
+ size_t write_size = 24 << 20; // 24 MiB.
+ std::vector<char> writebuf(write_size);
+
+ ScopedThread t([&s]() {
+ absl::SleepFor(absl::Milliseconds(250));
+ ASSERT_THAT(shutdown(s.get(), SHUT_WR), SyscallSucceeds());
+ });
+
+ // Try to send the whole thing.
+ int n;
+ ASSERT_THAT(n = SendFd(s.get(), writebuf.data(), writebuf.size(), 0),
+ SyscallFailsWithErrno(EPIPE));
+}
+
+TEST_P(SimpleTcpSocketTest, SelfConnectRecvShutdownRead) {
+ // Initialize address to the loopback one.
+ sockaddr_storage addr =
+ ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam()));
+ socklen_t addrlen = sizeof(addr);
+
+ const FileDescriptor s =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
+
+ ASSERT_THAT(bind(s.get(), AsSockAddr(&addr), addrlen), SyscallSucceeds());
+ // Get the bound port.
+ ASSERT_THAT(getsockname(s.get(), AsSockAddr(&addr), &addrlen),
+ SyscallSucceeds());
+ ASSERT_THAT(RetryEINTR(connect)(s.get(), AsSockAddr(&addr), addrlen),
+ SyscallSucceeds());
+
+ ScopedThread t([&s]() {
+ absl::SleepFor(absl::Milliseconds(250));
+ ASSERT_THAT(shutdown(s.get(), SHUT_RD), SyscallSucceeds());
+ });
+
+ char buf[1];
+ EXPECT_THAT(recv(s.get(), buf, 0, 0), SyscallSucceedsWithValue(0));
+}
+
void NonBlockingConnect(int family, int16_t pollMask) {
const FileDescriptor listener =
ASSERT_NO_ERRNO_AND_VALUE(Socket(family, SOCK_STREAM, IPPROTO_TCP));
diff --git a/test/syscalls/linux/timers.cc b/test/syscalls/linux/timers.cc
index 93a98adb1..bc12dd4af 100644
--- a/test/syscalls/linux/timers.cc
+++ b/test/syscalls/linux/timers.cc
@@ -26,6 +26,7 @@
#include "absl/flags/flag.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
+#include "benchmark/benchmark.h"
#include "test/util/cleanup.h"
#include "test/util/logging.h"
#include "test/util/multiprocess_util.h"
@@ -92,6 +93,8 @@ TEST(TimerTest, ProcessKilledOnCPUSoftLimit) {
TEST_PCHECK(setrlimit(RLIMIT_CPU, &cpu_limits) == 0);
MaybeSave();
for (;;) {
+ int x = 0;
+ benchmark::DoNotOptimize(x); // Don't optimize this loop away.
}
}
ASSERT_THAT(pid, SyscallSucceeds());
@@ -151,6 +154,8 @@ TEST(TimerTest, ProcessPingedRepeatedlyAfterCPUSoftLimit) {
TEST_PCHECK(setrlimit(RLIMIT_CPU, &cpu_limits) == 0);
MaybeSave();
for (;;) {
+ int x = 0;
+ benchmark::DoNotOptimize(x); // Don't optimize this loop away.
}
}
ASSERT_THAT(pid, SyscallSucceeds());
@@ -197,6 +202,8 @@ TEST(TimerTest, ProcessKilledOnCPUHardLimit) {
TEST_PCHECK(setrlimit(RLIMIT_CPU, &cpu_limits) == 0);
MaybeSave();
for (;;) {
+ int x = 0;
+ benchmark::DoNotOptimize(x); // Don't optimize this loop away.
}
}
ASSERT_THAT(pid, SyscallSucceeds());
diff --git a/test/syscalls/linux/tuntap.cc b/test/syscalls/linux/tuntap.cc
index e209b9cbc..1c74b9724 100644
--- a/test/syscalls/linux/tuntap.cc
+++ b/test/syscalls/linux/tuntap.cc
@@ -23,6 +23,7 @@
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
+
#include <cstddef>
#include "gmock/gmock.h"
@@ -429,18 +430,22 @@ TEST_F(TuntapTest, TUNNoPacketInfo) {
// Interface setup.
auto link = ASSERT_NO_ERRNO_AND_VALUE(GetLinkByName(kTunName));
const struct in_addr dev_ipv4_addr = {.s_addr = kTapIPAddr};
- EXPECT_NO_ERRNO(LinkAddLocalAddr(link.index, AF_INET, 24, &dev_ipv4_addr, sizeof(dev_ipv4_addr)));
+ EXPECT_NO_ERRNO(LinkAddLocalAddr(link.index, AF_INET, 24, &dev_ipv4_addr,
+ sizeof(dev_ipv4_addr)));
- ping_pkt ping_req = CreatePingPacket(kMacB, kTapPeerIPAddr, kMacA, kTapIPAddr);
+ ping_pkt ping_req =
+ CreatePingPacket(kMacB, kTapPeerIPAddr, kMacA, kTapIPAddr);
size_t packet_size = sizeof(ping_req) - offsetof(ping_pkt, ip);
// Send ICMP query
- EXPECT_THAT(write(fd.get(), &ping_req.ip, packet_size), SyscallSucceedsWithValue(packet_size));
+ EXPECT_THAT(write(fd.get(), &ping_req.ip, packet_size),
+ SyscallSucceedsWithValue(packet_size));
// Receive loop to process inbound packets.
while (1) {
ping_pkt ping_resp = {};
- EXPECT_THAT(read(fd.get(), &ping_resp.ip, packet_size), SyscallSucceedsWithValue(packet_size));
+ EXPECT_THAT(read(fd.get(), &ping_resp.ip, packet_size),
+ SyscallSucceedsWithValue(packet_size));
// Process ping response packet.
if (!memcmp(&ping_resp.ip.saddr, &ping_req.ip.daddr, kIPLen) &&
diff --git a/test/syscalls/linux/udp_socket.cc b/test/syscalls/linux/udp_socket.cc
index 29e174f71..b40598767 100644
--- a/test/syscalls/linux/udp_socket.cc
+++ b/test/syscalls/linux/udp_socket.cc
@@ -791,14 +791,14 @@ TEST_P(UdpSocketTest, RecvErrorConnRefused) {
iov.iov_len = kBufLen;
size_t control_buf_len = CMSG_SPACE(sizeof(sock_extended_err) + addrlen_);
- char* control_buf = static_cast<char*>(calloc(1, control_buf_len));
+ std::vector<char> control_buf(control_buf_len);
struct sockaddr_storage remote;
memset(&remote, 0, sizeof(remote));
struct msghdr msg = {};
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_flags = 0;
- msg.msg_control = control_buf;
+ msg.msg_control = control_buf.data();
msg.msg_controllen = control_buf_len;
msg.msg_name = reinterpret_cast<void*>(&remote);
msg.msg_namelen = addrlen_;
@@ -1429,12 +1429,8 @@ TEST_P(UdpSocketTest, FIONREADZeroLengthPacket) {
sendto(sock_.get(), buf + i * psize, 0, 0, bind_addr_, addrlen_),
SyscallSucceedsWithValue(0));
- // TODO(gvisor.dev/issue/2726): sending a zero-length message to a hostinet
- // socket does not cause a poll event to be triggered.
- if (!IsRunningWithHostinet()) {
- ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000),
- SyscallSucceedsWithValue(1));
- }
+ ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000),
+ SyscallSucceedsWithValue(1));
// Check that regardless of how many packets are in the queue, the size
// reported is that of a single packet.
diff --git a/test/syscalls/linux/uidgid.cc b/test/syscalls/linux/uidgid.cc
index 4139a18d8..d95a3e010 100644
--- a/test/syscalls/linux/uidgid.cc
+++ b/test/syscalls/linux/uidgid.cc
@@ -170,7 +170,9 @@ TEST(UidGidRootTest, SetgidNotFromThreadGroupLeader) {
const gid_t gid = absl::GetFlag(FLAGS_scratch_gid1);
// NOTE(b/64676707): Do setgid in a separate thread so that we can test if
// info.si_pid is set correctly.
- ScopedThread([gid] { ASSERT_THAT(setgid(gid), SyscallSucceeds()); });
+ ScopedThread thread =
+ ScopedThread([gid] { ASSERT_THAT(setgid(gid), SyscallSucceeds()); });
+ thread.Join();
EXPECT_NO_ERRNO(CheckGIDs(gid, gid, gid));
#pragma pop_macro("allow_setgid")
diff --git a/test/syscalls/linux/uname.cc b/test/syscalls/linux/uname.cc
index 759ea4f53..c52abef5c 100644
--- a/test/syscalls/linux/uname.cc
+++ b/test/syscalls/linux/uname.cc
@@ -88,7 +88,7 @@ TEST(UnameTest, UnshareUTS) {
struct utsname init;
ASSERT_THAT(uname(&init), SyscallSucceeds());
- ScopedThread([&]() {
+ ScopedThread thread = ScopedThread([&]() {
EXPECT_THAT(unshare(CLONE_NEWUTS), SyscallSucceeds());
constexpr char kHostname[] = "wubbalubba";
@@ -97,6 +97,7 @@ TEST(UnameTest, UnshareUTS) {
char hostname[65];
EXPECT_THAT(gethostname(hostname, sizeof(hostname)), SyscallSucceeds());
});
+ thread.Join();
struct utsname after;
EXPECT_THAT(uname(&after), SyscallSucceeds());
diff --git a/test/syscalls/linux/verity_getdents.cc b/test/syscalls/linux/verity_getdents.cc
new file mode 100644
index 000000000..093595dd3
--- /dev/null
+++ b/test/syscalls/linux/verity_getdents.cc
@@ -0,0 +1,95 @@
+// Copyright 2021 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <dirent.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/syscall.h>
+
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "test/util/capability_util.h"
+#include "test/util/fs_util.h"
+#include "test/util/temp_path.h"
+#include "test/util/test_util.h"
+#include "test/util/verity_util.h"
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+class GetDentsTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // Verity is implemented in VFS2.
+ SKIP_IF(IsRunningWithVFS1());
+
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
+ // Mount a tmpfs file system, to be wrapped by a verity fs.
+ tmpfs_dir_ = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ ASSERT_THAT(mount("", tmpfs_dir_.path().c_str(), "tmpfs", 0, ""),
+ SyscallSucceeds());
+
+ // Create a new file in the tmpfs mount.
+ file_ = ASSERT_NO_ERRNO_AND_VALUE(
+ TempPath::CreateFileWith(tmpfs_dir_.path(), kContents, 0777));
+ filename_ = Basename(file_.path());
+ }
+
+ TempPath tmpfs_dir_;
+ TempPath file_;
+ std::string filename_;
+};
+
+TEST_F(GetDentsTest, GetDents) {
+ std::string verity_dir =
+ ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_));
+
+ std::vector<std::string> expect = {".", "..", filename_};
+ EXPECT_NO_ERRNO(DirContains(verity_dir, expect, /*exclude=*/{}));
+}
+
+TEST_F(GetDentsTest, Deleted) {
+ std::string verity_dir =
+ ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_));
+
+ EXPECT_THAT(unlink(JoinPath(tmpfs_dir_.path(), filename_).c_str()),
+ SyscallSucceeds());
+
+ EXPECT_THAT(DirContains(verity_dir, /*expect=*/{}, /*exclude=*/{}),
+ PosixErrorIs(EIO, ::testing::_));
+}
+
+TEST_F(GetDentsTest, Renamed) {
+ std::string verity_dir =
+ ASSERT_NO_ERRNO_AND_VALUE(MountVerity(tmpfs_dir_.path(), filename_));
+
+ std::string new_file_name = "renamed-" + filename_;
+ EXPECT_THAT(rename(JoinPath(tmpfs_dir_.path(), filename_).c_str(),
+ JoinPath(tmpfs_dir_.path(), new_file_name).c_str()),
+ SyscallSucceeds());
+
+ EXPECT_THAT(DirContains(verity_dir, /*expect=*/{}, /*exclude=*/{}),
+ PosixErrorIs(EIO, ::testing::_));
+}
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/syscalls/linux/verity_mount.cc b/test/syscalls/linux/verity_mount.cc
index e73dd5599..d6bfcb46d 100644
--- a/test/syscalls/linux/verity_mount.cc
+++ b/test/syscalls/linux/verity_mount.cc
@@ -22,13 +22,14 @@
#include "test/util/capability_util.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
+#include "test/util/verity_util.h"
namespace gvisor {
namespace testing {
namespace {
-// Mount verity file system on an existing gofer mount.
+// Mount verity file system on an existing tmpfs mount.
TEST(MountTest, MountExisting) {
// Verity is implemented in VFS2.
SKIP_IF(IsRunningWithVFS1());
@@ -43,8 +44,11 @@ TEST(MountTest, MountExisting) {
// Mount a verity file system on the existing gofer mount.
auto const verity_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
std::string opts = "lower_path=" + tmpfs_dir.path();
- EXPECT_THAT(mount("", verity_dir.path().c_str(), "verity", 0, opts.c_str()),
+ ASSERT_THAT(mount("", verity_dir.path().c_str(), "verity", 0, opts.c_str()),
SyscallSucceeds());
+ auto const fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(verity_dir.path(), O_RDONLY, 0777));
+ EXPECT_THAT(ioctl(fd.get(), FS_IOC_ENABLE_VERITY), SyscallSucceeds());
}
} // namespace
diff --git a/test/util/posix_error.h b/test/util/posix_error.h
index 9ca09b77c..40853cb21 100644
--- a/test/util/posix_error.h
+++ b/test/util/posix_error.h
@@ -385,7 +385,7 @@ class PosixErrorIsMatcher {
};
// Returns a gMock matcher that matches a PosixError or PosixErrorOr<> whose
-// whose error code matches code_matcher, and whose error message matches
+// error code matches code_matcher, and whose error message matches
// message_matcher.
template <typename ErrorCodeMatcher>
PosixErrorIsMatcher PosixErrorIs(
@@ -395,6 +395,14 @@ PosixErrorIsMatcher PosixErrorIs(
std::move(message_matcher));
}
+// Returns a gMock matcher that matches a PosixError or PosixErrorOr<> whose
+// error code matches code_matcher.
+template <typename ErrorCodeMatcher>
+PosixErrorIsMatcher PosixErrorIs(ErrorCodeMatcher&& code_matcher) {
+ return PosixErrorIsMatcher(std::forward<ErrorCodeMatcher>(code_matcher),
+ ::testing::_);
+}
+
// Returns a gMock matcher that matches a PosixErrorOr<> which is ok() and
// value matches the inner matcher.
template <typename InnerMatcher>