summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/benchmarks/base/BUILD13
-rw-r--r--test/benchmarks/base/base.go4
-rw-r--r--test/benchmarks/base/size_test.go220
-rw-r--r--test/benchmarks/base/startup_test.go156
-rw-r--r--test/benchmarks/base/sysbench_test.go2
-rw-r--r--test/benchmarks/harness/util.go10
-rw-r--r--test/benchmarks/network/BUILD1
-rw-r--r--test/benchmarks/network/nginx_test.go2
-rw-r--r--test/benchmarks/network/node_test.go32
-rw-r--r--test/benchmarks/network/ruby_test.go134
-rw-r--r--test/benchmarks/tcp/BUILD41
-rw-r--r--test/benchmarks/tcp/README.md87
-rw-r--r--test/benchmarks/tcp/nsjoin.c47
-rwxr-xr-xtest/benchmarks/tcp/tcp_benchmark.sh392
-rw-r--r--test/benchmarks/tcp/tcp_proxy.go451
-rw-r--r--test/benchmarks/tools/BUILD2
-rw-r--r--test/benchmarks/tools/hey.go2
-rw-r--r--test/benchmarks/tools/meminfo.go60
-rw-r--r--test/benchmarks/tools/meminfo_test.go84
-rw-r--r--test/e2e/integration_test.go18
-rw-r--r--test/fuse/linux/fuse_base.cc7
-rw-r--r--test/packetdrill/BUILD9
-rw-r--r--test/packetimpact/runner/BUILD8
-rw-r--r--test/packetimpact/runner/defs.bzl22
-rw-r--r--test/packetimpact/runner/packetimpact_test.go18
-rw-r--r--test/packetimpact/testbench/testbench.go6
-rw-r--r--test/packetimpact/tests/BUILD2
-rw-r--r--test/packetimpact/tests/tcp_reordering_test.go6
-rw-r--r--test/runner/BUILD8
-rw-r--r--test/runtimes/BUILD17
-rw-r--r--test/runtimes/defs.bzl4
-rw-r--r--test/runtimes/exclude_java11.csv13
-rw-r--r--test/runtimes/exclude_nodejs12.4.0.csv2
-rw-r--r--test/runtimes/exclude_php7.3.6.csv1
-rw-r--r--test/runtimes/exclude_python3.7.3.csv27
-rw-r--r--test/syscalls/BUILD1
-rw-r--r--test/syscalls/linux/inotify.cc2
-rw-r--r--test/syscalls/linux/proc_net.cc38
-rw-r--r--test/syscalls/linux/socket_ip_tcp_generic.cc17
39 files changed, 1874 insertions, 92 deletions
diff --git a/test/benchmarks/base/BUILD b/test/benchmarks/base/BUILD
index 3cb07797d..5e099d0f9 100644
--- a/test/benchmarks/base/BUILD
+++ b/test/benchmarks/base/BUILD
@@ -5,14 +5,20 @@ package(licenses = ["notice"])
go_library(
name = "base",
testonly = 1,
- srcs = ["base.go"],
+ srcs = [
+ "base.go",
+ ],
deps = ["//test/benchmarks/harness"],
)
go_test(
name = "base_test",
- size = "small",
- srcs = ["sysbench_test.go"],
+ size = "large",
+ srcs = [
+ "size_test.go",
+ "startup_test.go",
+ "sysbench_test.go",
+ ],
library = ":base",
tags = [
# Requires docker and runsc to be configured before test runs.
@@ -21,6 +27,7 @@ go_test(
],
deps = [
"//pkg/test/dockerutil",
+ "//test/benchmarks/harness",
"//test/benchmarks/tools",
],
)
diff --git a/test/benchmarks/base/base.go b/test/benchmarks/base/base.go
index 7eb44d0ab..7bac52ff1 100644
--- a/test/benchmarks/base/base.go
+++ b/test/benchmarks/base/base.go
@@ -22,10 +22,10 @@ import (
"gvisor.dev/gvisor/test/benchmarks/harness"
)
-var h harness.Harness
+var testHarness harness.Harness
// TestMain is the main method for package network.
func TestMain(m *testing.M) {
- h.Init()
+ testHarness.Init()
os.Exit(m.Run())
}
diff --git a/test/benchmarks/base/size_test.go b/test/benchmarks/base/size_test.go
new file mode 100644
index 000000000..3c1364faf
--- /dev/null
+++ b/test/benchmarks/base/size_test.go
@@ -0,0 +1,220 @@
+// 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 base
+
+import (
+ "context"
+ "testing"
+ "time"
+
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+ "gvisor.dev/gvisor/test/benchmarks/tools"
+)
+
+// BenchmarkSizeEmpty creates N empty containers and reads memory usage from
+// /proc/meminfo.
+func BenchmarkSizeEmpty(b *testing.B) {
+ machine, err := testHarness.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get machine: %v", err)
+ }
+ defer machine.CleanUp()
+ meminfo := tools.Meminfo{}
+ ctx := context.Background()
+ containers := make([]*dockerutil.Container, 0, b.N)
+
+ // DropCaches before the test.
+ harness.DropCaches(machine)
+
+ // Check available memory on 'machine'.
+ cmd, args := meminfo.MakeCmd()
+ before, err := machine.RunCommand(cmd, args...)
+ if err != nil {
+ b.Fatalf("failed to get meminfo: %v", err)
+ }
+
+ // Make N containers.
+ for i := 0; i < b.N; i++ {
+ container := machine.GetContainer(ctx, b)
+ containers = append(containers, container)
+ if err := container.Spawn(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/alpine",
+ }, "sh", "-c", "echo Hello && sleep 1000"); err != nil {
+ cleanUpContainers(ctx, containers)
+ b.Fatalf("failed to run container: %v", err)
+ }
+ if _, err := container.WaitForOutputSubmatch(ctx, "Hello", 5*time.Second); err != nil {
+ cleanUpContainers(ctx, containers)
+ b.Fatalf("failed to read container output: %v", err)
+ }
+ }
+
+ // Drop caches again before second measurement.
+ harness.DropCaches(machine)
+
+ // Check available memory after containers are up.
+ after, err := machine.RunCommand(cmd, args...)
+ cleanUpContainers(ctx, containers)
+ if err != nil {
+ b.Fatalf("failed to get meminfo: %v", err)
+ }
+ meminfo.Report(b, before, after)
+}
+
+// BenchmarkSizeNginx starts N containers running Nginx, checks that they're
+// serving, and checks memory used based on /proc/meminfo.
+func BenchmarkSizeNginx(b *testing.B) {
+ machine, err := testHarness.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get machine with: %v", err)
+ }
+ defer machine.CleanUp()
+
+ // DropCaches for the first measurement.
+ harness.DropCaches(machine)
+
+ // Measure MemAvailable before creating containers.
+ meminfo := tools.Meminfo{}
+ cmd, args := meminfo.MakeCmd()
+ before, err := machine.RunCommand(cmd, args...)
+ if err != nil {
+ b.Fatalf("failed to run meminfo command: %v", err)
+ }
+
+ // Make N Nginx containers.
+ ctx := context.Background()
+ runOpts := dockerutil.RunOpts{
+ Image: "benchmarks/nginx",
+ }
+ const port = 80
+ servers := startServers(ctx, b,
+ serverArgs{
+ machine: machine,
+ port: port,
+ runOpts: runOpts,
+ })
+ defer cleanUpContainers(ctx, servers)
+
+ // DropCaches after servers are created.
+ harness.DropCaches(machine)
+ // Take after measurement.
+ after, err := machine.RunCommand(cmd, args...)
+ if err != nil {
+ b.Fatalf("failed to run meminfo command: %v", err)
+ }
+ meminfo.Report(b, before, after)
+}
+
+// BenchmarkSizeNode starts N containers running a Node app, checks that
+// they're serving, and checks memory used based on /proc/meminfo.
+func BenchmarkSizeNode(b *testing.B) {
+ machine, err := testHarness.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get machine with: %v", err)
+ }
+ defer machine.CleanUp()
+
+ // Make a redis instance for Node to connect.
+ ctx := context.Background()
+ redis, redisIP := redisInstance(ctx, b, machine)
+ defer redis.CleanUp(ctx)
+
+ // DropCaches after redis is created.
+ harness.DropCaches(machine)
+
+ // Take before measurement.
+ meminfo := tools.Meminfo{}
+ cmd, args := meminfo.MakeCmd()
+ before, err := machine.RunCommand(cmd, args...)
+ if err != nil {
+ b.Fatalf("failed to run meminfo commend: %v", err)
+ }
+
+ // Create N Node servers.
+ runOpts := dockerutil.RunOpts{
+ Image: "benchmarks/node",
+ WorkDir: "/usr/src/app",
+ Links: []string{redis.MakeLink("redis")},
+ }
+ nodeCmd := []string{"node", "index.js", redisIP.String()}
+ const port = 8080
+ servers := startServers(ctx, b,
+ serverArgs{
+ machine: machine,
+ port: port,
+ runOpts: runOpts,
+ cmd: nodeCmd,
+ })
+ defer cleanUpContainers(ctx, servers)
+
+ // DropCaches after servers are created.
+ harness.DropCaches(machine)
+ // Take after measurement.
+ cmd, args = meminfo.MakeCmd()
+ after, err := machine.RunCommand(cmd, args...)
+ if err != nil {
+ b.Fatalf("failed to run meminfo command: %v", err)
+ }
+ meminfo.Report(b, before, after)
+}
+
+// serverArgs wraps args for startServers and runServerWorkload.
+type serverArgs struct {
+ machine harness.Machine
+ port int
+ runOpts dockerutil.RunOpts
+ cmd []string
+}
+
+// startServers starts b.N containers defined by 'runOpts' and 'cmd' and uses
+// 'machine' to check that each is up.
+func startServers(ctx context.Context, b *testing.B, args serverArgs) []*dockerutil.Container {
+ b.Helper()
+ servers := make([]*dockerutil.Container, 0, b.N)
+
+ // Create N servers and wait until each of them is serving.
+ for i := 0; i < b.N; i++ {
+ server := args.machine.GetContainer(ctx, b)
+ servers = append(servers, server)
+ if err := server.Spawn(ctx, args.runOpts, args.cmd...); err != nil {
+ cleanUpContainers(ctx, servers)
+ b.Fatalf("failed to spawn node instance: %v", err)
+ }
+
+ // Get the container IP.
+ servingIP, err := server.FindIP(ctx, false)
+ if err != nil {
+ cleanUpContainers(ctx, servers)
+ b.Fatalf("failed to get ip from server: %v", err)
+ }
+
+ // Wait until the server is up.
+ if err := harness.WaitUntilServing(ctx, args.machine, servingIP, args.port); err != nil {
+ cleanUpContainers(ctx, servers)
+ b.Fatalf("failed to wait for serving")
+ }
+ }
+ return servers
+}
+
+// cleanUpContainers cleans up a slice of containers.
+func cleanUpContainers(ctx context.Context, containers []*dockerutil.Container) {
+ for _, c := range containers {
+ if c != nil {
+ c.CleanUp(ctx)
+ }
+ }
+}
diff --git a/test/benchmarks/base/startup_test.go b/test/benchmarks/base/startup_test.go
new file mode 100644
index 000000000..4628a0a41
--- /dev/null
+++ b/test/benchmarks/base/startup_test.go
@@ -0,0 +1,156 @@
+// 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 base
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "testing"
+ "time"
+
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+)
+
+// BenchmarkStartEmpty times startup time for an empty container.
+func BenchmarkStartupEmpty(b *testing.B) {
+ machine, err := testHarness.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get machine: %v", err)
+ }
+ defer machine.CleanUp()
+
+ ctx := context.Background()
+ for i := 0; i < b.N; i++ {
+ container := machine.GetContainer(ctx, b)
+ defer container.CleanUp(ctx)
+ if _, err := container.Run(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/alpine",
+ }, "true"); err != nil {
+ b.Fatalf("failed to run container: %v", err)
+ }
+ }
+}
+
+// BenchmarkStartupNginx times startup for a Nginx instance.
+// Time is measured from start until the first request is served.
+func BenchmarkStartupNginx(b *testing.B) {
+ // The machine to hold Nginx and the Node Server.
+ machine, err := testHarness.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get machine with: %v", err)
+ }
+ defer machine.CleanUp()
+
+ ctx := context.Background()
+ runOpts := dockerutil.RunOpts{
+ Image: "benchmarks/nginx",
+ }
+ runServerWorkload(ctx, b,
+ serverArgs{
+ machine: machine,
+ runOpts: runOpts,
+ port: 80,
+ })
+}
+
+// BenchmarkStartupNode times startup for a Node application instance.
+// Time is measured from start until the first request is served.
+// Note that the Node app connects to a Redis instance before serving.
+func BenchmarkStartupNode(b *testing.B) {
+ machine, err := testHarness.GetMachine()
+ if err != nil {
+ b.Fatalf("failed to get machine with: %v", err)
+ }
+ defer machine.CleanUp()
+
+ ctx := context.Background()
+ redis, redisIP := redisInstance(ctx, b, machine)
+ defer redis.CleanUp(ctx)
+ runOpts := dockerutil.RunOpts{
+ Image: "benchmarks/node",
+ WorkDir: "/usr/src/app",
+ Links: []string{redis.MakeLink("redis")},
+ }
+
+ cmd := []string{"node", "index.js", redisIP.String()}
+ runServerWorkload(ctx, b,
+ serverArgs{
+ machine: machine,
+ port: 8080,
+ runOpts: runOpts,
+ cmd: cmd,
+ })
+}
+
+// redisInstance returns a Redis container and its reachable IP.
+func redisInstance(ctx context.Context, b *testing.B, machine harness.Machine) (*dockerutil.Container, net.IP) {
+ b.Helper()
+ // Spawn a redis instance for the app to use.
+ redis := machine.GetNativeContainer(ctx, b)
+ if err := redis.Spawn(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/redis",
+ }); err != nil {
+ redis.CleanUp(ctx)
+ b.Fatalf("failed to spwan redis instance: %v", err)
+ }
+
+ if out, err := redis.WaitForOutput(ctx, "Ready to accept connections", 3*time.Second); err != nil {
+ redis.CleanUp(ctx)
+ b.Fatalf("failed to start redis server: %v %s", err, out)
+ }
+ redisIP, err := redis.FindIP(ctx, false)
+ if err != nil {
+ redis.CleanUp(ctx)
+ b.Fatalf("failed to get IP from redis instance: %v", err)
+ }
+ return redis, redisIP
+}
+
+// runServerWorkload runs a server workload defined by 'runOpts' and 'cmd'.
+// 'clientMachine' is used to connect to the server on 'serverMachine'.
+func runServerWorkload(ctx context.Context, b *testing.B, args serverArgs) {
+ b.Helper()
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ if err := func() error {
+ server := args.machine.GetContainer(ctx, b)
+ defer func() {
+ b.StopTimer()
+ // Cleanup servers as we run so that we can go indefinitely.
+ server.CleanUp(ctx)
+ b.StartTimer()
+ }()
+ if err := server.Spawn(ctx, args.runOpts, args.cmd...); err != nil {
+ return fmt.Errorf("failed to spawn node instance: %v", err)
+ }
+
+ servingIP, err := server.FindIP(ctx, false)
+ if err != nil {
+ return fmt.Errorf("failed to get ip from server: %v", err)
+ }
+
+ // Wait until the Client sees the server as up.
+ if err := harness.WaitUntilServing(ctx, args.machine, servingIP, args.port); err != nil {
+ return fmt.Errorf("failed to wait for serving: %v", err)
+ }
+ return nil
+ }(); err != nil {
+ b.Fatal(err)
+ }
+ }
+}
diff --git a/test/benchmarks/base/sysbench_test.go b/test/benchmarks/base/sysbench_test.go
index 7df73e38b..6fb813640 100644
--- a/test/benchmarks/base/sysbench_test.go
+++ b/test/benchmarks/base/sysbench_test.go
@@ -64,7 +64,7 @@ func BenchmarkSysbench(b *testing.B) {
},
}
- machine, err := h.GetMachine()
+ machine, err := testHarness.GetMachine()
if err != nil {
b.Fatalf("failed to get machine: %v", err)
}
diff --git a/test/benchmarks/harness/util.go b/test/benchmarks/harness/util.go
index bc551c582..86b863f78 100644
--- a/test/benchmarks/harness/util.go
+++ b/test/benchmarks/harness/util.go
@@ -23,23 +23,25 @@ import (
"gvisor.dev/gvisor/pkg/test/testutil"
)
+//TODO(gvisor.dev/issue/3535): move to own package or move methods to harness struct.
+
// WaitUntilServing grabs a container from `machine` and waits for a server at
// IP:port.
func WaitUntilServing(ctx context.Context, machine Machine, server net.IP, port int) error {
- var logger testutil.DefaultLogger = "netcat"
+ var logger testutil.DefaultLogger = "util"
netcat := machine.GetNativeContainer(ctx, logger)
defer netcat.CleanUp(ctx)
- cmd := fmt.Sprintf("while ! nc -zv %s %d; do true; done", server, port)
+ cmd := fmt.Sprintf("while ! wget -q --spider http://%s:%d; do true; done", server, port)
_, err := netcat.Run(ctx, dockerutil.RunOpts{
- Image: "packetdrill",
+ Image: "benchmarks/util",
}, "sh", "-c", cmd)
return err
}
// DropCaches drops caches on the provided machine. Requires root.
func DropCaches(machine Machine) error {
- if out, err := machine.RunCommand("/bin/sh", "-c", "sync | sysctl vm.drop_caches=3"); err != nil {
+ if out, err := machine.RunCommand("/bin/sh", "-c", "sync && sysctl vm.drop_caches=3"); err != nil {
return fmt.Errorf("failed to drop caches: %v logs: %s", err, out)
}
return nil
diff --git a/test/benchmarks/network/BUILD b/test/benchmarks/network/BUILD
index d15cd55ee..df5ff7265 100644
--- a/test/benchmarks/network/BUILD
+++ b/test/benchmarks/network/BUILD
@@ -17,6 +17,7 @@ go_test(
"iperf_test.go",
"nginx_test.go",
"node_test.go",
+ "ruby_test.go",
],
library = ":network",
tags = [
diff --git a/test/benchmarks/network/nginx_test.go b/test/benchmarks/network/nginx_test.go
index 5965652a5..2bf1a3624 100644
--- a/test/benchmarks/network/nginx_test.go
+++ b/test/benchmarks/network/nginx_test.go
@@ -25,7 +25,7 @@ import (
// BenchmarkNginxConcurrency iterates the concurrency argument and tests
// how well the runtime under test handles requests in parallel.
-// TODO(zkoopmans): Update with different doc sizes like Httpd.
+// TODO(gvisor.dev/issue/3536): Update with different doc sizes like Httpd.
func BenchmarkNginxConcurrency(b *testing.B) {
// Grab a machine for the client and server.
clientMachine, err := h.GetMachine()
diff --git a/test/benchmarks/network/node_test.go b/test/benchmarks/network/node_test.go
index 5b568cfe5..52eb794c4 100644
--- a/test/benchmarks/network/node_test.go
+++ b/test/benchmarks/network/node_test.go
@@ -24,18 +24,16 @@ import (
"gvisor.dev/gvisor/test/benchmarks/tools"
)
-// BenchmarkNode runs 10K requests using 'hey' against a Node server run on
+// BenchmarkNode runs requests using 'hey' against a Node server run on
// 'runtime'. The server responds to requests by grabbing some data in a
// redis instance and returns the data in its reponse. The test loops through
// increasing amounts of concurency for requests.
func BenchmarkNode(b *testing.B) {
- requests := 10000
concurrency := []int{1, 5, 10, 25}
-
for _, c := range concurrency {
b.Run(fmt.Sprintf("Concurrency%d", c), func(b *testing.B) {
hey := &tools.Hey{
- Requests: requests,
+ Requests: b.N * c, // Requests b.N requests per thread.
Concurrency: c,
}
runNode(b, hey)
@@ -113,19 +111,17 @@ func runNode(b *testing.B, hey *tools.Hey) {
nodeApp.RestartProfiles()
b.ResetTimer()
- for i := 0; i < b.N; i++ {
- // the client should run on Native.
- client := clientMachine.GetNativeContainer(ctx, b)
- out, err := client.Run(ctx, dockerutil.RunOpts{
- Image: "benchmarks/hey",
- }, heyCmd...)
- if err != nil {
- b.Fatalf("hey container failed: %v logs: %s", err, out)
- }
-
- // Stop the timer to parse the data and report stats.
- b.StopTimer()
- hey.Report(b, out)
- b.StartTimer()
+ // the client should run on Native.
+ client := clientMachine.GetNativeContainer(ctx, b)
+ out, err := client.Run(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/hey",
+ }, heyCmd...)
+ if err != nil {
+ b.Fatalf("hey container failed: %v logs: %s", err, out)
}
+
+ // Stop the timer to parse the data and report stats.
+ b.StopTimer()
+ hey.Report(b, out)
+ b.StartTimer()
}
diff --git a/test/benchmarks/network/ruby_test.go b/test/benchmarks/network/ruby_test.go
new file mode 100644
index 000000000..5e0b2b724
--- /dev/null
+++ b/test/benchmarks/network/ruby_test.go
@@ -0,0 +1,134 @@
+// Copyright 2020 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package network
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "gvisor.dev/gvisor/pkg/test/dockerutil"
+ "gvisor.dev/gvisor/test/benchmarks/harness"
+ "gvisor.dev/gvisor/test/benchmarks/tools"
+)
+
+// BenchmarkRuby runs requests using 'hey' against a ruby application server.
+// On start, ruby app generates some random data and pushes it to a redis
+// instance. On a request, the app grabs for random entries from the redis
+// server, publishes it to a document, and returns the doc to the request.
+func BenchmarkRuby(b *testing.B) {
+ concurrency := []int{1, 5, 10, 25}
+ for _, c := range concurrency {
+ b.Run(fmt.Sprintf("Concurrency%d", c), func(b *testing.B) {
+ hey := &tools.Hey{
+ Requests: b.N * c, // b.N requests per thread.
+ Concurrency: c,
+ }
+ runRuby(b, hey)
+ })
+ }
+}
+
+// runRuby runs the test for a given # of requests and concurrency.
+func runRuby(b *testing.B, hey *tools.Hey) {
+ b.Helper()
+ // The machine to hold Redis and the Ruby Server.
+ serverMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatal("failed to get machine with: %v", err)
+ }
+ defer serverMachine.CleanUp()
+
+ // The machine to run 'hey'.
+ clientMachine, err := h.GetMachine()
+ if err != nil {
+ b.Fatal("failed to get machine with: %v", err)
+ }
+ defer clientMachine.CleanUp()
+ ctx := context.Background()
+
+ // Spawn a redis instance for the app to use.
+ redis := serverMachine.GetNativeContainer(ctx, b)
+ if err := redis.Spawn(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/redis",
+ }); err != nil {
+ b.Fatalf("failed to spwan redis instance: %v", err)
+ }
+ defer redis.CleanUp(ctx)
+
+ if out, err := redis.WaitForOutput(ctx, "Ready to accept connections", 3*time.Second); err != nil {
+ b.Fatalf("failed to start redis server: %v %s", err, out)
+ }
+ redisIP, err := redis.FindIP(ctx, false)
+ if err != nil {
+ b.Fatalf("failed to get IP from redis instance: %v", err)
+ }
+
+ // Ruby runs on port 9292.
+ const port = 9292
+
+ // Start-up the Ruby server.
+ rubyApp := serverMachine.GetContainer(ctx, b)
+ if err := rubyApp.Spawn(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/ruby",
+ WorkDir: "/app",
+ Links: []string{redis.MakeLink("redis")},
+ Ports: []int{port},
+ Env: []string{
+ fmt.Sprintf("PORT=%d", port),
+ "WEB_CONCURRENCY=20",
+ "WEB_MAX_THREADS=20",
+ "RACK_ENV=production",
+ fmt.Sprintf("HOST=%s", redisIP),
+ },
+ User: "nobody",
+ }, "sh", "-c", "/usr/bin/puma"); err != nil {
+ b.Fatalf("failed to spawn node instance: %v", err)
+ }
+ defer rubyApp.CleanUp(ctx)
+
+ servingIP, err := serverMachine.IPAddress()
+ if err != nil {
+ b.Fatalf("failed to get ip from server: %v", err)
+ }
+
+ servingPort, err := rubyApp.FindPort(ctx, port)
+ if err != nil {
+ b.Fatalf("failed to port from node instance: %v", err)
+ }
+
+ // Wait until the Client sees the server as up.
+ if err := harness.WaitUntilServing(ctx, clientMachine, servingIP, servingPort); err != nil {
+ b.Fatalf("failed to wait until serving: %v", err)
+ }
+ heyCmd := hey.MakeCmd(servingIP, servingPort)
+ rubyApp.RestartProfiles()
+ b.ResetTimer()
+
+ // the client should run on Native.
+ client := clientMachine.GetNativeContainer(ctx, b)
+ defer client.CleanUp(ctx)
+ out, err := client.Run(ctx, dockerutil.RunOpts{
+ Image: "benchmarks/hey",
+ }, heyCmd...)
+ if err != nil {
+ b.Fatalf("hey container failed: %v logs: %s", err, out)
+ }
+
+ // Stop the timer to parse the data and report stats.
+ b.StopTimer()
+ hey.Report(b, out)
+ b.StartTimer()
+}
diff --git a/test/benchmarks/tcp/BUILD b/test/benchmarks/tcp/BUILD
new file mode 100644
index 000000000..6dde7d9e6
--- /dev/null
+++ b/test/benchmarks/tcp/BUILD
@@ -0,0 +1,41 @@
+load("//tools:defs.bzl", "cc_binary", "go_binary")
+
+package(licenses = ["notice"])
+
+go_binary(
+ name = "tcp_proxy",
+ srcs = ["tcp_proxy.go"],
+ visibility = ["//:sandbox"],
+ deps = [
+ "//pkg/tcpip",
+ "//pkg/tcpip/adapters/gonet",
+ "//pkg/tcpip/link/fdbased",
+ "//pkg/tcpip/link/qdisc/fifo",
+ "//pkg/tcpip/network/arp",
+ "//pkg/tcpip/network/ipv4",
+ "//pkg/tcpip/stack",
+ "//pkg/tcpip/transport/tcp",
+ "//pkg/tcpip/transport/udp",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+# nsjoin is a trivial replacement for nsenter. This is used because nsenter is
+# not available on all systems where this benchmark is run (and we aim to
+# minimize external dependencies.)
+
+cc_binary(
+ name = "nsjoin",
+ srcs = ["nsjoin.c"],
+ visibility = ["//:sandbox"],
+)
+
+sh_binary(
+ name = "tcp_benchmark",
+ srcs = ["tcp_benchmark.sh"],
+ data = [
+ ":nsjoin",
+ ":tcp_proxy",
+ ],
+ visibility = ["//:sandbox"],
+)
diff --git a/test/benchmarks/tcp/README.md b/test/benchmarks/tcp/README.md
new file mode 100644
index 000000000..38e6e69f0
--- /dev/null
+++ b/test/benchmarks/tcp/README.md
@@ -0,0 +1,87 @@
+# TCP Benchmarks
+
+This directory contains a standardized TCP benchmark. This helps to evaluate the
+performance of netstack and native networking stacks under various conditions.
+
+## `tcp_benchmark`
+
+This benchmark allows TCP throughput testing under various conditions. The setup
+consists of an iperf client, a client proxy, a server proxy and an iperf server.
+The client proxy and server proxy abstract the network mechanism used to
+communicate between the iperf client and server.
+
+The setup looks like the following:
+
+```
+ +--------------+ (native) +--------------+
+ | iperf client |[lo @ 10.0.0.1]------>| client proxy |
+ +--------------+ +--------------+
+ [client.0 @ 10.0.0.2]
+ (netstack) | | (native)
+ +------+-----+
+ |
+ [br0]
+ |
+ Network emulation applied ---> [wan.0:wan.1]
+ |
+ [br1]
+ |
+ +------+-----+
+ (netstack) | | (native)
+ [server.0 @ 10.0.0.3]
+ +--------------+ +--------------+
+ | iperf server |<------[lo @ 10.0.0.4]| server proxy |
+ +--------------+ (native) +--------------+
+```
+
+Different configurations can be run using different arguments. For example:
+
+* Native test under normal internet conditions: `tcp_benchmark`
+* Native test under ideal conditions: `tcp_benchmark --ideal`
+* Netstack client under ideal conditions: `tcp_benchmark --client --ideal`
+* Netstack client with 5% packet loss: `tcp_benchmark --client --ideal --loss
+ 5`
+
+Use `tcp_benchmark --help` for full arguments.
+
+This tool may be used to easily generate data for graphing. For example, to
+generate a CSV for various latencies, you might do:
+
+```
+rm -f /tmp/netstack_latency.csv /tmp/native_latency.csv
+latencies=$(seq 0 5 50;
+ seq 60 10 100;
+ seq 125 25 250;
+ seq 300 50 500)
+for latency in $latencies; do
+ read throughput client_cpu server_cpu <<< \
+ $(./tcp_benchmark --duration 30 --client --ideal --latency $latency)
+ echo $latency,$throughput,$client_cpu >> /tmp/netstack_latency.csv
+done
+for latency in $latencies; do
+ read throughput client_cpu server_cpu <<< \
+ $(./tcp_benchmark --duration 30 --ideal --latency $latency)
+ echo $latency,$throughput,$client_cpu >> /tmp/native_latency.csv
+done
+```
+
+Similarly, to generate a CSV for various levels of packet loss, the following
+would be appropriate:
+
+```
+rm -f /tmp/netstack_loss.csv /tmp/native_loss.csv
+losses=$(seq 0 0.1 1.0;
+ seq 1.2 0.2 2.0;
+ seq 2.5 0.5 5.0;
+ seq 6.0 1.0 10.0)
+for loss in $losses; do
+ read throughput client_cpu server_cpu <<< \
+ $(./tcp_benchmark --duration 30 --client --ideal --latency 10 --loss $loss)
+ echo $loss,$throughput,$client_cpu >> /tmp/netstack_loss.csv
+done
+for loss in $losses; do
+ read throughput client_cpu server_cpu <<< \
+ $(./tcp_benchmark --duration 30 --ideal --latency 10 --loss $loss)
+ echo $loss,$throughput,$client_cpu >> /tmp/native_loss.csv
+done
+```
diff --git a/test/benchmarks/tcp/nsjoin.c b/test/benchmarks/tcp/nsjoin.c
new file mode 100644
index 000000000..524b4d549
--- /dev/null
+++ b/test/benchmarks/tcp/nsjoin.c
@@ -0,0 +1,47 @@
+// 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 _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+int main(int argc, char** argv) {
+ if (argc <= 2) {
+ fprintf(stderr, "error: must provide a namespace file.\n");
+ fprintf(stderr, "usage: %s <file> [arguments...]\n", argv[0]);
+ return 1;
+ }
+
+ int fd = open(argv[1], O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "error opening %s: %s\n", argv[1], strerror(errno));
+ return 1;
+ }
+ if (setns(fd, 0) < 0) {
+ fprintf(stderr, "error joining %s: %s\n", argv[1], strerror(errno));
+ return 1;
+ }
+
+ execvp(argv[2], &argv[2]);
+ return 1;
+}
diff --git a/test/benchmarks/tcp/tcp_benchmark.sh b/test/benchmarks/tcp/tcp_benchmark.sh
new file mode 100755
index 000000000..ef04b4ace
--- /dev/null
+++ b/test/benchmarks/tcp/tcp_benchmark.sh
@@ -0,0 +1,392 @@
+#!/bin/bash
+
+# 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.
+
+# TCP benchmark; see README.md for documentation.
+
+# Fixed parameters.
+iperf_port=45201 # Not likely to be privileged.
+proxy_port=44000 # Ditto.
+client_addr=10.0.0.1
+client_proxy_addr=10.0.0.2
+server_proxy_addr=10.0.0.3
+server_addr=10.0.0.4
+mask=8
+
+# Defaults; this provides a reasonable approximation of a decent internet link.
+# Parameters can be varied independently from this set to see response to
+# various changes in the kind of link available.
+client=false
+server=false
+verbose=false
+gso=0
+swgso=false
+mtu=1280 # 1280 is a reasonable lowest-common-denominator.
+latency=10 # 10ms approximates a fast, dedicated connection.
+latency_variation=1 # +/- 1ms is a relatively low amount of jitter.
+loss=0.1 # 0.1% loss is non-zero, but not extremely high.
+duplicate=0.1 # 0.1% means duplicates are 1/10x as frequent as losses.
+duration=30 # 30s is enough time to consistent results (experimentally).
+helper_dir=$(dirname $0)
+netstack_opts=
+disable_linux_gso=
+num_client_threads=1
+
+# Check for netem support.
+lsmod_output=$(lsmod | grep sch_netem)
+if [ "$?" != "0" ]; then
+ echo "warning: sch_netem may not be installed." >&2
+fi
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ --client)
+ client=true
+ ;;
+ --client_tcp_probe_file)
+ shift
+ netstack_opts="${netstack_opts} -client_tcp_probe_file=$1"
+ ;;
+ --server)
+ server=true
+ ;;
+ --verbose)
+ verbose=true
+ ;;
+ --gso)
+ shift
+ gso=$1
+ ;;
+ --swgso)
+ swgso=true
+ ;;
+ --server_tcp_probe_file)
+ shift
+ netstack_opts="${netstack_opts} -server_tcp_probe_file=$1"
+ ;;
+ --ideal)
+ mtu=1500 # Standard ethernet.
+ latency=0 # No latency.
+ latency_variation=0 # No jitter.
+ loss=0 # No loss.
+ duplicate=0 # No duplicates.
+ ;;
+ --mtu)
+ shift
+ [ "$#" -le 0 ] && echo "no mtu provided" && exit 1
+ mtu=$1
+ ;;
+ --sack)
+ netstack_opts="${netstack_opts} -sack"
+ ;;
+ --cubic)
+ netstack_opts="${netstack_opts} -cubic"
+ ;;
+ --moderate-recv-buf)
+ netstack_opts="${netstack_opts} -moderate_recv_buf"
+ ;;
+ --duration)
+ shift
+ [ "$#" -le 0 ] && echo "no duration provided" && exit 1
+ duration=$1
+ ;;
+ --latency)
+ shift
+ [ "$#" -le 0 ] && echo "no latency provided" && exit 1
+ latency=$1
+ ;;
+ --latency-variation)
+ shift
+ [ "$#" -le 0 ] && echo "no latency variation provided" && exit 1
+ latency_variation=$1
+ ;;
+ --loss)
+ shift
+ [ "$#" -le 0 ] && echo "no loss probability provided" && exit 1
+ loss=$1
+ ;;
+ --duplicate)
+ shift
+ [ "$#" -le 0 ] && echo "no duplicate provided" && exit 1
+ duplicate=$1
+ ;;
+ --cpuprofile)
+ shift
+ netstack_opts="${netstack_opts} -cpuprofile=$1"
+ ;;
+ --memprofile)
+ shift
+ netstack_opts="${netstack_opts} -memprofile=$1"
+ ;;
+ --disable-linux-gso)
+ disable_linux_gso=1
+ ;;
+ --num-client-threads)
+ shift
+ num_client_threads=$1
+ ;;
+ --helpers)
+ shift
+ [ "$#" -le 0 ] && echo "no helper dir provided" && exit 1
+ helper_dir=$1
+ ;;
+ *)
+ echo "usage: $0 [options]"
+ echo "options:"
+ echo " --help show this message"
+ echo " --verbose verbose output"
+ echo " --client use netstack as the client"
+ echo " --ideal reset all network emulation"
+ echo " --server use netstack as the server"
+ echo " --mtu set the mtu (bytes)"
+ echo " --sack enable SACK support"
+ echo " --moderate-recv-buf enable TCP receive buffer auto-tuning"
+ echo " --cubic enable CUBIC congestion control for Netstack"
+ echo " --duration set the test duration (s)"
+ echo " --latency set the latency (ms)"
+ echo " --latency-variation set the latency variation"
+ echo " --loss set the loss probability (%)"
+ echo " --duplicate set the duplicate probability (%)"
+ echo " --helpers set the helper directory"
+ echo " --num-client-threads number of parallel client threads to run"
+ echo " --disable-linux-gso disable segmentation offload in the Linux network stack"
+ echo ""
+ echo "The output will of the script will be:"
+ echo " <throughput> <client-cpu-usage> <server-cpu-usage>"
+ exit 1
+ esac
+ shift
+done
+
+if [ ${verbose} == "true" ]; then
+ set -x
+fi
+
+# Latency needs to be halved, since it's applied on both ways.
+half_latency=$(echo ${latency}/2 | bc -l | awk '{printf "%1.2f", $0}')
+half_loss=$(echo ${loss}/2 | bc -l | awk '{printf "%1.6f", $0}')
+half_duplicate=$(echo ${duplicate}/2 | bc -l | awk '{printf "%1.6f", $0}')
+helper_dir=${helper_dir#$(pwd)/} # Use relative paths.
+proxy_binary=${helper_dir}/tcp_proxy
+nsjoin_binary=${helper_dir}/nsjoin
+
+if [ ! -e ${proxy_binary} ]; then
+ echo "Could not locate ${proxy_binary}, please make sure you've built the binary"
+ exit 1
+fi
+
+if [ ! -e ${nsjoin_binary} ]; then
+ echo "Could not locate ${nsjoin_binary}, please make sure you've built the binary"
+ exit 1
+fi
+
+if [ $(echo ${latency_variation} | awk '{printf "%1.2f", $0}') != "0.00" ]; then
+ # As long as there's some jitter, then we use the paretonormal distribution.
+ # This will preserve the minimum RTT, but add a realistic amount of jitter to
+ # the connection and cause re-ordering, etc. The regular pareto distribution
+ # appears to an unreasonable level of delay (we want only small spikes.)
+ distribution="distribution paretonormal"
+else
+ distribution=""
+fi
+
+# Client proxy that will listen on the client's iperf target forward traffic
+# using the host networking stack.
+client_args="${proxy_binary} -port ${proxy_port} -forward ${server_proxy_addr}:${proxy_port}"
+if ${client}; then
+ # Client proxy that will listen on the client's iperf target
+ # and forward traffic using netstack.
+ client_args="${proxy_binary} ${netstack_opts} -port ${proxy_port} -client \\
+ -mtu ${mtu} -iface client.0 -addr ${client_proxy_addr} -mask ${mask} \\
+ -forward ${server_proxy_addr}:${proxy_port} -gso=${gso} -swgso=${swgso}"
+fi
+
+# Server proxy that will listen on the proxy port and forward to the server's
+# iperf server using the host networking stack.
+server_args="${proxy_binary} -port ${proxy_port} -forward ${server_addr}:${iperf_port}"
+if ${server}; then
+ # Server proxy that will listen on the proxy port and forward to the servers'
+ # iperf server using netstack.
+ server_args="${proxy_binary} ${netstack_opts} -port ${proxy_port} -server \\
+ -mtu ${mtu} -iface server.0 -addr ${server_proxy_addr} -mask ${mask} \\
+ -forward ${server_addr}:${iperf_port} -gso=${gso} -swgso=${swgso}"
+fi
+
+# Specify loss and duplicate parameters only if they are non-zero
+loss_opt=""
+if [ "$(echo $half_loss | bc -q)" != "0" ]; then
+ loss_opt="loss random ${half_loss}%"
+fi
+duplicate_opt=""
+if [ "$(echo $half_duplicate | bc -q)" != "0" ]; then
+ duplicate_opt="duplicate ${half_duplicate}%"
+fi
+
+exec unshare -U -m -n -r -f -p --mount-proc /bin/bash << EOF
+set -e -m
+
+if [ ${verbose} == "true" ]; then
+ set -x
+fi
+
+mount -t tmpfs netstack-bench /tmp
+
+# We may have reset the path in the unshare if the shell loaded some public
+# profiles. Ensure that tools are discoverable via the parent's PATH.
+export PATH=${PATH}
+
+# Add client, server interfaces.
+ip link add client.0 type veth peer name client.1
+ip link add server.0 type veth peer name server.1
+
+# Add network emulation devices.
+ip link add wan.0 type veth peer name wan.1
+ip link set wan.0 up
+ip link set wan.1 up
+
+# Enroll on the bridge.
+ip link add name br0 type bridge
+ip link add name br1 type bridge
+ip link set client.1 master br0
+ip link set server.1 master br1
+ip link set wan.0 master br0
+ip link set wan.1 master br1
+ip link set br0 up
+ip link set br1 up
+
+# Set the MTU appropriately.
+ip link set client.0 mtu ${mtu}
+ip link set server.0 mtu ${mtu}
+ip link set wan.0 mtu ${mtu}
+ip link set wan.1 mtu ${mtu}
+
+# Add appropriate latency, loss and duplication.
+#
+# This is added in at the point of bridge connection.
+for device in wan.0 wan.1; do
+ # NOTE: We don't support a loss correlation as testing has shown that it
+ # actually doesn't work. The man page actually has a small comment about this
+ # "It is also possible to add a correlation, but this option is now deprecated
+ # due to the noticed bad behavior." For more information see netem(8).
+ tc qdisc add dev \$device root netem \\
+ delay ${half_latency}ms ${latency_variation}ms ${distribution} \\
+ ${loss_opt} ${duplicate_opt}
+done
+
+# Start a client proxy.
+touch /tmp/client.netns
+unshare -n mount --bind /proc/self/ns/net /tmp/client.netns
+
+# Move the endpoint into the namespace.
+while ip link | grep client.0 > /dev/null; do
+ ip link set dev client.0 netns /tmp/client.netns
+done
+
+if ! ${client}; then
+ # Only add the address to NIC if netstack is not in use. Otherwise the host
+ # will also process the inbound SYN and send a RST back.
+ ${nsjoin_binary} /tmp/client.netns ip addr add ${client_proxy_addr}/${mask} dev client.0
+fi
+
+# Start a server proxy.
+touch /tmp/server.netns
+unshare -n mount --bind /proc/self/ns/net /tmp/server.netns
+# Move the endpoint into the namespace.
+while ip link | grep server.0 > /dev/null; do
+ ip link set dev server.0 netns /tmp/server.netns
+done
+if ! ${server}; then
+ # Only add the address to NIC if netstack is not in use. Otherwise the host
+ # will also process the inbound SYN and send a RST back.
+ ${nsjoin_binary} /tmp/server.netns ip addr add ${server_proxy_addr}/${mask} dev server.0
+fi
+
+# Add client and server addresses, and bring everything up.
+${nsjoin_binary} /tmp/client.netns ip addr add ${client_addr}/${mask} dev client.0
+${nsjoin_binary} /tmp/server.netns ip addr add ${server_addr}/${mask} dev server.0
+if [ "${disable_linux_gso}" == "1" ]; then
+ ${nsjoin_binary} /tmp/client.netns ethtool -K client.0 tso off
+ ${nsjoin_binary} /tmp/client.netns ethtool -K client.0 gro off
+ ${nsjoin_binary} /tmp/client.netns ethtool -K client.0 gso off
+ ${nsjoin_binary} /tmp/server.netns ethtool -K server.0 tso off
+ ${nsjoin_binary} /tmp/server.netns ethtool -K server.0 gso off
+ ${nsjoin_binary} /tmp/server.netns ethtool -K server.0 gro off
+fi
+${nsjoin_binary} /tmp/client.netns ip link set client.0 up
+${nsjoin_binary} /tmp/client.netns ip link set lo up
+${nsjoin_binary} /tmp/server.netns ip link set server.0 up
+${nsjoin_binary} /tmp/server.netns ip link set lo up
+ip link set dev client.1 up
+ip link set dev server.1 up
+
+${nsjoin_binary} /tmp/client.netns ${client_args} &
+client_pid=\$!
+${nsjoin_binary} /tmp/server.netns ${server_args} &
+server_pid=\$!
+
+# Start the iperf server.
+${nsjoin_binary} /tmp/server.netns iperf -p ${iperf_port} -s >&2 &
+iperf_pid=\$!
+
+# Show traffic information.
+if ! ${client} && ! ${server}; then
+ ${nsjoin_binary} /tmp/client.netns ping -c 100 -i 0.001 -W 1 ${server_addr} >&2 || true
+fi
+
+results_file=\$(mktemp)
+function cleanup {
+ rm -f \$results_file
+ kill -TERM \$client_pid
+ kill -TERM \$server_pid
+ wait \$client_pid
+ wait \$server_pid
+ kill -9 \$iperf_pid 2>/dev/null
+}
+
+# Allow failure from this point.
+set +e
+trap cleanup EXIT
+
+# Run the benchmark, recording the results file.
+while ${nsjoin_binary} /tmp/client.netns iperf \\
+ -p ${proxy_port} -c ${client_addr} -t ${duration} -f m -P ${num_client_threads} 2>&1 \\
+ | tee \$results_file \\
+ | grep "connect failed" >/dev/null; do
+ sleep 0.1 # Wait for all services.
+done
+
+# Unlink all relevant devices from the bridge. This is because when the bridge
+# is deleted, the kernel may hang. It appears that this problem is fixed in
+# upstream commit 1ce5cce895309862d2c35d922816adebe094fe4a.
+ip link set client.1 nomaster
+ip link set server.1 nomaster
+ip link set wan.0 nomaster
+ip link set wan.1 nomaster
+
+# Emit raw results.
+cat \$results_file >&2
+
+# Emit a useful result (final throughput).
+mbits=\$(grep Mbits/sec \$results_file \\
+ | sed -n -e 's/^.*[[:space:]]\\([[:digit:]]\\+\\(\\.[[:digit:]]\\+\\)\\?\\)[[:space:]]*Mbits\\/sec.*/\\1/p')
+client_cpu_ticks=\$(cat /proc/\$client_pid/stat \\
+ | awk '{print (\$14+\$15);}')
+server_cpu_ticks=\$(cat /proc/\$server_pid/stat \\
+ | awk '{print (\$14+\$15);}')
+ticks_per_sec=\$(getconf CLK_TCK)
+client_cpu_load=\$(bc -l <<< \$client_cpu_ticks/\$ticks_per_sec/${duration})
+server_cpu_load=\$(bc -l <<< \$server_cpu_ticks/\$ticks_per_sec/${duration})
+echo \$mbits \$client_cpu_load \$server_cpu_load
+EOF
diff --git a/test/benchmarks/tcp/tcp_proxy.go b/test/benchmarks/tcp/tcp_proxy.go
new file mode 100644
index 000000000..4b7ca7a14
--- /dev/null
+++ b/test/benchmarks/tcp/tcp_proxy.go
@@ -0,0 +1,451 @@
+// 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.
+
+// Binary tcp_proxy is a simple TCP proxy.
+package main
+
+import (
+ "encoding/gob"
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "math/rand"
+ "net"
+ "os"
+ "os/signal"
+ "regexp"
+ "runtime"
+ "runtime/pprof"
+ "strconv"
+ "syscall"
+ "time"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip"
+ "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
+ "gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
+ "gvisor.dev/gvisor/pkg/tcpip/link/qdisc/fifo"
+ "gvisor.dev/gvisor/pkg/tcpip/network/arp"
+ "gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
+ "gvisor.dev/gvisor/pkg/tcpip/stack"
+ "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
+ "gvisor.dev/gvisor/pkg/tcpip/transport/udp"
+)
+
+var (
+ port = flag.Int("port", 0, "bind port (all addresses)")
+ forward = flag.String("forward", "", "forwarding target")
+ client = flag.Bool("client", false, "use netstack for listen")
+ server = flag.Bool("server", false, "use netstack for dial")
+
+ // Netstack-specific options.
+ mtu = flag.Int("mtu", 1280, "mtu for network stack")
+ addr = flag.String("addr", "", "address for tap-based netstack")
+ mask = flag.Int("mask", 8, "mask size for address")
+ iface = flag.String("iface", "", "network interface name to bind for netstack")
+ sack = flag.Bool("sack", false, "enable SACK support for netstack")
+ moderateRecvBuf = flag.Bool("moderate_recv_buf", false, "enable TCP Receive Buffer Auto-tuning")
+ cubic = flag.Bool("cubic", false, "enable use of CUBIC congestion control for netstack")
+ gso = flag.Int("gso", 0, "GSO maximum size")
+ swgso = flag.Bool("swgso", false, "software-level GSO")
+ clientTCPProbeFile = flag.String("client_tcp_probe_file", "", "if specified, installs a tcp probe to dump endpoint state to the specified file.")
+ serverTCPProbeFile = flag.String("server_tcp_probe_file", "", "if specified, installs a tcp probe to dump endpoint state to the specified file.")
+ cpuprofile = flag.String("cpuprofile", "", "write cpu profile to the specified file.")
+ memprofile = flag.String("memprofile", "", "write memory profile to the specified file.")
+)
+
+type impl interface {
+ dial(address string) (net.Conn, error)
+ listen(port int) (net.Listener, error)
+ printStats()
+}
+
+type netImpl struct{}
+
+func (netImpl) dial(address string) (net.Conn, error) {
+ return net.Dial("tcp", address)
+}
+
+func (netImpl) listen(port int) (net.Listener, error) {
+ return net.Listen("tcp", fmt.Sprintf(":%d", port))
+}
+
+func (netImpl) printStats() {
+}
+
+const (
+ nicID = 1 // Fixed.
+ bufSize = 4 << 20 // 4MB.
+)
+
+type netstackImpl struct {
+ s *stack.Stack
+ addr tcpip.Address
+ mode string
+}
+
+func setupNetwork(ifaceName string, numChannels int) (fds []int, err error) {
+ // Get all interfaces in the namespace.
+ ifaces, err := net.Interfaces()
+ if err != nil {
+ return nil, fmt.Errorf("querying interfaces: %v", err)
+ }
+
+ for _, iface := range ifaces {
+ if iface.Name != ifaceName {
+ continue
+ }
+ // Create the socket.
+ const protocol = 0x0300 // htons(ETH_P_ALL)
+ fds := make([]int, numChannels)
+ for i := range fds {
+ fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, protocol)
+ if err != nil {
+ return nil, fmt.Errorf("unable to create raw socket: %v", err)
+ }
+
+ // Bind to the appropriate device.
+ ll := syscall.SockaddrLinklayer{
+ Protocol: protocol,
+ Ifindex: iface.Index,
+ Pkttype: syscall.PACKET_HOST,
+ }
+ if err := syscall.Bind(fd, &ll); err != nil {
+ return nil, fmt.Errorf("unable to bind to %q: %v", iface.Name, err)
+ }
+
+ // RAW Sockets by default have a very small SO_RCVBUF of 256KB,
+ // up it to at least 4MB to reduce packet drops.
+ if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_RCVBUF, bufSize); err != nil {
+ return nil, fmt.Errorf("setsockopt(..., SO_RCVBUF, %v,..) = %v", bufSize, err)
+ }
+
+ if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_SNDBUF, bufSize); err != nil {
+ return nil, fmt.Errorf("setsockopt(..., SO_SNDBUF, %v,..) = %v", bufSize, err)
+ }
+
+ if !*swgso && *gso != 0 {
+ if err := syscall.SetsockoptInt(fd, syscall.SOL_PACKET, unix.PACKET_VNET_HDR, 1); err != nil {
+ return nil, fmt.Errorf("unable to enable the PACKET_VNET_HDR option: %v", err)
+ }
+ }
+ fds[i] = fd
+ }
+ return fds, nil
+ }
+ return nil, fmt.Errorf("failed to find interface: %v", ifaceName)
+}
+
+func newNetstackImpl(mode string) (impl, error) {
+ fds, err := setupNetwork(*iface, runtime.GOMAXPROCS(-1))
+ if err != nil {
+ return nil, err
+ }
+
+ // Parse details.
+ parsedAddr := tcpip.Address(net.ParseIP(*addr).To4())
+ parsedDest := tcpip.Address("") // Filled in below.
+ parsedMask := tcpip.AddressMask("") // Filled in below.
+ switch *mask {
+ case 8:
+ parsedDest = tcpip.Address([]byte{parsedAddr[0], 0, 0, 0})
+ parsedMask = tcpip.AddressMask([]byte{0xff, 0, 0, 0})
+ case 16:
+ parsedDest = tcpip.Address([]byte{parsedAddr[0], parsedAddr[1], 0, 0})
+ parsedMask = tcpip.AddressMask([]byte{0xff, 0xff, 0, 0})
+ case 24:
+ parsedDest = tcpip.Address([]byte{parsedAddr[0], parsedAddr[1], parsedAddr[2], 0})
+ parsedMask = tcpip.AddressMask([]byte{0xff, 0xff, 0xff, 0})
+ default:
+ // This is just laziness; we don't expect a different mask.
+ return nil, fmt.Errorf("mask %d not supported", mask)
+ }
+
+ // Create a new network stack.
+ netProtos := []stack.NetworkProtocol{ipv4.NewProtocol(), arp.NewProtocol()}
+ transProtos := []stack.TransportProtocol{tcp.NewProtocol(), udp.NewProtocol()}
+ s := stack.New(stack.Options{
+ NetworkProtocols: netProtos,
+ TransportProtocols: transProtos,
+ })
+
+ // Generate a new mac for the eth device.
+ mac := make(net.HardwareAddr, 6)
+ rand.Read(mac) // Fill with random data.
+ mac[0] &^= 0x1 // Clear multicast bit.
+ mac[0] |= 0x2 // Set local assignment bit (IEEE802).
+ ep, err := fdbased.New(&fdbased.Options{
+ FDs: fds,
+ MTU: uint32(*mtu),
+ EthernetHeader: true,
+ Address: tcpip.LinkAddress(mac),
+ // Enable checksum generation as we need to generate valid
+ // checksums for the veth device to deliver our packets to the
+ // peer. But we do want to disable checksum verification as veth
+ // devices do perform GRO and the linux host kernel may not
+ // regenerate valid checksums after GRO.
+ TXChecksumOffload: false,
+ RXChecksumOffload: true,
+ PacketDispatchMode: fdbased.RecvMMsg,
+ GSOMaxSize: uint32(*gso),
+ SoftwareGSOEnabled: *swgso,
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to create FD endpoint: %v", err)
+ }
+ if err := s.CreateNIC(nicID, fifo.New(ep, runtime.GOMAXPROCS(0), 1000)); err != nil {
+ return nil, fmt.Errorf("error creating NIC %q: %v", *iface, err)
+ }
+ if err := s.AddAddress(nicID, arp.ProtocolNumber, arp.ProtocolAddress); err != nil {
+ return nil, fmt.Errorf("error adding ARP address to %q: %v", *iface, err)
+ }
+ if err := s.AddAddress(nicID, ipv4.ProtocolNumber, parsedAddr); err != nil {
+ return nil, fmt.Errorf("error adding IP address to %q: %v", *iface, err)
+ }
+
+ subnet, err := tcpip.NewSubnet(parsedDest, parsedMask)
+ if err != nil {
+ return nil, fmt.Errorf("tcpip.Subnet(%s, %s): %s", parsedDest, parsedMask, err)
+ }
+ // Add default route; we only support
+ s.SetRouteTable([]tcpip.Route{
+ {
+ Destination: subnet,
+ NIC: nicID,
+ },
+ })
+
+ // Set protocol options.
+ if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, tcp.SACKEnabled(*sack)); err != nil {
+ return nil, fmt.Errorf("SetTransportProtocolOption for SACKEnabled failed: %s", err)
+ }
+
+ // Enable Receive Buffer Auto-Tuning.
+ if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, tcpip.ModerateReceiveBufferOption(*moderateRecvBuf)); err != nil {
+ return nil, fmt.Errorf("SetTransportProtocolOption failed: %s", err)
+ }
+
+ // Set Congestion Control to cubic if requested.
+ if *cubic {
+ if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, tcpip.CongestionControlOption("cubic")); err != nil {
+ return nil, fmt.Errorf("SetTransportProtocolOption for CongestionControlOption(cubic) failed: %s", err)
+ }
+ }
+
+ return netstackImpl{
+ s: s,
+ addr: parsedAddr,
+ mode: mode,
+ }, nil
+}
+
+func (n netstackImpl) dial(address string) (net.Conn, error) {
+ host, port, err := net.SplitHostPort(address)
+ if err != nil {
+ return nil, err
+ }
+ if host == "" {
+ // A host must be provided for the dial.
+ return nil, fmt.Errorf("no host provided")
+ }
+ portNumber, err := strconv.Atoi(port)
+ if err != nil {
+ return nil, err
+ }
+ addr := tcpip.FullAddress{
+ NIC: nicID,
+ Addr: tcpip.Address(net.ParseIP(host).To4()),
+ Port: uint16(portNumber),
+ }
+ conn, err := gonet.DialTCP(n.s, addr, ipv4.ProtocolNumber)
+ if err != nil {
+ return nil, err
+ }
+ return conn, nil
+}
+
+func (n netstackImpl) listen(port int) (net.Listener, error) {
+ addr := tcpip.FullAddress{
+ NIC: nicID,
+ Port: uint16(port),
+ }
+ listener, err := gonet.ListenTCP(n.s, addr, ipv4.ProtocolNumber)
+ if err != nil {
+ return nil, err
+ }
+ return listener, nil
+}
+
+var zeroFieldsRegexp = regexp.MustCompile(`\s*[a-zA-Z0-9]*:0`)
+
+func (n netstackImpl) printStats() {
+ // Don't show zero fields.
+ stats := zeroFieldsRegexp.ReplaceAllString(fmt.Sprintf("%+v", n.s.Stats()), "")
+ log.Printf("netstack %s Stats: %+v\n", n.mode, stats)
+}
+
+// installProbe installs a TCP Probe function that will dump endpoint
+// state to the specified file. It also returns a close func() that
+// can be used to close the probeFile.
+func (n netstackImpl) installProbe(probeFileName string) (close func()) {
+ // Install Probe to dump out end point state.
+ probeFile, err := os.Create(probeFileName)
+ if err != nil {
+ log.Fatalf("failed to create tcp_probe file %s: %v", probeFileName, err)
+ }
+ probeEncoder := gob.NewEncoder(probeFile)
+ // Install a TCP Probe.
+ n.s.AddTCPProbe(func(state stack.TCPEndpointState) {
+ probeEncoder.Encode(state)
+ })
+ return func() { probeFile.Close() }
+}
+
+func main() {
+ flag.Parse()
+ if *port == 0 {
+ log.Fatalf("no port provided")
+ }
+ if *forward == "" {
+ log.Fatalf("no forward provided")
+ }
+ // Seed the random number generator to ensure that we are given MAC addresses that don't
+ // for the case of the client and server stack.
+ rand.Seed(time.Now().UTC().UnixNano())
+
+ if *cpuprofile != "" {
+ f, err := os.Create(*cpuprofile)
+ if err != nil {
+ log.Fatal("could not create CPU profile: ", err)
+ }
+ defer func() {
+ if err := f.Close(); err != nil {
+ log.Print("error closing CPU profile: ", err)
+ }
+ }()
+ if err := pprof.StartCPUProfile(f); err != nil {
+ log.Fatal("could not start CPU profile: ", err)
+ }
+ defer pprof.StopCPUProfile()
+ }
+
+ var (
+ in impl
+ out impl
+ err error
+ )
+ if *server {
+ in, err = newNetstackImpl("server")
+ if *serverTCPProbeFile != "" {
+ defer in.(netstackImpl).installProbe(*serverTCPProbeFile)()
+ }
+
+ } else {
+ in = netImpl{}
+ }
+ if err != nil {
+ log.Fatalf("netstack error: %v", err)
+ }
+ if *client {
+ out, err = newNetstackImpl("client")
+ if *clientTCPProbeFile != "" {
+ defer out.(netstackImpl).installProbe(*clientTCPProbeFile)()
+ }
+ } else {
+ out = netImpl{}
+ }
+ if err != nil {
+ log.Fatalf("netstack error: %v", err)
+ }
+
+ // Dial forward before binding.
+ var next net.Conn
+ for {
+ next, err = out.dial(*forward)
+ if err == nil {
+ break
+ }
+ time.Sleep(50 * time.Millisecond)
+ log.Printf("connect failed retrying: %v", err)
+ }
+
+ // Bind once to the server socket.
+ listener, err := in.listen(*port)
+ if err != nil {
+ // Should not happen, everything must be bound by this time
+ // this proxy is started.
+ log.Fatalf("unable to listen: %v", err)
+ }
+ log.Printf("client=%v, server=%v, ready.", *client, *server)
+
+ sigs := make(chan os.Signal, 1)
+ signal.Notify(sigs, syscall.SIGTERM)
+ go func() {
+ <-sigs
+ if *cpuprofile != "" {
+ pprof.StopCPUProfile()
+ }
+ if *memprofile != "" {
+ f, err := os.Create(*memprofile)
+ if err != nil {
+ log.Fatal("could not create memory profile: ", err)
+ }
+ defer func() {
+ if err := f.Close(); err != nil {
+ log.Print("error closing memory profile: ", err)
+ }
+ }()
+ runtime.GC() // get up-to-date statistics
+ if err := pprof.WriteHeapProfile(f); err != nil {
+ log.Fatalf("Unable to write heap profile: %v", err)
+ }
+ }
+ os.Exit(0)
+ }()
+
+ for {
+ // Forward all connections.
+ inConn, err := listener.Accept()
+ if err != nil {
+ // This should not happen; we are listening
+ // successfully. Exhausted all available FDs?
+ log.Fatalf("accept error: %v", err)
+ }
+ log.Printf("incoming connection established.")
+
+ // Copy both ways.
+ go io.Copy(inConn, next)
+ go io.Copy(next, inConn)
+
+ // Print stats every second.
+ go func() {
+ t := time.NewTicker(time.Second)
+ defer t.Stop()
+ for {
+ <-t.C
+ in.printStats()
+ out.printStats()
+ }
+ }()
+
+ for {
+ // Dial again.
+ next, err = out.dial(*forward)
+ if err == nil {
+ break
+ }
+ }
+ }
+}
diff --git a/test/benchmarks/tools/BUILD b/test/benchmarks/tools/BUILD
index a6bd949e6..e5734d85c 100644
--- a/test/benchmarks/tools/BUILD
+++ b/test/benchmarks/tools/BUILD
@@ -9,6 +9,7 @@ go_library(
"fio.go",
"hey.go",
"iperf.go",
+ "meminfo.go",
"redis.go",
"sysbench.go",
"tools.go",
@@ -24,6 +25,7 @@ go_test(
"fio_test.go",
"hey_test.go",
"iperf_test.go",
+ "meminfo_test.go",
"redis_test.go",
"sysbench_test.go",
],
diff --git a/test/benchmarks/tools/hey.go b/test/benchmarks/tools/hey.go
index 699497c64..b1e20e356 100644
--- a/test/benchmarks/tools/hey.go
+++ b/test/benchmarks/tools/hey.go
@@ -25,7 +25,7 @@ import (
// Hey is for the client application 'hey'.
type Hey struct {
- Requests int
+ Requests int // Note: requests cannot be less than concurrency.
Concurrency int
Doc string
}
diff --git a/test/benchmarks/tools/meminfo.go b/test/benchmarks/tools/meminfo.go
new file mode 100644
index 000000000..2414a96a7
--- /dev/null
+++ b/test/benchmarks/tools/meminfo.go
@@ -0,0 +1,60 @@
+// Copyright 2020 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tools
+
+import (
+ "fmt"
+ "regexp"
+ "strconv"
+ "testing"
+)
+
+// Meminfo wraps measurements of MemAvailable using /proc/meminfo.
+type Meminfo struct {
+}
+
+// MakeCmd returns a command for checking meminfo.
+func (*Meminfo) MakeCmd() (string, []string) {
+ return "cat", []string{"/proc/meminfo"}
+}
+
+// Report takes two reads of meminfo, parses them, and reports the difference
+// divided by b.N.
+func (*Meminfo) Report(b *testing.B, before, after string) {
+ b.Helper()
+
+ beforeVal, err := parseMemAvailable(before)
+ if err != nil {
+ b.Fatalf("could not parse before value %s: %v", before, err)
+ }
+
+ afterVal, err := parseMemAvailable(after)
+ if err != nil {
+ b.Fatalf("could not parse before value %s: %v", before, err)
+ }
+ val := 1024 * ((beforeVal - afterVal) / float64(b.N))
+ b.ReportMetric(val, "average_container_size_bytes")
+}
+
+var memInfoRE = regexp.MustCompile(`MemAvailable:\s*(\d+)\skB\n`)
+
+// parseMemAvailable grabs the MemAvailable number from /proc/meminfo.
+func parseMemAvailable(data string) (float64, error) {
+ match := memInfoRE.FindStringSubmatch(data)
+ if len(match) < 2 {
+ return 0, fmt.Errorf("couldn't find MemAvailable in %s", data)
+ }
+ return strconv.ParseFloat(match[1], 64)
+}
diff --git a/test/benchmarks/tools/meminfo_test.go b/test/benchmarks/tools/meminfo_test.go
new file mode 100644
index 000000000..ba803540f
--- /dev/null
+++ b/test/benchmarks/tools/meminfo_test.go
@@ -0,0 +1,84 @@
+// Copyright 2020 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tools
+
+import (
+ "testing"
+)
+
+// TestMeminfo checks the Meminfo parser on sample output.
+func TestMeminfo(t *testing.T) {
+ sampleData := `
+MemTotal: 16337408 kB
+MemFree: 3742696 kB
+MemAvailable: 9319948 kB
+Buffers: 1433884 kB
+Cached: 4607036 kB
+SwapCached: 45284 kB
+Active: 8288376 kB
+Inactive: 2685928 kB
+Active(anon): 4724912 kB
+Inactive(anon): 1047940 kB
+Active(file): 3563464 kB
+Inactive(file): 1637988 kB
+Unevictable: 326940 kB
+Mlocked: 48 kB
+SwapTotal: 33292284 kB
+SwapFree: 32865736 kB
+Dirty: 708 kB
+Writeback: 0 kB
+AnonPages: 4304204 kB
+Mapped: 975424 kB
+Shmem: 910292 kB
+KReclaimable: 744532 kB
+Slab: 1058448 kB
+SReclaimable: 744532 kB
+SUnreclaim: 313916 kB
+KernelStack: 25188 kB
+PageTables: 65300 kB
+NFS_Unstable: 0 kB
+Bounce: 0 kB
+WritebackTmp: 0 kB
+CommitLimit: 41460988 kB
+Committed_AS: 22859492 kB
+VmallocTotal: 34359738367 kB
+VmallocUsed: 63088 kB
+VmallocChunk: 0 kB
+Percpu: 9248 kB
+HardwareCorrupted: 0 kB
+AnonHugePages: 786432 kB
+ShmemHugePages: 0 kB
+ShmemPmdMapped: 0 kB
+FileHugePages: 0 kB
+FilePmdMapped: 0 kB
+HugePages_Total: 0
+HugePages_Free: 0
+HugePages_Rsvd: 0
+HugePages_Surp: 0
+Hugepagesize: 2048 kB
+Hugetlb: 0 kB
+DirectMap4k: 5408532 kB
+DirectMap2M: 11241472 kB
+DirectMap1G: 1048576 kB
+`
+ want := 9319948.0
+ got, err := parseMemAvailable(sampleData)
+ if err != nil {
+ t.Fatalf("parseMemAvailable failed: %v", err)
+ }
+ if got != want {
+ t.Fatalf("parseMemAvailable got %f, want %f", got, want)
+ }
+}
diff --git a/test/e2e/integration_test.go b/test/e2e/integration_test.go
index 6fe6d304f..71ec4791e 100644
--- a/test/e2e/integration_test.go
+++ b/test/e2e/integration_test.go
@@ -467,6 +467,24 @@ func TestHostOverlayfsRewindDir(t *testing.T) {
}
}
+// Basic test for linkat(2). Syscall tests requires CAP_DAC_READ_SEARCH and it
+// cannot use tricks like userns as root. For this reason, run a basic link test
+// to ensure some coverage.
+func TestLink(t *testing.T) {
+ ctx := context.Background()
+ d := dockerutil.MakeContainer(ctx, t)
+ defer d.CleanUp(ctx)
+
+ if got, err := d.Run(ctx, dockerutil.RunOpts{
+ Image: "basic/linktest",
+ WorkDir: "/root",
+ }, "./link_test"); err != nil {
+ t.Fatalf("docker run failed: %v", err)
+ } else if got != "" {
+ t.Errorf("test failed:\n%s", got)
+ }
+}
+
func TestMain(m *testing.M) {
dockerutil.EnsureSupportedDockerVersion()
flag.Parse()
diff --git a/test/fuse/linux/fuse_base.cc b/test/fuse/linux/fuse_base.cc
index 6c8432fd0..ce69276c9 100644
--- a/test/fuse/linux/fuse_base.cc
+++ b/test/fuse/linux/fuse_base.cc
@@ -100,8 +100,11 @@ PosixError FuseTest::ConsumeFuseInit() {
.error = 0,
.unique = 2,
};
- // Returns an empty init out payload since this is just a test.
- struct fuse_init_out out_payload;
+ // Returns a fake fuse_init_out with 7.0 version to avoid ECONNREFUSED
+ // error in the initialization of FUSE connection.
+ struct fuse_init_out out_payload = {
+ .major = 7,
+ };
iov_out[0].iov_len = sizeof(out_header);
iov_out[0].iov_base = &out_header;
iov_out[1].iov_len = sizeof(out_payload);
diff --git a/test/packetdrill/BUILD b/test/packetdrill/BUILD
index dfcd55f60..49642f282 100644
--- a/test/packetdrill/BUILD
+++ b/test/packetdrill/BUILD
@@ -1,4 +1,5 @@
-load("defs.bzl", "packetdrill_test")
+load("//tools:defs.bzl", "bzl_library")
+load("//test/packetdrill:defs.bzl", "packetdrill_test")
package(licenses = ["notice"])
@@ -36,3 +37,9 @@ packetdrill_test(
name = "tcp_defer_accept_timeout_test",
scripts = ["tcp_defer_accept_timeout.pkt"],
)
+
+bzl_library(
+ name = "defs_bzl",
+ srcs = ["defs.bzl"],
+ visibility = ["//visibility:private"],
+)
diff --git a/test/packetimpact/runner/BUILD b/test/packetimpact/runner/BUILD
index bad4f0183..ff2be9b30 100644
--- a/test/packetimpact/runner/BUILD
+++ b/test/packetimpact/runner/BUILD
@@ -1,4 +1,4 @@
-load("//tools:defs.bzl", "go_test")
+load("//tools:defs.bzl", "bzl_library", "go_test")
package(
default_visibility = ["//test/packetimpact:__subpackages__"],
@@ -19,3 +19,9 @@ go_test(
"@com_github_docker_docker//api/types/mount:go_default_library",
],
)
+
+bzl_library(
+ name = "defs_bzl",
+ srcs = ["defs.bzl"],
+ visibility = ["//visibility:private"],
+)
diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl
index 79b3c9162..93a36c6c2 100644
--- a/test/packetimpact/runner/defs.bzl
+++ b/test/packetimpact/runner/defs.bzl
@@ -61,12 +61,12 @@ PACKETIMPACT_TAGS = [
"packetimpact",
]
-def packetimpact_linux_test(
+def packetimpact_native_test(
name,
testbench_binary,
expect_failure = False,
**kwargs):
- """Add a packetimpact test on linux.
+ """Add a native packetimpact test.
Args:
name: name of the test
@@ -76,9 +76,9 @@ def packetimpact_linux_test(
"""
expect_failure_flag = ["--expect_failure"] if expect_failure else []
_packetimpact_test(
- name = name + "_linux_test",
+ name = name + "_native_test",
testbench_binary = testbench_binary,
- flags = ["--dut_platform", "linux"] + expect_failure_flag,
+ flags = ["--native"] + expect_failure_flag,
tags = PACKETIMPACT_TAGS,
**kwargs
)
@@ -102,21 +102,21 @@ def packetimpact_netstack_test(
_packetimpact_test(
name = name + "_netstack_test",
testbench_binary = testbench_binary,
- # This is the default runtime unless
- # "--test_arg=--runtime=OTHER_RUNTIME" is used to override the value.
- flags = ["--dut_platform", "netstack", "--runtime=runsc-d"] + expect_failure_flag,
+ # Note that a distinct runtime must be provided in the form
+ # --test_arg=--runtime=other when invoking bazel.
+ flags = expect_failure_flag,
tags = PACKETIMPACT_TAGS,
**kwargs
)
-def packetimpact_go_test(name, size = "small", pure = True, expect_linux_failure = False, expect_netstack_failure = False, **kwargs):
+def packetimpact_go_test(name, size = "small", pure = True, expect_native_failure = False, expect_netstack_failure = False, **kwargs):
"""Add packetimpact tests written in go.
Args:
name: name of the test
size: size of the test
pure: make a static go binary
- expect_linux_failure: the test must fail for Linux
+ expect_native_failure: the test must fail natively
expect_netstack_failure: the test must fail for Netstack
**kwargs: all the other args, forwarded to go_test
"""
@@ -131,9 +131,9 @@ def packetimpact_go_test(name, size = "small", pure = True, expect_linux_failure
],
**kwargs
)
- packetimpact_linux_test(
+ packetimpact_native_test(
name = name,
- expect_failure = expect_linux_failure,
+ expect_failure = expect_native_failure,
testbench_binary = testbench_binary,
)
packetimpact_netstack_test(
diff --git a/test/packetimpact/runner/packetimpact_test.go b/test/packetimpact/runner/packetimpact_test.go
index 74e1e6def..e8c183977 100644
--- a/test/packetimpact/runner/packetimpact_test.go
+++ b/test/packetimpact/runner/packetimpact_test.go
@@ -50,7 +50,7 @@ func (l *stringList) Set(value string) error {
}
var (
- dutPlatform = flag.String("dut_platform", "", "either \"linux\" or \"netstack\"")
+ native = flag.Bool("native", false, "whether the test should be run natively")
testbenchBinary = flag.String("testbench_binary", "", "path to the testbench binary")
tshark = flag.Bool("tshark", false, "use more verbose tshark in logs instead of tcpdump")
extraTestArgs = stringList{}
@@ -84,17 +84,9 @@ func (l logger) Logf(format string, args ...interface{}) {
func TestOne(t *testing.T) {
flag.Var(&extraTestArgs, "extra_test_arg", "extra arguments to pass to the testbench")
flag.Parse()
- if *dutPlatform != "linux" && *dutPlatform != "netstack" {
- t.Fatal("--dut_platform should be either linux or netstack")
- }
if *testbenchBinary == "" {
t.Fatal("--testbench_binary is missing")
}
- if *dutPlatform == "netstack" {
- if _, err := dockerutil.RuntimePath(); err != nil {
- t.Fatal("--runtime is missing or invalid with --dut_platform=netstack:", err)
- }
- }
dockerutil.EnsureSupportedDockerVersion()
ctx := context.Background()
@@ -140,9 +132,11 @@ func TestOne(t *testing.T) {
const testOutputDir = "/tmp/testoutput"
// Create the Docker container for the DUT.
- dut := dockerutil.MakeContainer(ctx, logger("dut"))
- if *dutPlatform == "linux" {
+ var dut *dockerutil.Container
+ if *native {
dut = dockerutil.MakeNativeContainer(ctx, logger("dut"))
+ } else {
+ dut = dockerutil.MakeContainer(ctx, logger("dut"))
}
runOpts := dockerutil.RunOpts{
@@ -307,7 +301,7 @@ func TestOne(t *testing.T) {
"--remote_mac", remoteMAC.String(),
"--remote_interface_id", fmt.Sprintf("%d", dutDeviceInfo.ID),
"--device", testNetDev,
- "--dut_type", *dutPlatform,
+ fmt.Sprintf("--native=%t", *native),
)
testbenchLogs, err := testbench.Exec(ctx, dockerutil.ExecOpts{}, testArgs...)
if (err != nil) != *expectFailure {
diff --git a/test/packetimpact/testbench/testbench.go b/test/packetimpact/testbench/testbench.go
index 242464e3a..e3629e1f3 100644
--- a/test/packetimpact/testbench/testbench.go
+++ b/test/packetimpact/testbench/testbench.go
@@ -27,8 +27,8 @@ import (
)
var (
- // DUTType is the type of device under test.
- DUTType = ""
+ // Native indicates that the test is being run natively.
+ Native = false
// Device is the local device on the test network.
Device = ""
@@ -81,7 +81,7 @@ func RegisterFlags(fs *flag.FlagSet) {
fs.StringVar(&RemoteIPv6, "remote_ipv6", RemoteIPv6, "remote IPv6 address for test packets")
fs.StringVar(&RemoteMAC, "remote_mac", RemoteMAC, "remote mac address for test packets")
fs.StringVar(&Device, "device", Device, "local device for test packets")
- fs.StringVar(&DUTType, "dut_type", DUTType, "type of device under test")
+ fs.BoolVar(&Native, "native", Native, "whether the test is running natively")
fs.Uint64Var(&RemoteInterfaceID, "remote_interface_id", RemoteInterfaceID, "remote interface ID for test packets")
}
diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD
index 0c2a05380..74658fea0 100644
--- a/test/packetimpact/tests/BUILD
+++ b/test/packetimpact/tests/BUILD
@@ -40,8 +40,6 @@ packetimpact_go_test(
packetimpact_go_test(
name = "udp_recv_mcast_bcast",
srcs = ["udp_recv_mcast_bcast_test.go"],
- # TODO(b/152813495): Fix netstack then remove the line below.
- expect_netstack_failure = True,
deps = [
"//pkg/tcpip",
"//pkg/tcpip/header",
diff --git a/test/packetimpact/tests/tcp_reordering_test.go b/test/packetimpact/tests/tcp_reordering_test.go
index 8742819ca..b4aeaab57 100644
--- a/test/packetimpact/tests/tcp_reordering_test.go
+++ b/test/packetimpact/tests/tcp_reordering_test.go
@@ -54,13 +54,13 @@ func TestReorderingWindow(t *testing.T) {
acceptFd, _ := dut.Accept(t, listenFd)
defer dut.Close(t, acceptFd)
- if tb.DUTType == "linux" {
+ if tb.Native {
// Linux has changed its handling of reordering, force the old behavior.
dut.SetSockOpt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_CONGESTION, []byte("reno"))
}
pls := dut.GetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_MAXSEG)
- if tb.DUTType == "netstack" {
+ if !tb.Native {
// netstack does not impliment TCP_MAXSEG correctly. Fake it
// here. Netstack uses the max SACK size which is 32. The MSS
// option is 8 bytes, making the total 36 bytes.
@@ -141,7 +141,7 @@ func TestReorderingWindow(t *testing.T) {
}
}
- if tb.DUTType == "netstack" {
+ if !tb.Native {
// The window should now be halved, so we should receive any
// more, even if we send them.
dut.Send(t, acceptFd, payload, 0)
diff --git a/test/runner/BUILD b/test/runner/BUILD
index 63c7ec83a..582d2946d 100644
--- a/test/runner/BUILD
+++ b/test/runner/BUILD
@@ -1,4 +1,4 @@
-load("//tools:defs.bzl", "go_binary")
+load("//tools:defs.bzl", "bzl_library", "go_binary")
package(licenses = ["notice"])
@@ -21,3 +21,9 @@ go_binary(
"@org_golang_x_sys//unix:go_default_library",
],
)
+
+bzl_library(
+ name = "defs_bzl",
+ srcs = ["defs.bzl"],
+ visibility = ["//visibility:private"],
+)
diff --git a/test/runtimes/BUILD b/test/runtimes/BUILD
index 3be123d94..066338ee3 100644
--- a/test/runtimes/BUILD
+++ b/test/runtimes/BUILD
@@ -1,3 +1,4 @@
+load("//tools:defs.bzl", "bzl_library")
load("//test/runtimes:defs.bzl", "runtime_test")
package(licenses = ["notice"])
@@ -6,7 +7,7 @@ runtime_test(
name = "go1.12",
exclude_file = "exclude_go1.12.csv",
lang = "go",
- shard_count = 5,
+ shard_count = 8,
)
runtime_test(
@@ -14,26 +15,32 @@ runtime_test(
batch = 100,
exclude_file = "exclude_java11.csv",
lang = "java",
- shard_count = 20,
+ shard_count = 16,
)
runtime_test(
name = "nodejs12.4.0",
exclude_file = "exclude_nodejs12.4.0.csv",
lang = "nodejs",
- shard_count = 10,
+ shard_count = 8,
)
runtime_test(
name = "php7.3.6",
exclude_file = "exclude_php7.3.6.csv",
lang = "php",
- shard_count = 5,
+ shard_count = 8,
)
runtime_test(
name = "python3.7.3",
exclude_file = "exclude_python3.7.3.csv",
lang = "python",
- shard_count = 5,
+ shard_count = 8,
+)
+
+bzl_library(
+ name = "defs_bzl",
+ srcs = ["defs.bzl"],
+ visibility = ["//visibility:private"],
)
diff --git a/test/runtimes/defs.bzl b/test/runtimes/defs.bzl
index db22029a8..702522d86 100644
--- a/test/runtimes/defs.bzl
+++ b/test/runtimes/defs.bzl
@@ -55,9 +55,13 @@ _runtime_test = rule(
),
"_runner": attr.label(
default = "//test/runtimes/runner:runner",
+ executable = True,
+ cfg = "target",
),
"_proctor": attr.label(
default = "//test/runtimes/proctor:proctor",
+ executable = True,
+ cfg = "target",
),
},
test = True,
diff --git a/test/runtimes/exclude_java11.csv b/test/runtimes/exclude_java11.csv
index 4d62f7d3a..997a29cad 100644
--- a/test/runtimes/exclude_java11.csv
+++ b/test/runtimes/exclude_java11.csv
@@ -18,8 +18,16 @@ java/lang/ClassLoader/nativeLibrary/NativeLibraryTest.java,,Fails in Docker
java/lang/module/ModuleDescriptorTest.java,,
java/lang/String/nativeEncoding/StringPlatformChars.java,,
java/net/CookieHandler/B6791927.java,,java.lang.RuntimeException: Expiration date shouldn't be 0
+java/net/ipv6tests/TcpTest.java,,java.net.ConnectException: Connection timed out (Connection timed out)
+java/net/ipv6tests/UdpTest.java,,Times out
+java/net/Inet6Address/B6558853.java,,Times out
+java/net/InetAddress/CheckJNI.java,,java.net.ConnectException: Connection timed out (Connection timed out)
java/net/InterfaceAddress/NetworkPrefixLength.java,b/78507103,
+java/net/MulticastSocket/B6425815.java,,java.net.SocketException: Protocol not available (Error getting socket option)
+java/net/MulticastSocket/B6427403.java,,java.net.SocketException: Protocol not available
java/net/MulticastSocket/MulticastTTL.java,,
+java/net/MulticastSocket/NetworkInterfaceEmptyGetInetAddressesTest.java,,java.net.SocketException: Protocol not available (Error getting socket option)
+java/net/MulticastSocket/NoLoopbackPackets.java,,java.net.SocketException: Protocol not available
java/net/MulticastSocket/Promiscuous.java,,
java/net/MulticastSocket/SetLoopbackMode.java,,
java/net/MulticastSocket/SetTTLAndGetTTL.java,,
@@ -27,6 +35,7 @@ java/net/MulticastSocket/Test.java,,
java/net/MulticastSocket/TestDefaults.java,,
java/net/MulticastSocket/TimeToLive.java,,
java/net/NetworkInterface/NetworkInterfaceStreamTest.java,,
+java/net/Socket/LinkLocal.java,,java.net.SocketTimeoutException: Receive timed out
java/net/Socket/SetSoLinger.java,b/78527327,SO_LINGER is not yet supported
java/net/Socket/UrgentDataTest.java,b/111515323,
java/net/SocketOption/OptionsTest.java,,Fails in Docker
@@ -167,6 +176,10 @@ sun/management/jmxremote/bootstrap/RmiSslBootstrapTest.sh,,
sun/management/jmxremote/startstop/JMXStartStopTest.java,,
sun/management/jmxremote/startstop/JMXStatusPerfCountersTest.java,,
sun/management/jmxremote/startstop/JMXStatusTest.java,,
+sun/management/jdp/JdpDefaultsTest.java,,
+sun/management/jdp/JdpJmxRemoteDynamicPortTest.java,,
+sun/management/jdp/JdpOffTest.java,,
+sun/management/jdp/JdpSpecificAddressTest.java,,
sun/text/resources/LocaleDataTest.java,,
sun/tools/jcmd/TestJcmdSanity.java,,
sun/tools/jhsdb/AlternateHashingTest.java,,
diff --git a/test/runtimes/exclude_nodejs12.4.0.csv b/test/runtimes/exclude_nodejs12.4.0.csv
index 525117f5c..1d8e65fd0 100644
--- a/test/runtimes/exclude_nodejs12.4.0.csv
+++ b/test/runtimes/exclude_nodejs12.4.0.csv
@@ -15,6 +15,7 @@ parallel/test-fs-write-stream.js,,Flaky
parallel/test-fs-write-stream-throw-type-error.js,b/110226209,
parallel/test-http-writable-true-after-close.js,,Flaky - Mismatched <anonymous> function calls. Expected exactly 1 actual 2
parallel/test-os.js,b/63997097,
+parallel/test-net-server-listen-options.js,,Flaky - EADDRINUSE
parallel/test-process-uid-gid.js,,
parallel/test-tls-cli-min-version-1.0.js,,Flaky - EADDRINUSE
parallel/test-tls-cli-min-version-1.1.js,,Flaky - EADDRINUSE
@@ -45,6 +46,7 @@ pseudo-tty/test-tty-stdout-resize.js,b/162801321,
pseudo-tty/test-tty-stream-constructors.js,b/162801321,
pseudo-tty/test-tty-window-size.js,b/162801321,
pseudo-tty/test-tty-wrap.js,b/162801321,
+pummel/test-heapdump-http2.js,,Flaky
pummel/test-net-pingpong.js,,
pummel/test-vm-memleak.js,b/162799436,
sequential/test-child-process-pass-fd.js,b/63926391,Flaky
diff --git a/test/runtimes/exclude_php7.3.6.csv b/test/runtimes/exclude_php7.3.6.csv
index e828f91dd..2ce979dc8 100644
--- a/test/runtimes/exclude_php7.3.6.csv
+++ b/test/runtimes/exclude_php7.3.6.csv
@@ -11,6 +11,7 @@ ext/mbstring/tests/mb_strrpos_encoding_3rd_param.phpt,,
ext/session/tests/session_module_name_variation4.phpt,,Flaky
ext/session/tests/session_set_save_handler_class_018.phpt,,
ext/session/tests/session_set_save_handler_iface_003.phpt,,
+ext/session/tests/session_set_save_handler_sid_001.phpt,,
ext/session/tests/session_set_save_handler_variation4.phpt,,
ext/standard/tests/file/fopen_variation19.phpt,b/162894964,
ext/standard/tests/file/lstat_stat_variation14.phpt,,Flaky
diff --git a/test/runtimes/exclude_python3.7.3.csv b/test/runtimes/exclude_python3.7.3.csv
index 3580b25c3..8760f8951 100644
--- a/test/runtimes/exclude_python3.7.3.csv
+++ b/test/runtimes/exclude_python3.7.3.csv
@@ -1,28 +1,21 @@
test name,bug id,comment
-test_asynchat,b/76031995,SO_REUSEADDR
test_asyncio,,Fails on Docker.
-test_asyncore,b/76031995,SO_REUSEADDR
-test_epoll,,
-test_fcntl,,fcntl invalid argument -- artificial test to make sure something works in 64 bit mode.
-test_ftplib,,Fails in Docker
-test_httplib,b/76031995,SO_REUSEADDR
-test_imaplib,,
-test_logging,,
+test_asyncore,b/162973328,
+test_epoll,b/162983393,
+test_fcntl,b/162978767,fcntl invalid argument -- artificial test to make sure something works in 64 bit mode.
+test_httplib,b/163000009,OSError: [Errno 98] Address already in use
+test_imaplib,b/162979661,
+test_logging,b/162980079,
test_multiprocessing_fork,,Flaky. Sometimes times out.
test_multiprocessing_forkserver,,Flaky. Sometimes times out.
test_multiprocessing_main_handling,,Flaky. Sometimes times out.
test_multiprocessing_spawn,,Flaky. Sometimes times out.
-test_nntplib,b/76031995,tests should not set SO_REUSEADDR
-test_poplib,,Fails on Docker
test_posix,b/76174079,posix.sched_get_priority_min not implemented + posix.sched_rr_get_interval not permitted
-test_pty,b/76157709,out of pty devices
-test_readline,b/76157709,out of pty devices
+test_pty,b/162979921,
+test_readline,b/162980389,TestReadline hangs forever
test_resource,b/76174079,
test_selectors,b/76116849,OSError not raised with epoll
-test_smtplib,b/76031995,SO_REUSEADDR and unclosed sockets
+test_smtplib,b/162980434,unclosed sockets
test_signal,,Flaky - signal: alarm clock
test_socket,b/75983380,
-test_ssl,b/76031995,SO_REUSEADDR
-test_subprocess,,
-test_support,b/76031995,SO_REUSEADDR
-test_telnetlib,b/76031995,SO_REUSEADDR
+test_subprocess,b/162980831,
diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD
index c19b30b4a..a31612b41 100644
--- a/test/syscalls/BUILD
+++ b/test/syscalls/BUILD
@@ -1023,6 +1023,7 @@ syscall_test(
syscall_test(
add_overlay = True,
test = "//test/syscalls/linux:truncate_test",
+ vfs2 = "True",
)
syscall_test(
diff --git a/test/syscalls/linux/inotify.cc b/test/syscalls/linux/inotify.cc
index 6cf398097..5cb325a9e 100644
--- a/test/syscalls/linux/inotify.cc
+++ b/test/syscalls/linux/inotify.cc
@@ -1693,7 +1693,7 @@ TEST(Inotify, Fallocate) {
InotifyAddWatch(inotify_fd.get(), file.path(), IN_ALL_EVENTS));
// Do an arbitrary modification with fallocate.
- ASSERT_THAT(fallocate(fd.get(), 0, 0, 123), SyscallSucceeds());
+ ASSERT_THAT(RetryEINTR(fallocate)(fd.get(), 0, 0, 123), SyscallSucceeds());
std::vector<Event> events =
ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
EXPECT_THAT(events, Are({Event(IN_MODIFY, wd)}));
diff --git a/test/syscalls/linux/proc_net.cc b/test/syscalls/linux/proc_net.cc
index 3377b65cf..4fab097f4 100644
--- a/test/syscalls/linux/proc_net.cc
+++ b/test/syscalls/linux/proc_net.cc
@@ -477,6 +477,44 @@ TEST(ProcNetSnmp, CheckSnmp) {
EXPECT_EQ(value_count, 1);
}
+TEST(ProcSysNetIpv4Recovery, Exists) {
+ EXPECT_THAT(open("/proc/sys/net/ipv4/tcp_recovery", O_RDONLY),
+ SyscallSucceeds());
+}
+
+TEST(ProcSysNetIpv4Recovery, CanReadAndWrite) {
+ // TODO(b/162988252): Enable save/restore for this test after the bug is
+ // fixed.
+ DisableSave ds;
+
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_DAC_OVERRIDE))));
+
+ auto const fd = ASSERT_NO_ERRNO_AND_VALUE(
+ Open("/proc/sys/net/ipv4/tcp_recovery", O_RDWR));
+
+ char buf[10] = {'\0'};
+ char to_write = '2';
+
+ // Check initial value is set to 1.
+ EXPECT_THAT(PreadFd(fd.get(), &buf, sizeof(buf), 0),
+ SyscallSucceedsWithValue(sizeof(to_write) + 1));
+ EXPECT_EQ(strcmp(buf, "1\n"), 0);
+
+ // Set tcp_recovery to one of the allowed constants.
+ EXPECT_THAT(PwriteFd(fd.get(), &to_write, sizeof(to_write), 0),
+ SyscallSucceedsWithValue(sizeof(to_write)));
+ EXPECT_THAT(PreadFd(fd.get(), &buf, sizeof(buf), 0),
+ SyscallSucceedsWithValue(sizeof(to_write) + 1));
+ EXPECT_EQ(strcmp(buf, "2\n"), 0);
+
+ // Set tcp_recovery to any random value.
+ char kMessage[] = "100";
+ EXPECT_THAT(PwriteFd(fd.get(), kMessage, strlen(kMessage), 0),
+ SyscallSucceedsWithValue(strlen(kMessage)));
+ EXPECT_THAT(PreadFd(fd.get(), buf, sizeof(kMessage), 0),
+ SyscallSucceedsWithValue(sizeof(kMessage)));
+ EXPECT_EQ(strcmp(buf, "100\n"), 0);
+}
} // namespace
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ip_tcp_generic.cc b/test/syscalls/linux/socket_ip_tcp_generic.cc
index c2ecb639f..53c076787 100644
--- a/test/syscalls/linux/socket_ip_tcp_generic.cc
+++ b/test/syscalls/linux/socket_ip_tcp_generic.cc
@@ -34,6 +34,9 @@
namespace gvisor {
namespace testing {
+using ::testing::AnyOf;
+using ::testing::Eq;
+
TEST_P(TCPSocketPairTest, TcpInfoSucceeds) {
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
@@ -800,6 +803,9 @@ TEST_P(TCPSocketPairTest, SetCongestionControlFailsForUnsupported) {
// Linux and Netstack both default to a 60s TCP_LINGER2 timeout.
constexpr int kDefaultTCPLingerTimeout = 60;
+// On Linux, the maximum linger2 timeout was changed from 60sec to 120sec.
+constexpr int kMaxTCPLingerTimeout = 120;
+constexpr int kOldMaxTCPLingerTimeout = 60;
TEST_P(TCPSocketPairTest, TCPLingerTimeoutDefault) {
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
@@ -827,12 +833,12 @@ TEST_P(TCPSocketPairTest, SetTCPLingerTimeoutZeroOrLess) {
SyscallSucceedsWithValue(0));
}
-TEST_P(TCPSocketPairTest, SetTCPLingerTimeoutAboveDefault) {
+TEST_P(TCPSocketPairTest, SetTCPLingerTimeoutAboveMax) {
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
// Values above the net.ipv4.tcp_fin_timeout are capped to tcp_fin_timeout
// on linux (defaults to 60 seconds on linux).
- constexpr int kAboveDefault = kDefaultTCPLingerTimeout + 1;
+ constexpr int kAboveDefault = kMaxTCPLingerTimeout + 1;
EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2,
&kAboveDefault, sizeof(kAboveDefault)),
SyscallSucceedsWithValue(0));
@@ -843,7 +849,12 @@ TEST_P(TCPSocketPairTest, SetTCPLingerTimeoutAboveDefault) {
getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2, &get, &get_len),
SyscallSucceedsWithValue(0));
EXPECT_EQ(get_len, sizeof(get));
- EXPECT_EQ(get, kDefaultTCPLingerTimeout);
+ if (IsRunningOnGvisor()) {
+ EXPECT_EQ(get, kMaxTCPLingerTimeout);
+ } else {
+ EXPECT_THAT(get,
+ AnyOf(Eq(kMaxTCPLingerTimeout), Eq(kOldMaxTCPLingerTimeout)));
+ }
}
TEST_P(TCPSocketPairTest, SetTCPLingerTimeout) {