diff options
Diffstat (limited to 'test')
67 files changed, 1359 insertions, 519 deletions
diff --git a/test/benchmarks/README.md b/test/benchmarks/README.md index d1bbabf6f..1bfb4a129 100644 --- a/test/benchmarks/README.md +++ b/test/benchmarks/README.md @@ -81,11 +81,8 @@ benchmarks. In general, benchmarks should look like this: ```golang - -var h harness.Harness - func BenchmarkMyCoolOne(b *testing.B) { - machine, err := h.GetMachine() + machine, err := harness.GetMachine() // check err defer machine.CleanUp() @@ -95,14 +92,14 @@ func BenchmarkMyCoolOne(b *testing.B) { b.ResetTimer() - //Respect b.N. + // Respect b.N. for i := 0; i < b.N; i++ { out, err := container.Run(ctx, dockerutil.RunOpts{ Image: "benchmarks/my-cool-image", Env: []string{"MY_VAR=awesome"}, other options...see dockerutil }, "sh", "-c", "echo MY_VAR") - //check err + // check err... b.StopTimer() // Do parsing and reporting outside of the timer. @@ -114,16 +111,13 @@ func BenchmarkMyCoolOne(b *testing.B) { } func TestMain(m *testing.M) { - h.Init() + harness.Init() os.Exit(m.Run()) } ``` Some notes on the above: -* The harness is initiated in the TestMain method and made global to test - module. The harness will handle any presetup that needs to happen with - flags, remote virtual machines (eventually), and other services. * Respect `b.N` in that users of the benchmark may want to "run for an hour" or something of the sort. * Use the `b.ReportMetric()` method to report custom metrics. diff --git a/test/benchmarks/base/size_test.go b/test/benchmarks/base/size_test.go index acc49cc7c..452926e5f 100644 --- a/test/benchmarks/base/size_test.go +++ b/test/benchmarks/base/size_test.go @@ -26,12 +26,10 @@ import ( "gvisor.dev/gvisor/test/benchmarks/tools" ) -var testHarness harness.Harness - // BenchmarkSizeEmpty creates N empty containers and reads memory usage from // /proc/meminfo. func BenchmarkSizeEmpty(b *testing.B) { - machine, err := testHarness.GetMachine() + machine, err := harness.GetMachine() if err != nil { b.Fatalf("failed to get machine: %v", err) } @@ -81,7 +79,7 @@ func BenchmarkSizeEmpty(b *testing.B) { // 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() + machine, err := harness.GetMachine() if err != nil { b.Fatalf("failed to get machine with: %v", err) } @@ -126,7 +124,7 @@ func BenchmarkSizeNginx(b *testing.B) { // 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() + machine, err := harness.GetMachine() if err != nil { b.Fatalf("failed to get machine with: %v", err) } @@ -178,6 +176,6 @@ func BenchmarkSizeNode(b *testing.B) { // TestMain is the main method for package network. func TestMain(m *testing.M) { - testHarness.Init() + harness.Init() os.Exit(m.Run()) } diff --git a/test/benchmarks/base/startup_test.go b/test/benchmarks/base/startup_test.go index 8ef9f99c4..05a43ad17 100644 --- a/test/benchmarks/base/startup_test.go +++ b/test/benchmarks/base/startup_test.go @@ -25,11 +25,9 @@ import ( "gvisor.dev/gvisor/test/benchmarks/harness" ) -var testHarness harness.Harness - // BenchmarkStartEmpty times startup time for an empty container. func BenchmarkStartupEmpty(b *testing.B) { - machine, err := testHarness.GetMachine() + machine, err := harness.GetMachine() if err != nil { b.Fatalf("failed to get machine: %v", err) } @@ -53,7 +51,7 @@ func BenchmarkStartupEmpty(b *testing.B) { // 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() + machine, err := harness.GetMachine() if err != nil { b.Fatalf("failed to get machine with: %v", err) } @@ -76,7 +74,7 @@ func BenchmarkStartupNginx(b *testing.B) { // 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() + machine, err := harness.GetMachine() if err != nil { b.Fatalf("failed to get machine with: %v", err) } @@ -126,8 +124,8 @@ func runServerWorkload(ctx context.Context, b *testing.B, args base.ServerArgs) return fmt.Errorf("failed to get ip from server: %v", err) } - harness.DebugLog(b, "Waiting for container to start.") // Wait until the Client sees the server as up. + harness.DebugLog(b, "Waiting for container to start.") if err := harness.WaitUntilServing(ctx, args.Machine, servingIP, args.Port); err != nil { return fmt.Errorf("failed to wait for serving: %v", err) } @@ -141,6 +139,6 @@ func runServerWorkload(ctx context.Context, b *testing.B, args base.ServerArgs) // TestMain is the main method for package network. func TestMain(m *testing.M) { - testHarness.Init() + harness.Init() os.Exit(m.Run()) } diff --git a/test/benchmarks/base/sysbench_test.go b/test/benchmarks/base/sysbench_test.go index bbb797e14..80569687c 100644 --- a/test/benchmarks/base/sysbench_test.go +++ b/test/benchmarks/base/sysbench_test.go @@ -23,8 +23,6 @@ import ( "gvisor.dev/gvisor/test/benchmarks/tools" ) -var testHarness harness.Harness - type testCase struct { name string test tools.Sysbench @@ -32,42 +30,34 @@ type testCase struct { // BenchmarSysbench runs sysbench on the runtime. func BenchmarkSysbench(b *testing.B) { - testCases := []testCase{ testCase{ name: "CPU", test: &tools.SysbenchCPU{ - Base: tools.SysbenchBase{ + SysbenchBase: tools.SysbenchBase{ Threads: 1, - Time: 5, }, - MaxPrime: 50000, }, }, testCase{ name: "Memory", test: &tools.SysbenchMemory{ - Base: tools.SysbenchBase{ + SysbenchBase: tools.SysbenchBase{ Threads: 1, }, - BlockSize: "1M", - TotalSize: "500G", }, }, testCase{ name: "Mutex", test: &tools.SysbenchMutex{ - Base: tools.SysbenchBase{ + SysbenchBase: tools.SysbenchBase{ Threads: 8, }, - Loops: 1, - Locks: 10000000, - Num: 4, }, }, } - machine, err := testHarness.GetMachine() + machine, err := harness.GetMachine() if err != nil { b.Fatalf("failed to get machine: %v", err) } @@ -87,12 +77,15 @@ func BenchmarkSysbench(b *testing.B) { sysbench := machine.GetContainer(ctx, b) defer sysbench.CleanUp(ctx) + cmd := tc.test.MakeCmd(b) + b.ResetTimer() out, err := sysbench.Run(ctx, dockerutil.RunOpts{ Image: "benchmarks/sysbench", - }, tc.test.MakeCmd()...) + }, cmd...) if err != nil { b.Fatalf("failed to run sysbench: %v: logs:%s", err, out) } + b.StopTimer() tc.test.Report(b, out) }) } diff --git a/test/benchmarks/database/BUILD b/test/benchmarks/database/BUILD index bfa7f71b6..0b1743603 100644 --- a/test/benchmarks/database/BUILD +++ b/test/benchmarks/database/BUILD @@ -7,11 +7,10 @@ go_library( name = "database", testonly = 1, srcs = ["database.go"], - deps = ["//test/benchmarks/harness"], ) benchmark_test( - name = "database_test", + name = "redis_test", size = "enormous", srcs = ["redis_test.go"], library = ":database", diff --git a/test/benchmarks/database/database.go b/test/benchmarks/database/database.go index 9eeb59f9a..c15ca661c 100644 --- a/test/benchmarks/database/database.go +++ b/test/benchmarks/database/database.go @@ -14,18 +14,3 @@ // Package database holds benchmarks around database applications. package database - -import ( - "os" - "testing" - - "gvisor.dev/gvisor/test/benchmarks/harness" -) - -var h harness.Harness - -// TestMain is the main method for package database. -func TestMain(m *testing.M) { - h.Init() - os.Exit(m.Run()) -} diff --git a/test/benchmarks/database/redis_test.go b/test/benchmarks/database/redis_test.go index 02e67154e..f3c4522ac 100644 --- a/test/benchmarks/database/redis_test.go +++ b/test/benchmarks/database/redis_test.go @@ -16,6 +16,7 @@ package database import ( "context" + "os" "testing" "time" @@ -49,13 +50,13 @@ var operations []string = []string{ // BenchmarkRedis runs redis-benchmark against a redis instance and reports // data in queries per second. Each is reported by named operation (e.g. LPUSH). func BenchmarkRedis(b *testing.B) { - clientMachine, err := h.GetMachine() + clientMachine, err := harness.GetMachine() if err != nil { b.Fatalf("failed to get machine: %v", err) } defer clientMachine.CleanUp() - serverMachine, err := h.GetMachine() + serverMachine, err := harness.GetMachine() if err != nil { b.Fatalf("failed to get machine: %v", err) } @@ -64,7 +65,6 @@ func BenchmarkRedis(b *testing.B) { // Redis runs on port 6379 by default. port := 6379 ctx := context.Background() - for _, operation := range operations { param := tools.Parameter{ Name: "operation", @@ -104,28 +104,26 @@ func BenchmarkRedis(b *testing.B) { b.Fatalf("failed to start redis with: %v", err) } + client := clientMachine.GetNativeContainer(ctx, b) + defer client.CleanUp(ctx) + redis := tools.Redis{ Operation: operation, } - - // Reset profiles and timer to begin the measurement. - server.RestartProfiles() b.ResetTimer() - for i := 0; i < b.N; i++ { - client := clientMachine.GetNativeContainer(ctx, b) - defer client.CleanUp(ctx) - out, err := client.Run(ctx, dockerutil.RunOpts{ - Image: "benchmarks/redis", - }, redis.MakeCmd(ip, serverPort)...) - if err != nil { - b.Fatalf("redis-benchmark failed with: %v", err) - } - - // Stop time while we parse results. - b.StopTimer() - redis.Report(b, out) - b.StartTimer() + 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) }) } } + +func TestMain(m *testing.M) { + harness.Init() + os.Exit(m.Run()) +} diff --git a/test/benchmarks/fs/bazel_test.go b/test/benchmarks/fs/bazel_test.go index 53ed3f9f2..8baeff0db 100644 --- a/test/benchmarks/fs/bazel_test.go +++ b/test/benchmarks/fs/bazel_test.go @@ -25,8 +25,6 @@ import ( "gvisor.dev/gvisor/test/benchmarks/tools" ) -var h harness.Harness - // Note: CleanCache versions of this test require running with root permissions. func BenchmarkBuildABSL(b *testing.B) { runBuildBenchmark(b, "benchmarks/absl", "/abseil-cpp", "absl/base/...") @@ -41,7 +39,7 @@ func BenchmarkBuildRunsc(b *testing.B) { func runBuildBenchmark(b *testing.B, image, workdir, target string) { b.Helper() // Get a machine from the Harness on which to run. - machine, err := h.GetMachine() + machine, err := harness.GetMachine() if err != nil { b.Fatalf("failed to get machine: %v", err) } @@ -61,10 +59,10 @@ func runBuildBenchmark(b *testing.B, image, workdir, target string) { for _, bm := range benchmarks { pageCache := tools.Parameter{ Name: "page_cache", - Value: "clean", + Value: "dirty", } if bm.clearCache { - pageCache.Value = "dirty" + pageCache.Value = "clean" } filesystem := tools.Parameter{ @@ -102,21 +100,20 @@ func runBuildBenchmark(b *testing.B, image, workdir, target string) { prefix = "/tmp" } - // Restart profiles after the copy. - container.RestartProfiles() b.ResetTimer() + b.StopTimer() + // Drop Caches and bazel clean should happen inside the loop as we may use // time options with b.N. (e.g. Run for an hour.) for i := 0; i < b.N; i++ { - b.StopTimer() // Drop Caches for clear cache runs. if bm.clearCache { if err := harness.DropCaches(machine); err != nil { b.Skipf("failed to drop caches: %v. You probably need root.", err) } } - b.StartTimer() + b.StartTimer() got, err := container.Exec(ctx, dockerutil.ExecOpts{ WorkDir: prefix + workdir, }, "bazel", "build", "-c", "opt", target) @@ -129,14 +126,15 @@ func runBuildBenchmark(b *testing.B, image, workdir, target string) { if !strings.Contains(got, want) { b.Fatalf("string %s not in: %s", want, got) } - // Clean bazel in case we use b.N. - _, err = container.Exec(ctx, dockerutil.ExecOpts{ - WorkDir: prefix + workdir, - }, "bazel", "clean") - if err != nil { - b.Fatalf("build failed with: %v", err) + + // Clean bazel in the case we are doing another run. + if i < b.N-1 { + if _, err = container.Exec(ctx, dockerutil.ExecOpts{ + WorkDir: prefix + workdir, + }, "bazel", "clean"); err != nil { + b.Fatalf("build failed with: %v", err) + } } - b.StartTimer() } }) } @@ -144,6 +142,7 @@ func runBuildBenchmark(b *testing.B, image, workdir, target string) { // TestMain is the main method for package fs. func TestMain(m *testing.M) { - h.Init() + harness.Init() + harness.SetFixedBenchmarks() os.Exit(m.Run()) } diff --git a/test/benchmarks/fs/fio_test.go b/test/benchmarks/fs/fio_test.go index 96340373c..0c772b768 100644 --- a/test/benchmarks/fs/fio_test.go +++ b/test/benchmarks/fs/fio_test.go @@ -27,42 +27,50 @@ import ( "gvisor.dev/gvisor/test/benchmarks/tools" ) -var h harness.Harness - // BenchmarkFio runs fio on the runtime under test. There are 4 basic test // cases each run on a tmpfs mount and a bind mount. Fio requires root so that // caches can be dropped. func BenchmarkFio(b *testing.B) { testCases := []tools.Fio{ tools.Fio{ - Test: "write", - Size: "5G", - Blocksize: "1M", - Iodepth: 4, + Test: "write4K", + Size: b.N, + BlockSize: 4, + IODepth: 4, + }, + tools.Fio{ + Test: "write1M", + Size: b.N, + BlockSize: 1024, + IODepth: 4, + }, + tools.Fio{ + Test: "read4K", + Size: b.N, + BlockSize: 4, + IODepth: 4, }, tools.Fio{ - Test: "read", - Size: "5G", - Blocksize: "1M", - Iodepth: 4, + Test: "read1M", + Size: b.N, + BlockSize: 1024, + IODepth: 4, }, tools.Fio{ - Test: "randwrite", - Size: "5G", - Blocksize: "4K", - Iodepth: 4, - Time: 30, + Test: "randwrite4K", + Size: b.N, + BlockSize: 4, + IODepth: 4, }, tools.Fio{ - Test: "randread", - Size: "5G", - Blocksize: "4K", - Iodepth: 4, - Time: 30, + Test: "randread4K", + Size: b.N, + BlockSize: 4, + IODepth: 4, }, } - machine, err := h.GetMachine() + machine, err := harness.GetMachine() if err != nil { b.Fatalf("failed to get machine with: %v", err) } @@ -116,7 +124,7 @@ func BenchmarkFio(b *testing.B) { // For reads, we need a file to read so make one inside the container. if strings.Contains(tc.Test, "read") { - fallocateCmd := fmt.Sprintf("fallocate -l %s %s", tc.Size, outfile) + fallocateCmd := fmt.Sprintf("fallocate -l %dK %s", tc.Size, outfile) if out, err := container.Exec(ctx, dockerutil.ExecOpts{}, strings.Split(fallocateCmd, " ")...); err != nil { b.Fatalf("failed to create readable file on mount: %v, %s", err, out) @@ -128,22 +136,24 @@ func BenchmarkFio(b *testing.B) { b.Skipf("failed to drop caches with %v. You probably need root.", err) } cmd := tc.MakeCmd(outfile) - container.RestartProfiles() + b.ResetTimer() + b.StopTimer() + for i := 0; i < b.N; i++ { + if err := harness.DropCaches(machine); err != nil { + b.Fatalf("failed to drop caches: %v", err) + } + // Run fio. + b.StartTimer() data, err := container.Exec(ctx, dockerutil.ExecOpts{}, cmd...) if err != nil { b.Fatalf("failed to run cmd %v: %v", cmd, err) } b.StopTimer() + b.SetBytes(1024 * 1024) // Bytes for go reporting (Size is in megabytes). tc.Report(b, data) - // If b.N is used (i.e. we run for an hour), we should drop caches - // after each run. - if err := harness.DropCaches(machine); err != nil { - b.Fatalf("failed to drop caches: %v", err) - } - b.StartTimer() } }) } @@ -185,6 +195,6 @@ func makeMount(machine harness.Machine, mountType mount.Type, target string) (mo // TestMain is the main method for package fs. func TestMain(m *testing.M) { - h.Init() + harness.Init() os.Exit(m.Run()) } diff --git a/test/benchmarks/harness/harness.go b/test/benchmarks/harness/harness.go index 4c6e724aa..a853b7ba8 100644 --- a/test/benchmarks/harness/harness.go +++ b/test/benchmarks/harness/harness.go @@ -28,12 +28,8 @@ var ( debug = flag.Bool("debug", false, "turns on debug messages for individual benchmarks") ) -// Harness is a handle for managing state in benchmark runs. -type Harness struct { -} - // Init performs any harness initilialization before runs. -func (h *Harness) Init() error { +func Init() error { flag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: %s -- --test.bench=<regex>\n", os.Args[0]) flag.PrintDefaults() @@ -47,7 +43,15 @@ func (h *Harness) Init() error { return nil } +// SetFixedBenchmarks causes all benchmarks to run once. +// +// This must be set if they cannot scale with N. Note that this uses 1ns +// instead of 1x due to https://github.com/golang/go/issues/32051. +func SetFixedBenchmarks() { + flag.Set("test.benchtime", "1ns") +} + // GetMachine returns this run's implementation of machine. -func (h *Harness) GetMachine() (Machine, error) { +func GetMachine() (Machine, error) { return &localMachine{}, nil } diff --git a/test/benchmarks/media/BUILD b/test/benchmarks/media/BUILD index 46e8dc8b5..380783f0b 100644 --- a/test/benchmarks/media/BUILD +++ b/test/benchmarks/media/BUILD @@ -7,12 +7,11 @@ go_library( name = "media", testonly = 1, srcs = ["media.go"], - deps = ["//test/benchmarks/harness"], ) benchmark_test( - name = "media_test", - size = "large", + name = "ffmpeg_test", + size = "enormous", srcs = ["ffmpeg_test.go"], library = ":media", visibility = ["//:sandbox"], diff --git a/test/benchmarks/media/ffmpeg_test.go b/test/benchmarks/media/ffmpeg_test.go index 7822dfad7..1b99a319a 100644 --- a/test/benchmarks/media/ffmpeg_test.go +++ b/test/benchmarks/media/ffmpeg_test.go @@ -15,6 +15,7 @@ package media import ( "context" + "os" "strings" "testing" @@ -25,29 +26,36 @@ import ( // BenchmarkFfmpeg runs ffmpeg in a container and records runtime. // BenchmarkFfmpeg should run as root to drop caches. func BenchmarkFfmpeg(b *testing.B) { - machine, err := h.GetMachine() + machine, err := harness.GetMachine() if err != nil { b.Fatalf("failed to get machine: %v", err) } defer machine.CleanUp() ctx := context.Background() - container := machine.GetContainer(ctx, b) - defer container.CleanUp(ctx) cmd := strings.Split("ffmpeg -i video.mp4 -c:v libx264 -preset veryslow output.mp4", " ") b.ResetTimer() + b.StopTimer() + for i := 0; i < b.N; i++ { - b.StopTimer() + container := machine.GetContainer(ctx, b) + defer container.CleanUp(ctx) if err := harness.DropCaches(machine); err != nil { b.Skipf("failed to drop caches: %v. You probably need root.", err) } - b.StartTimer() + b.StartTimer() if _, err := container.Run(ctx, dockerutil.RunOpts{ Image: "benchmarks/ffmpeg", }, cmd...); err != nil { b.Fatalf("failed to run container: %v", err) } + b.StopTimer() } } + +func TestMain(m *testing.M) { + harness.Init() + os.Exit(m.Run()) +} diff --git a/test/benchmarks/media/media.go b/test/benchmarks/media/media.go index c7b35b758..ed7b24651 100644 --- a/test/benchmarks/media/media.go +++ b/test/benchmarks/media/media.go @@ -14,18 +14,3 @@ // Package media holds benchmarks around media processing applications. package media - -import ( - "os" - "testing" - - "gvisor.dev/gvisor/test/benchmarks/harness" -) - -var h harness.Harness - -// TestMain is the main method for package media. -func TestMain(m *testing.M) { - h.Init() - os.Exit(m.Run()) -} diff --git a/test/benchmarks/ml/BUILD b/test/benchmarks/ml/BUILD index 02ff6966f..285ec35d9 100644 --- a/test/benchmarks/ml/BUILD +++ b/test/benchmarks/ml/BUILD @@ -7,12 +7,11 @@ go_library( name = "ml", testonly = 1, srcs = ["ml.go"], - deps = ["//test/benchmarks/harness"], ) benchmark_test( - name = "ml_test", - size = "large", + name = "tensorflow_test", + size = "enormous", srcs = ["tensorflow_test.go"], library = ":ml", visibility = ["//:sandbox"], diff --git a/test/benchmarks/ml/ml.go b/test/benchmarks/ml/ml.go index 13282d7bb..d5fc5b7da 100644 --- a/test/benchmarks/ml/ml.go +++ b/test/benchmarks/ml/ml.go @@ -14,18 +14,3 @@ // Package ml holds benchmarks around machine learning performance. package ml - -import ( - "os" - "testing" - - "gvisor.dev/gvisor/test/benchmarks/harness" -) - -var h harness.Harness - -// TestMain is the main method for package ml. -func TestMain(m *testing.M) { - h.Init() - os.Exit(m.Run()) -} diff --git a/test/benchmarks/ml/tensorflow_test.go b/test/benchmarks/ml/tensorflow_test.go index f7746897d..b0e0c4720 100644 --- a/test/benchmarks/ml/tensorflow_test.go +++ b/test/benchmarks/ml/tensorflow_test.go @@ -15,6 +15,7 @@ package ml import ( "context" + "os" "testing" "gvisor.dev/gvisor/pkg/test/dockerutil" @@ -35,7 +36,7 @@ func BenchmarkTensorflow(b *testing.B) { "NeuralNetwork": "3_NeuralNetworks/neural_network.py", } - machine, err := h.GetMachine() + machine, err := harness.GetMachine() if err != nil { b.Fatalf("failed to get machine: %v", err) } @@ -44,17 +45,19 @@ func BenchmarkTensorflow(b *testing.B) { for name, workload := range workloads { b.Run(name, func(b *testing.B) { ctx := context.Background() - container := machine.GetContainer(ctx, b) - defer container.CleanUp(ctx) b.ResetTimer() + b.StopTimer() + for i := 0; i < b.N; i++ { - b.StopTimer() + container := machine.GetContainer(ctx, b) + defer container.CleanUp(ctx) if err := harness.DropCaches(machine); err != nil { b.Skipf("failed to drop caches: %v. You probably need root.", err) } - b.StartTimer() + // Run tensorflow. + b.StartTimer() if out, err := container.Run(ctx, dockerutil.RunOpts{ Image: "benchmarks/tensorflow", Env: []string{"PYTHONPATH=$PYTHONPATH:/TensorFlow-Examples/examples"}, @@ -62,8 +65,14 @@ func BenchmarkTensorflow(b *testing.B) { }, "python", workload); err != nil { b.Fatalf("failed to run container: %v logs: %s", err, out) } + b.StopTimer() } }) } +} +func TestMain(m *testing.M) { + harness.Init() + harness.SetFixedBenchmarks() + os.Exit(m.Run()) } diff --git a/test/benchmarks/network/BUILD b/test/benchmarks/network/BUILD index c75d1ce11..2741570f5 100644 --- a/test/benchmarks/network/BUILD +++ b/test/benchmarks/network/BUILD @@ -8,7 +8,6 @@ go_library( testonly = 1, srcs = [ "network.go", - "static_server.go", ], deps = [ "//pkg/test/dockerutil", @@ -18,19 +17,76 @@ go_library( ) benchmark_test( - name = "network_test", - size = "large", + name = "iperf_test", + size = "enormous", srcs = [ - "httpd_test.go", "iperf_test.go", - "nginx_test.go", + ], + library = ":network", + visibility = ["//:sandbox"], + deps = [ + "//pkg/test/dockerutil", + "//pkg/test/testutil", + "//test/benchmarks/harness", + "//test/benchmarks/tools", + ], +) + +benchmark_test( + name = "node_test", + size = "enormous", + srcs = [ "node_test.go", + ], + library = ":network", + visibility = ["//:sandbox"], + deps = [ + "//pkg/test/dockerutil", + "//test/benchmarks/harness", + "//test/benchmarks/tools", + ], +) + +benchmark_test( + name = "ruby_test", + size = "enormous", + srcs = [ "ruby_test.go", ], library = ":network", visibility = ["//:sandbox"], deps = [ "//pkg/test/dockerutil", + "//test/benchmarks/harness", + "//test/benchmarks/tools", + ], +) + +benchmark_test( + name = "nginx_test", + size = "enormous", + srcs = [ + "nginx_test.go", + ], + library = ":network", + visibility = ["//:sandbox"], + deps = [ + "//pkg/test/dockerutil", + "//test/benchmarks/harness", + "//test/benchmarks/tools", + ], +) + +benchmark_test( + name = "httpd_test", + size = "enormous", + srcs = [ + "httpd_test.go", + ], + library = ":network", + visibility = ["//:sandbox"], + deps = [ + "//pkg/test/dockerutil", "//pkg/test/testutil", "//test/benchmarks/harness", "//test/benchmarks/tools", diff --git a/test/benchmarks/network/httpd_test.go b/test/benchmarks/network/httpd_test.go index 8d7d5f750..629127250 100644 --- a/test/benchmarks/network/httpd_test.go +++ b/test/benchmarks/network/httpd_test.go @@ -14,10 +14,12 @@ package network import ( + "os" "strconv" "testing" "gvisor.dev/gvisor/pkg/test/dockerutil" + "gvisor.dev/gvisor/test/benchmarks/harness" "gvisor.dev/gvisor/test/benchmarks/tools" ) @@ -34,18 +36,20 @@ var httpdDocs = map[string]string{ // BenchmarkHttpd iterates over different sized payloads and concurrency, testing // how well the runtime handles sending different payload sizes. func BenchmarkHttpd(b *testing.B) { - benchmarkHttpdDocSize(b, false /* reverse */) + benchmarkHttpdDocSize(b) } -// BenchmarkReverseHttpd iterates over different sized payloads, testing -// how well the runtime handles receiving different payload sizes. -func BenchmarkReverseHttpd(b *testing.B) { - benchmarkHttpdDocSize(b, true /* reverse */) +// BenchmarkContinuousHttpd runs specific benchmarks for continous jobs. +// The runtime under test is the server serving a runc client. +func BenchmarkContinuousHttpd(b *testing.B) { + sizes := []string{"10Kb", "100Kb", "1Mb"} + threads := []int{1, 25, 100, 1000} + benchmarkHttpdContinuous(b, threads, sizes) } // benchmarkHttpdDocSize iterates through all doc sizes, running subbenchmarks // for each size. -func benchmarkHttpdDocSize(b *testing.B, reverse bool) { +func benchmarkHttpdDocSize(b *testing.B) { b.Helper() for size, filename := range httpdDocs { concurrency := []int{1, 25, 50, 100, 1000} @@ -64,18 +68,49 @@ func benchmarkHttpdDocSize(b *testing.B, reverse bool) { } b.Run(name, func(b *testing.B) { hey := &tools.Hey{ - Requests: c * b.N, + Requests: b.N, Concurrency: c, Doc: filename, } - runHttpd(b, hey, reverse) + runHttpd(b, hey) + }) + } + } +} + +// benchmarkHttpdContinuous iterates through given sizes and concurrencies. +func benchmarkHttpdContinuous(b *testing.B, concurrency []int, sizes []string) { + for _, size := range sizes { + filename := httpdDocs[size] + for _, c := range concurrency { + fsize := tools.Parameter{ + Name: "filesize", + Value: size, + } + + threads := tools.Parameter{ + Name: "concurrency", + Value: strconv.Itoa(c), + } + + name, err := tools.ParametersToName(fsize, threads) + if err != nil { + b.Fatalf("Failed to parse parameters: %v", err) + } + b.Run(name, func(b *testing.B) { + hey := &tools.Hey{ + Requests: b.N, + Concurrency: c, + Doc: filename, + } + runHttpd(b, hey) }) } } } // runHttpd configures the static serving methods to run httpd. -func runHttpd(b *testing.B, hey *tools.Hey, reverse bool) { +func runHttpd(b *testing.B, hey *tools.Hey) { // httpd runs on port 80. port := 80 httpdRunOpts := dockerutil.RunOpts{ @@ -91,5 +126,10 @@ func runHttpd(b *testing.B, hey *tools.Hey, reverse bool) { }, } httpdCmd := []string{"sh", "-c", "mkdir -p /tmp/html; cp -r /local/* /tmp/html/.; apache2 -X"} - runStaticServer(b, httpdRunOpts, httpdCmd, port, hey, reverse) + runStaticServer(b, httpdRunOpts, httpdCmd, port, hey) +} + +func TestMain(m *testing.M) { + harness.Init() + os.Exit(m.Run()) } diff --git a/test/benchmarks/network/iperf_test.go b/test/benchmarks/network/iperf_test.go index b8ab7dfb8..5e81149fe 100644 --- a/test/benchmarks/network/iperf_test.go +++ b/test/benchmarks/network/iperf_test.go @@ -15,6 +15,7 @@ package network import ( "context" + "os" "testing" "gvisor.dev/gvisor/pkg/test/dockerutil" @@ -25,16 +26,16 @@ import ( func BenchmarkIperf(b *testing.B) { iperf := tools.Iperf{ - Time: 10, // time in seconds to run client. + Num: b.N, } - clientMachine, err := h.GetMachine() + clientMachine, err := harness.GetMachine() if err != nil { b.Fatalf("failed to get machine: %v", err) } defer clientMachine.CleanUp() - serverMachine, err := h.GetMachine() + serverMachine, err := harness.GetMachine() if err != nil { b.Fatalf("failed to get machine: %v", err) } @@ -91,23 +92,22 @@ func BenchmarkIperf(b *testing.B) { if err := harness.WaitUntilServing(ctx, clientMachine, ip, servingPort); err != nil { b.Fatalf("failed to wait for server: %v", err) } + // Run the client. b.ResetTimer() - - // Restart the server profiles. If the server isn't being profiled - // this does nothing. - server.RestartProfiles() - for i := 0; i < b.N; i++ { - out, err := client.Run(ctx, dockerutil.RunOpts{ - Image: "benchmarks/iperf", - }, iperf.MakeCmd(ip, servingPort)...) - if err != nil { - b.Fatalf("failed to run client: %v", err) - } - b.StopTimer() - iperf.Report(b, out) - b.StartTimer() + out, err := client.Run(ctx, dockerutil.RunOpts{ + Image: "benchmarks/iperf", + }, iperf.MakeCmd(ip, servingPort)...) + if err != nil { + b.Fatalf("failed to run client: %v", err) } + b.StopTimer() + iperf.Report(b, out) }) } } + +func TestMain(m *testing.M) { + harness.Init() + os.Exit(m.Run()) +} diff --git a/test/benchmarks/network/network.go b/test/benchmarks/network/network.go index ce17ddb94..d61002cea 100644 --- a/test/benchmarks/network/network.go +++ b/test/benchmarks/network/network.go @@ -16,16 +16,65 @@ package network import ( - "os" + "context" "testing" + "gvisor.dev/gvisor/pkg/test/dockerutil" "gvisor.dev/gvisor/test/benchmarks/harness" + "gvisor.dev/gvisor/test/benchmarks/tools" ) -var h harness.Harness +// runStaticServer runs static serving workloads (httpd, nginx). +func runStaticServer(b *testing.B, serverOpts dockerutil.RunOpts, serverCmd []string, port int, hey *tools.Hey) { + ctx := context.Background() -// TestMain is the main method for package network. -func TestMain(m *testing.M) { - h.Init() - os.Exit(m.Run()) + // Get two machines: a client and server. + clientMachine, err := harness.GetMachine() + if err != nil { + b.Fatalf("failed to get machine: %v", err) + } + defer clientMachine.CleanUp() + + serverMachine, err := harness.GetMachine() + if err != nil { + b.Fatalf("failed to get machine: %v", err) + } + defer serverMachine.CleanUp() + + // Make the containers. + client := clientMachine.GetNativeContainer(ctx, b) + defer client.CleanUp(ctx) + server := serverMachine.GetContainer(ctx, b) + defer server.CleanUp(ctx) + + // Start the server. + if err := server.Spawn(ctx, serverOpts, serverCmd...); err != nil { + b.Fatalf("failed to start server: %v", err) + } + + // Get its IP. + ip, err := serverMachine.IPAddress() + if err != nil { + b.Fatalf("failed to find server ip: %v", err) + } + + // Get the published port. + servingPort, err := server.FindPort(ctx, port) + if err != nil { + b.Fatalf("failed to find server port %d: %v", port, err) + } + + // Make sure the server is serving. + harness.WaitUntilServing(ctx, clientMachine, ip, servingPort) + + // Run the client. + b.ResetTimer() + out, err := client.Run(ctx, dockerutil.RunOpts{ + Image: "benchmarks/hey", + }, hey.MakeCmd(ip, servingPort)...) + if err != nil { + b.Fatalf("run failed with: %v", err) + } + b.StopTimer() + hey.Report(b, out) } diff --git a/test/benchmarks/network/nginx_test.go b/test/benchmarks/network/nginx_test.go index 08565d0b2..74f3578fc 100644 --- a/test/benchmarks/network/nginx_test.go +++ b/test/benchmarks/network/nginx_test.go @@ -14,10 +14,12 @@ package network import ( + "os" "strconv" "testing" "gvisor.dev/gvisor/pkg/test/dockerutil" + "gvisor.dev/gvisor/test/benchmarks/harness" "gvisor.dev/gvisor/test/benchmarks/tools" ) @@ -34,19 +36,21 @@ var nginxDocs = map[string]string{ // BenchmarkNginxDocSize iterates over different sized payloads, testing how // well the runtime handles sending different payload sizes. func BenchmarkNginxDocSize(b *testing.B) { - benchmarkNginxDocSize(b, false /* reverse */, true /* tmpfs */) - benchmarkNginxDocSize(b, false /* reverse */, false /* tmpfs */) + benchmarkNginxDocSize(b, true /* tmpfs */) + benchmarkNginxDocSize(b, false /* tmpfs */) } -// BenchmarkReverseNginxDocSize iterates over different sized payloads, testing -// how well the runtime handles receiving different payload sizes. -func BenchmarkReverseNginxDocSize(b *testing.B) { - benchmarkNginxDocSize(b, true /* reverse */, true /* tmpfs */) +// BenchmarkContinuousNginx runs specific benchmarks for continous jobs. +// The runtime under test is the sever serving a runc client. +func BenchmarkContinuousNginx(b *testing.B) { + sizes := []string{"10Kb", "100Kb", "1Mb"} + threads := []int{1, 25, 100, 1000} + benchmarkNginxContinuous(b, threads, sizes) } // benchmarkNginxDocSize iterates through all doc sizes, running subbenchmarks // for each size. -func benchmarkNginxDocSize(b *testing.B, reverse, tmpfs bool) { +func benchmarkNginxDocSize(b *testing.B, tmpfs bool) { for size, filename := range nginxDocs { concurrency := []int{1, 25, 50, 100, 1000} for _, c := range concurrency { @@ -71,21 +75,56 @@ func benchmarkNginxDocSize(b *testing.B, reverse, tmpfs bool) { if err != nil { b.Fatalf("Failed to parse parameters: %v", err) } + b.Run(name, func(b *testing.B) { + hey := &tools.Hey{ + Requests: b.N, + Concurrency: c, + Doc: filename, + } + runNginx(b, hey, tmpfs) + }) + } + } +} + +// benchmarkNginxContinuous iterates through given sizes and concurrencies on a tmpfs mount. +func benchmarkNginxContinuous(b *testing.B, concurrency []int, sizes []string) { + for _, size := range sizes { + filename := nginxDocs[size] + for _, c := range concurrency { + fsize := tools.Parameter{ + Name: "filesize", + Value: size, + } + threads := tools.Parameter{ + Name: "concurrency", + Value: strconv.Itoa(c), + } + + fs := tools.Parameter{ + Name: "filesystem", + Value: "tmpfs", + } + + name, err := tools.ParametersToName(fsize, threads, fs) + if err != nil { + b.Fatalf("Failed to parse parameters: %v", err) + } b.Run(name, func(b *testing.B) { hey := &tools.Hey{ - Requests: c * b.N, + Requests: b.N, Concurrency: c, Doc: filename, } - runNginx(b, hey, reverse, tmpfs) + runNginx(b, hey, true /*tmpfs*/) }) } } } // runNginx configures the static serving methods to run httpd. -func runNginx(b *testing.B, hey *tools.Hey, reverse, tmpfs bool) { +func runNginx(b *testing.B, hey *tools.Hey, tmpfs bool) { // nginx runs on port 80. port := 80 nginxRunOpts := dockerutil.RunOpts{ @@ -99,5 +138,10 @@ func runNginx(b *testing.B, hey *tools.Hey, reverse, tmpfs bool) { } // Command copies nginxDocs to tmpfs serving directory and runs nginx. - runStaticServer(b, nginxRunOpts, nginxCmd, port, hey, reverse) + runStaticServer(b, nginxRunOpts, nginxCmd, port, hey) +} + +func TestMain(m *testing.M) { + harness.Init() + os.Exit(m.Run()) } diff --git a/test/benchmarks/network/node_test.go b/test/benchmarks/network/node_test.go index 254538899..a1fc82f95 100644 --- a/test/benchmarks/network/node_test.go +++ b/test/benchmarks/network/node_test.go @@ -15,6 +15,7 @@ package network import ( "context" + "os" "strconv" "testing" "time" @@ -41,7 +42,7 @@ func BenchmarkNode(b *testing.B) { } b.Run(name, func(b *testing.B) { hey := &tools.Hey{ - Requests: b.N * c, // Requests b.N requests per thread. + Requests: b.N, Concurrency: c, } runNode(b, hey) @@ -54,14 +55,14 @@ func runNode(b *testing.B, hey *tools.Hey) { b.Helper() // The machine to hold Redis and the Node Server. - serverMachine, err := h.GetMachine() + serverMachine, err := harness.GetMachine() if err != nil { b.Fatalf("failed to get machine with: %v", err) } defer serverMachine.CleanUp() // The machine to run 'hey'. - clientMachine, err := h.GetMachine() + clientMachine, err := harness.GetMachine() if err != nil { b.Fatalf("failed to get machine with: %v", err) } @@ -116,10 +117,8 @@ func runNode(b *testing.B, hey *tools.Hey) { heyCmd := hey.MakeCmd(servingIP, servingPort) - nodeApp.RestartProfiles() - b.ResetTimer() - // the client should run on Native. + b.ResetTimer() client := clientMachine.GetNativeContainer(ctx, b) out, err := client.Run(ctx, dockerutil.RunOpts{ Image: "benchmarks/hey", @@ -129,7 +128,10 @@ func runNode(b *testing.B, hey *tools.Hey) { } // Stop the timer to parse the data and report stats. - b.StopTimer() hey.Report(b, out) - b.StartTimer() +} + +func TestMain(m *testing.M) { + harness.Init() + os.Exit(m.Run()) } diff --git a/test/benchmarks/network/ruby_test.go b/test/benchmarks/network/ruby_test.go index 0174ff3f3..b7ec16e0a 100644 --- a/test/benchmarks/network/ruby_test.go +++ b/test/benchmarks/network/ruby_test.go @@ -16,6 +16,7 @@ package network import ( "context" "fmt" + "os" "strconv" "testing" "time" @@ -42,7 +43,7 @@ func BenchmarkRuby(b *testing.B) { } b.Run(name, func(b *testing.B) { hey := &tools.Hey{ - Requests: b.N * c, // b.N requests per thread. + Requests: b.N, Concurrency: c, } runRuby(b, hey) @@ -52,16 +53,15 @@ func BenchmarkRuby(b *testing.B) { // runRuby runs the test for a given # of requests and concurrency. func runRuby(b *testing.B, hey *tools.Hey) { - b.Helper() // The machine to hold Redis and the Ruby Server. - serverMachine, err := h.GetMachine() + serverMachine, err := harness.GetMachine() if err != nil { b.Fatalf("failed to get machine with: %v", err) } defer serverMachine.CleanUp() // The machine to run 'hey'. - clientMachine, err := h.GetMachine() + clientMachine, err := harness.GetMachine() if err != nil { b.Fatalf("failed to get machine with: %v", err) } @@ -123,10 +123,9 @@ func runRuby(b *testing.B, hey *tools.Hey) { b.Fatalf("failed to wait until serving: %v", err) } heyCmd := hey.MakeCmd(servingIP, servingPort) - rubyApp.RestartProfiles() - b.ResetTimer() // the client should run on Native. + b.ResetTimer() client := clientMachine.GetNativeContainer(ctx, b) defer client.CleanUp(ctx) out, err := client.Run(ctx, dockerutil.RunOpts{ @@ -135,9 +134,11 @@ func runRuby(b *testing.B, hey *tools.Hey) { 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() +} + +func TestMain(m *testing.M) { + harness.Init() + os.Exit(m.Run()) } diff --git a/test/benchmarks/network/static_server.go b/test/benchmarks/network/static_server.go deleted file mode 100644 index e747a1395..000000000 --- a/test/benchmarks/network/static_server.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package network - -import ( - "context" - "testing" - - "gvisor.dev/gvisor/pkg/test/dockerutil" - "gvisor.dev/gvisor/test/benchmarks/harness" - "gvisor.dev/gvisor/test/benchmarks/tools" -) - -// runStaticServer runs static serving workloads (httpd, nginx). -func runStaticServer(b *testing.B, serverOpts dockerutil.RunOpts, serverCmd []string, port int, hey *tools.Hey, reverse bool) { - ctx := context.Background() - - // Get two machines: a client and server. - clientMachine, err := h.GetMachine() - if err != nil { - b.Fatalf("failed to get machine: %v", err) - } - defer clientMachine.CleanUp() - - serverMachine, err := h.GetMachine() - if err != nil { - b.Fatalf("failed to get machine: %v", err) - } - defer serverMachine.CleanUp() - - // Make the containers. 'reverse=true' specifies that the client should use the - // runtime under test. - var client, server *dockerutil.Container - if reverse { - client = clientMachine.GetContainer(ctx, b) - server = serverMachine.GetNativeContainer(ctx, b) - } else { - client = clientMachine.GetNativeContainer(ctx, b) - server = serverMachine.GetContainer(ctx, b) - } - defer client.CleanUp(ctx) - defer server.CleanUp(ctx) - - // Start the server. - if err := server.Spawn(ctx, serverOpts, serverCmd...); err != nil { - b.Fatalf("failed to start server: %v", err) - } - - // Get its IP. - ip, err := serverMachine.IPAddress() - if err != nil { - b.Fatalf("failed to find server ip: %v", err) - } - - // Get the published port. - servingPort, err := server.FindPort(ctx, port) - if err != nil { - b.Fatalf("failed to find server port %d: %v", port, err) - } - - // Make sure the server is serving. - harness.WaitUntilServing(ctx, clientMachine, ip, servingPort) - b.ResetTimer() - server.RestartProfiles() - out, err := client.Run(ctx, dockerutil.RunOpts{ - Image: "benchmarks/hey", - }, hey.MakeCmd(ip, servingPort)...) - if err != nil { - b.Fatalf("run failed with: %v", err) - } - - b.StopTimer() - hey.Report(b, out) - b.StartTimer() -} diff --git a/test/benchmarks/tools/fio.go b/test/benchmarks/tools/fio.go index f5f60fa84..f6324c3ab 100644 --- a/test/benchmarks/tools/fio.go +++ b/test/benchmarks/tools/fio.go @@ -25,25 +25,20 @@ import ( // Fio makes 'fio' commands and parses their output. type Fio struct { Test string // test to run: read, write, randread, randwrite. - Size string // total size to be read/written of format N[GMK] (e.g. 5G). - Blocksize string // blocksize to be read/write of format N[GMK] (e.g. 4K). - Iodepth int // iodepth for reads/writes. - Time int // time to run the test in seconds, usually for rand(read/write). + Size int // total size to be read/written in megabytes. + BlockSize int // block size to be read/written in kilobytes. + IODepth int // I/O depth for reads/writes. } // MakeCmd makes a 'fio' command. func (f *Fio) MakeCmd(filename string) []string { cmd := []string{"fio", "--output-format=json", "--ioengine=sync"} cmd = append(cmd, fmt.Sprintf("--name=%s", f.Test)) - cmd = append(cmd, fmt.Sprintf("--size=%s", f.Size)) - cmd = append(cmd, fmt.Sprintf("--blocksize=%s", f.Blocksize)) + cmd = append(cmd, fmt.Sprintf("--size=%dM", f.Size)) + cmd = append(cmd, fmt.Sprintf("--blocksize=%dK", f.BlockSize)) cmd = append(cmd, fmt.Sprintf("--filename=%s", filename)) - cmd = append(cmd, fmt.Sprintf("--iodepth=%d", f.Iodepth)) + cmd = append(cmd, fmt.Sprintf("--iodepth=%d", f.IODepth)) cmd = append(cmd, fmt.Sprintf("--rw=%s", f.Test)) - if f.Time != 0 { - cmd = append(cmd, "--time_based") - cmd = append(cmd, fmt.Sprintf("--runtime=%d", f.Time)) - } return cmd } diff --git a/test/benchmarks/tools/hey.go b/test/benchmarks/tools/hey.go index b8cb938fe..de908feeb 100644 --- a/test/benchmarks/tools/hey.go +++ b/test/benchmarks/tools/hey.go @@ -19,7 +19,6 @@ import ( "net" "regexp" "strconv" - "strings" "testing" ) @@ -32,8 +31,16 @@ type Hey struct { // MakeCmd returns a 'hey' command. func (h *Hey) MakeCmd(ip net.IP, port int) []string { - return strings.Split(fmt.Sprintf("hey -n %d -c %d http://%s:%d/%s", - h.Requests, h.Concurrency, ip, port, h.Doc), " ") + c := h.Concurrency + if c > h.Requests { + c = h.Requests + } + return []string{ + "hey", + "-n", fmt.Sprintf("%d", h.Requests), + "-c", fmt.Sprintf("%d", c), + fmt.Sprintf("http://%s:%d/%s", ip.String(), port, h.Doc), + } } // Report parses output from 'hey' and reports metrics. diff --git a/test/benchmarks/tools/iperf.go b/test/benchmarks/tools/iperf.go index 5c4e7125b..abf296731 100644 --- a/test/benchmarks/tools/iperf.go +++ b/test/benchmarks/tools/iperf.go @@ -19,19 +19,27 @@ import ( "net" "regexp" "strconv" - "strings" "testing" ) +const length = 64 * 1024 + // Iperf is for the client side of `iperf`. type Iperf struct { - Time int + Num int } // MakeCmd returns a iperf client command. func (i *Iperf) MakeCmd(ip net.IP, port int) []string { - // iperf report in Kb realtime - return strings.Split(fmt.Sprintf("iperf -f K --realtime --time %d -c %s -p %d", i.Time, ip, port), " ") + return []string{ + "iperf", + "--format", "K", // Output in KBytes. + "--realtime", // Measured in realtime. + "--num", fmt.Sprintf("%d", i.Num), + "--length", fmt.Sprintf("%d", length), + "--client", ip.String(), + "--port", fmt.Sprintf("%d", port), + } } // Report parses output from iperf client and reports metrics. @@ -42,6 +50,7 @@ func (i *Iperf) Report(b *testing.B, output string) { if err != nil { b.Fatalf("failed to parse bandwitdth from %s: %v", output, err) } + b.SetBytes(length) // Measure Bytes/sec for b.N, although below is iperf output. ReportCustomMetric(b, bW*1024, "bandwidth" /*metric name*/, "bytes_per_second" /*unit*/) } diff --git a/test/benchmarks/tools/redis.go b/test/benchmarks/tools/redis.go index e35886437..12fdbc7cc 100644 --- a/test/benchmarks/tools/redis.go +++ b/test/benchmarks/tools/redis.go @@ -19,7 +19,6 @@ import ( "net" "regexp" "strconv" - "strings" "testing" ) @@ -29,17 +28,29 @@ type Redis struct { } // MakeCmd returns a redis-benchmark client command. -func (r *Redis) MakeCmd(ip net.IP, port int) []string { +func (r *Redis) MakeCmd(ip net.IP, port, requests int) []string { // There is no -t PING_BULK for redis-benchmark, so adjust the command in that case. // Note that "ping" will run both PING_INLINE and PING_BULK. if r.Operation == "PING_BULK" { - return strings.Split( - fmt.Sprintf("redis-benchmark --csv -t ping -h %s -p %d", ip, port), " ") + return []string{ + "redis-benchmark", + "--csv", + "-t", "ping", + "-h", ip.String(), + "-p", fmt.Sprintf("%d", port), + "-n", fmt.Sprintf("%d", requests), + } } // runs redis-benchmark -t operation for 100K requests against server. - return strings.Split( - fmt.Sprintf("redis-benchmark --csv -t %s -h %s -p %d", r.Operation, ip, port), " ") + return []string{ + "redis-benchmark", + "--csv", + "-t", r.Operation, + "-h", ip.String(), + "-p", fmt.Sprintf("%d", port), + "-n", fmt.Sprintf("%d", requests), + } } // Report parses output from redis-benchmark client and reports metrics. diff --git a/test/benchmarks/tools/sysbench.go b/test/benchmarks/tools/sysbench.go index 7ccacd8ff..2b8e6c8aa 100644 --- a/test/benchmarks/tools/sysbench.go +++ b/test/benchmarks/tools/sysbench.go @@ -18,58 +18,46 @@ import ( "fmt" "regexp" "strconv" - "strings" "testing" ) -var warmup = "sysbench --threads=8 --memory-total-size=5G memory run > /dev/null &&" - // Sysbench represents a 'sysbench' command. type Sysbench interface { - MakeCmd() []string // Makes a sysbench command. - flags() []string - Report(*testing.B, string) // Reports results contained in string. + // MakeCmd constructs the relevant command line. + MakeCmd(*testing.B) []string + + // Report reports relevant custom metrics. + Report(*testing.B, string) } // SysbenchBase is the top level struct for sysbench and holds top-level arguments // for sysbench. See: 'sysbench --help' type SysbenchBase struct { - Threads int // number of Threads for the test. - Time int // time limit for test in seconds. + // Threads is the number of threads for the test. + Threads int } // baseFlags returns top level flags. -func (s *SysbenchBase) baseFlags() []string { +func (s *SysbenchBase) baseFlags(b *testing.B) []string { var ret []string if s.Threads > 0 { ret = append(ret, fmt.Sprintf("--threads=%d", s.Threads)) } - if s.Time > 0 { - ret = append(ret, fmt.Sprintf("--time=%d", s.Time)) - } + ret = append(ret, "--time=0") // Ensure events is used. + ret = append(ret, fmt.Sprintf("--events=%d", b.N)) return ret } // SysbenchCPU is for 'sysbench [flags] cpu run' and holds CPU specific arguments. type SysbenchCPU struct { - Base SysbenchBase - MaxPrime int // upper limit for primes generator [10000]. + SysbenchBase } // MakeCmd makes commands for SysbenchCPU. -func (s *SysbenchCPU) MakeCmd() []string { - cmd := []string{warmup, "sysbench"} - cmd = append(cmd, s.flags()...) - cmd = append(cmd, "cpu run") - return []string{"sh", "-c", strings.Join(cmd, " ")} -} - -// flags makes flags for SysbenchCPU cmds. -func (s *SysbenchCPU) flags() []string { - cmd := s.Base.baseFlags() - if s.MaxPrime > 0 { - return append(cmd, fmt.Sprintf("--cpu-max-prime=%d", s.MaxPrime)) - } +func (s *SysbenchCPU) MakeCmd(b *testing.B) []string { + cmd := []string{"sysbench"} + cmd = append(cmd, s.baseFlags(b)...) + cmd = append(cmd, "cpu", "run") return cmd } @@ -96,9 +84,9 @@ func (s *SysbenchCPU) parseEvents(data string) (float64, error) { // SysbenchMemory is for 'sysbench [FLAGS] memory run' and holds Memory specific arguments. type SysbenchMemory struct { - Base SysbenchBase - BlockSize string // size of test memory block [1K]. - TotalSize string // size of data to transfer [100G]. + SysbenchBase + BlockSize int // size of test memory block in megabytes [1]. + TotalSize int // size of data to transfer in gigabytes [100]. Scope string // memory access scope {global, local} [global]. HugeTLB bool // allocate memory from HugeTLB [off]. OperationType string // type of memory ops {read, write, none} [write]. @@ -106,21 +94,21 @@ type SysbenchMemory struct { } // MakeCmd makes commands for SysbenchMemory. -func (s *SysbenchMemory) MakeCmd() []string { - cmd := []string{warmup, "sysbench"} - cmd = append(cmd, s.flags()...) - cmd = append(cmd, "memory run") - return []string{"sh", "-c", strings.Join(cmd, " ")} +func (s *SysbenchMemory) MakeCmd(b *testing.B) []string { + cmd := []string{"sysbench"} + cmd = append(cmd, s.flags(b)...) + cmd = append(cmd, "memory", "run") + return cmd } // flags makes flags for SysbenchMemory cmds. -func (s *SysbenchMemory) flags() []string { - cmd := s.Base.baseFlags() - if s.BlockSize != "" { - cmd = append(cmd, fmt.Sprintf("--memory-block-size=%s", s.BlockSize)) +func (s *SysbenchMemory) flags(b *testing.B) []string { + cmd := s.baseFlags(b) + if s.BlockSize != 0 { + cmd = append(cmd, fmt.Sprintf("--memory-block-size=%dM", s.BlockSize)) } - if s.TotalSize != "" { - cmd = append(cmd, fmt.Sprintf("--memory-total-size=%s", s.TotalSize)) + if s.TotalSize != 0 { + cmd = append(cmd, fmt.Sprintf("--memory-total-size=%dG", s.TotalSize)) } if s.Scope != "" { cmd = append(cmd, fmt.Sprintf("--memory-scope=%s", s.Scope)) @@ -147,7 +135,7 @@ func (s *SysbenchMemory) Report(b *testing.B, output string) { ReportCustomMetric(b, result, "memory_operations" /*metric name*/, "ops_per_second" /*unit*/) } -var memoryOperationsRE = regexp.MustCompile(`Total\soperations:\s+\d*\s*\((\d*\.\d*)\sper\ssecond\)`) +var memoryOperationsRE = regexp.MustCompile(`Total\s+operations:\s+\d+\s+\((\s*\d+\.\d+\s*)\s+per\s+second\)`) // parseOperations parses memory operations per second form sysbench memory ouput. func (s *SysbenchMemory) parseOperations(data string) (float64, error) { @@ -160,24 +148,24 @@ func (s *SysbenchMemory) parseOperations(data string) (float64, error) { // SysbenchMutex is for 'sysbench [FLAGS] mutex run' and holds Mutex specific arguments. type SysbenchMutex struct { - Base SysbenchBase + SysbenchBase Num int // total size of mutex array [4096]. - Locks int // number of mutex locks per thread [50K]. - Loops int // number of loops to do outside mutex lock [10K]. + Locks int // number of mutex locks per thread [50000]. + Loops int // number of loops to do outside mutex lock [10000]. } // MakeCmd makes commands for SysbenchMutex. -func (s *SysbenchMutex) MakeCmd() []string { - cmd := []string{warmup, "sysbench"} - cmd = append(cmd, s.flags()...) - cmd = append(cmd, "mutex run") - return []string{"sh", "-c", strings.Join(cmd, " ")} +func (s *SysbenchMutex) MakeCmd(b *testing.B) []string { + cmd := []string{"sysbench"} + cmd = append(cmd, s.flags(b)...) + cmd = append(cmd, "mutex", "run") + return cmd } // flags makes flags for SysbenchMutex commands. -func (s *SysbenchMutex) flags() []string { +func (s *SysbenchMutex) flags(b *testing.B) []string { var cmd []string - cmd = append(cmd, s.Base.baseFlags()...) + cmd = append(cmd, s.baseFlags(b)...) if s.Num > 0 { cmd = append(cmd, fmt.Sprintf("--mutex-num=%d", s.Num)) } diff --git a/test/cmd/test_app/fds.go b/test/cmd/test_app/fds.go index a7658eefd..d4354f0d3 100644 --- a/test/cmd/test_app/fds.go +++ b/test/cmd/test_app/fds.go @@ -16,6 +16,7 @@ package main import ( "context" + "io" "io/ioutil" "log" "os" @@ -168,8 +169,8 @@ func (fdr *fdReceiver) Execute(ctx context.Context, f *flag.FlagSet, args ...int file := os.NewFile(uintptr(fd), "received file") defer file.Close() - if _, err := file.Seek(0, os.SEEK_SET); err != nil { - log.Fatalf("Seek(0, 0) failed: %v", err) + if _, err := file.Seek(0, io.SeekStart); err != nil { + log.Fatalf("Error from seek(0, 0): %v", err) } got, err := ioutil.ReadAll(file) diff --git a/test/e2e/integration_test.go b/test/e2e/integration_test.go index 03bdfa889..d07ed6ba5 100644 --- a/test/e2e/integration_test.go +++ b/test/e2e/integration_test.go @@ -260,12 +260,10 @@ func TestMemLimit(t *testing.T) { d := dockerutil.MakeContainer(ctx, t) defer d.CleanUp(ctx) - // N.B. Because the size of the memory file may grow in large chunks, - // there is a minimum threshold of 1GB for the MemTotal figure. - allocMemory := 1024 * 1024 // In kb. + allocMemoryKb := 50 * 1024 out, err := d.Run(ctx, dockerutil.RunOpts{ Image: "basic/alpine", - Memory: allocMemory * 1024, // In bytes. + Memory: allocMemoryKb * 1024, // In bytes. }, "sh", "-c", "cat /proc/meminfo | grep MemTotal: | awk '{print $2}'") if err != nil { t.Fatalf("docker run failed: %v", err) @@ -285,7 +283,7 @@ func TestMemLimit(t *testing.T) { if err != nil { t.Fatalf("failed to parse %q: %v", out, err) } - if want := uint64(allocMemory); got != want { + if want := uint64(allocMemoryKb); got != want { t.Errorf("MemTotal got: %d, want: %d", got, want) } } diff --git a/test/e2e/regression_test.go b/test/e2e/regression_test.go index 70bbe5121..84564cdaa 100644 --- a/test/e2e/regression_test.go +++ b/test/e2e/regression_test.go @@ -35,7 +35,7 @@ func TestBindOverlay(t *testing.T) { // Run the container. got, err := d.Run(ctx, dockerutil.RunOpts{ Image: "basic/ubuntu", - }, "bash", "-c", "nc -l -U /var/run/sock & p=$! && sleep 1 && echo foobar-asdf | nc -U /var/run/sock && wait $p") + }, "bash", "-c", "nc -q -1 -l -U /var/run/sock & p=$! && sleep 1 && echo foobar-asdf | nc -q 0 -U /var/run/sock && wait $p") if err != nil { t.Fatalf("docker run failed: %v", err) } diff --git a/test/fuse/linux/BUILD b/test/fuse/linux/BUILD index d1fb178e8..2f745bd47 100644 --- a/test/fuse/linux/BUILD +++ b/test/fuse/linux/BUILD @@ -235,6 +235,7 @@ cc_binary( srcs = ["mount_test.cc"], deps = [ gtest, + "//test/util:mount_util", "//test/util:temp_path", "//test/util:test_main", "//test/util:test_util", diff --git a/test/fuse/linux/mount_test.cc b/test/fuse/linux/mount_test.cc index a5c2fbb01..8a5478116 100644 --- a/test/fuse/linux/mount_test.cc +++ b/test/fuse/linux/mount_test.cc @@ -17,6 +17,7 @@ #include <sys/mount.h> #include "gtest/gtest.h" +#include "test/util/mount_util.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" @@ -25,6 +26,17 @@ namespace testing { namespace { +TEST(FuseMount, Success) { + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/fuse", O_WRONLY)); + std::string mopts = absl::StrCat("fd=", std::to_string(fd.get())); + + const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + + const auto mount = + ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "fuse", 0, mopts, 0)); +} + TEST(FuseMount, FDNotParsable) { int devfd; EXPECT_THAT(devfd = open("/dev/fuse", O_RDWR), SyscallSucceeds()); @@ -35,6 +47,36 @@ TEST(FuseMount, FDNotParsable) { SyscallFailsWithErrno(EINVAL)); } +TEST(FuseMount, NoDevice) { + const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + + EXPECT_THAT(mount("", dir.path().c_str(), "fuse", 0, ""), + SyscallFailsWithErrno(EINVAL)); +} + +TEST(FuseMount, ClosedFD) { + FileDescriptor f = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/fuse", O_WRONLY)); + int fd = f.release(); + close(fd); + std::string mopts = absl::StrCat("fd=", std::to_string(fd)); + + const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + + EXPECT_THAT(mount("", dir.path().c_str(), "fuse", 0, mopts.c_str()), + SyscallFailsWithErrno(EINVAL)); +} + +TEST(FuseMount, BadFD) { + const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); + std::string mopts = absl::StrCat("fd=", std::to_string(fd.get())); + + EXPECT_THAT(mount("", dir.path().c_str(), "fuse", 0, mopts.c_str()), + SyscallFailsWithErrno(EINVAL)); +} + } // namespace } // namespace testing diff --git a/test/iptables/filter_output.go b/test/iptables/filter_output.go index d3e5efd4f..f4af45e96 100644 --- a/test/iptables/filter_output.go +++ b/test/iptables/filter_output.go @@ -248,7 +248,7 @@ func (FilterOutputOwnerFail) Name() string { // ContainerAction implements TestCase.ContainerAction. func (FilterOutputOwnerFail) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error { if err := filterTable(ipv6, "-A", "OUTPUT", "-p", "udp", "-m", "owner", "-j", "ACCEPT"); err == nil { - return fmt.Errorf("Invalid argument") + return fmt.Errorf("invalid argument") } return nil diff --git a/test/packetdrill/BUILD b/test/packetdrill/BUILD index 49642f282..5d95516ee 100644 --- a/test/packetdrill/BUILD +++ b/test/packetdrill/BUILD @@ -38,6 +38,15 @@ packetdrill_test( scripts = ["tcp_defer_accept_timeout.pkt"], ) +test_suite( + name = "all_tests", + tags = [ + "manual", + "packetdrill", + ], + tests = existing_rules(), +) + bzl_library( name = "defs_bzl", srcs = ["defs.bzl"], diff --git a/test/packetimpact/runner/dut.go b/test/packetimpact/runner/dut.go index 8be2c6526..3e26c73cb 100644 --- a/test/packetimpact/runner/dut.go +++ b/test/packetimpact/runner/dut.go @@ -162,7 +162,7 @@ func setUpDUT(ctx context.Context, t *testing.T, id int, mkDevice func(*dockerut Image: "packetimpact", CapAdd: []string{"NET_ADMIN"}, } - if _, err := mountTempDirectory(t, &runOpts, "dut-output", testOutputDir); err != nil { + if _, err := MountTempDirectory(t, &runOpts, "dut-output", testOutputDir); err != nil { return dutInfo{}, err } @@ -228,7 +228,7 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co Image: "packetimpact", CapAdd: []string{"NET_ADMIN"}, } - if _, err := mountTempDirectory(t, &runOpts, "testbench-output", testOutputDir); err != nil { + if _, err := MountTempDirectory(t, &runOpts, "testbench-output", testOutputDir); err != nil { t.Fatal(err) } tbb := path.Base(testbenchBinary) @@ -565,11 +565,11 @@ func StartContainer(ctx context.Context, runOpts dockerutil.RunOpts, c *dockerut return nil } -// mountTempDirectory creates a temporary directory on host with the template +// MountTempDirectory creates a temporary directory on host with the template // and then mounts it into the container under the name provided. The temporary // directory name is returned. Content in that directory will be copied to // TEST_UNDECLARED_OUTPUTS_DIR in cleanup phase. -func mountTempDirectory(t *testing.T, runOpts *dockerutil.RunOpts, hostDirTemplate, containerDir string) (string, error) { +func MountTempDirectory(t *testing.T, runOpts *dockerutil.RunOpts, hostDirTemplate, containerDir string) (string, error) { t.Helper() tmpDir, err := ioutil.TempDir("", hostDirTemplate) if err != nil { diff --git a/test/packetimpact/testbench/connections.go b/test/packetimpact/testbench/connections.go index 50b9ccf68..576577310 100644 --- a/test/packetimpact/testbench/connections.go +++ b/test/packetimpact/testbench/connections.go @@ -306,11 +306,11 @@ func (s *tcpState) incoming(received Layer) Layer { if s.remoteSeqNum != nil { newIn.SeqNum = Uint32(uint32(*s.remoteSeqNum)) } - if s.localSeqNum != nil && (*tcpReceived.Flags&header.TCPFlagAck) != 0 { + if seq, flags := s.localSeqNum, tcpReceived.Flags; seq != nil && flags != nil && *flags&header.TCPFlagAck != 0 { // The caller didn't specify an AckNum so we'll expect the calculated one, // but only if the ACK flag is set because the AckNum is not valid in a // header if ACK is not set. - newIn.AckNum = Uint32(uint32(*s.localSeqNum)) + newIn.AckNum = Uint32(uint32(*seq)) } return &newIn } @@ -603,9 +603,9 @@ func (conn *Connection) ExpectFrame(t *testing.T, layers Layers, timeout time.Du } if gotLayers == nil { if errs == nil { - return nil, fmt.Errorf("got no frames matching %v during %s", layers, timeout) + return nil, fmt.Errorf("got no frames matching %s during %s", layers, timeout) } - return nil, fmt.Errorf("got frames %w want %v during %s", errs, layers, timeout) + return nil, fmt.Errorf("got frames:\n%w want %s during %s", errs, layers, timeout) } if conn.match(layers, gotLayers) { for i, s := range conn.layerStates { @@ -615,7 +615,12 @@ func (conn *Connection) ExpectFrame(t *testing.T, layers Layers, timeout time.Du } return gotLayers, nil } - errs = multierr.Combine(errs, &layersError{got: gotLayers, want: conn.incoming(gotLayers)}) + want := conn.incoming(layers) + if err := want.merge(layers); err != nil { + errs = multierr.Combine(errs, err) + } else { + errs = multierr.Combine(errs, &layersError{got: gotLayers, want: want}) + } } } diff --git a/test/packetimpact/testbench/layers.go b/test/packetimpact/testbench/layers.go index dcff4ab36..19e6b8d7d 100644 --- a/test/packetimpact/testbench/layers.go +++ b/test/packetimpact/testbench/layers.go @@ -505,13 +505,13 @@ func (l *IPv6) ToBytes() ([]byte, error) { } } if l.NextHeader != nil { - fields.NextHeader = *l.NextHeader + fields.TransportProtocol = tcpip.TransportProtocolNumber(*l.NextHeader) } else { nh, err := nextHeaderByLayer(l.next()) if err != nil { return nil, err } - fields.NextHeader = nh + fields.TransportProtocol = tcpip.TransportProtocolNumber(nh) } if l.HopLimit != nil { fields.HopLimit = *l.HopLimit diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD index 373ab8d2f..b1b3c578b 100644 --- a/test/packetimpact/tests/BUILD +++ b/test/packetimpact/tests/BUILD @@ -383,3 +383,12 @@ validate_all_tests() expect_netstack_failure = hasattr(t, "expect_netstack_failure"), num_duts = t.num_duts if hasattr(t, "num_duts") else 1, ) for t in ALL_TESTS] + +test_suite( + name = "all_tests", + tags = [ + "manual", + "packetimpact", + ], + tests = existing_rules(), +) diff --git a/test/packetimpact/tests/tcp_zero_receive_window_test.go b/test/packetimpact/tests/tcp_zero_receive_window_test.go index cf0431c57..d06690705 100644 --- a/test/packetimpact/tests/tcp_zero_receive_window_test.go +++ b/test/packetimpact/tests/tcp_zero_receive_window_test.go @@ -15,7 +15,6 @@ package tcp_zero_receive_window_test import ( - "context" "flag" "fmt" "testing" @@ -46,16 +45,30 @@ func TestZeroReceiveWindow(t *testing.T) { dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - samplePayload := &testbench.Payload{Bytes: make([]byte, payloadLen)} //testbench.GenerateRandomPayload(t, payloadLen)} - // Expect the DUT to eventually advertize zero receive window. + samplePayload := &testbench.Payload{Bytes: testbench.GenerateRandomPayload(t, payloadLen)} + // Expect the DUT to eventually advertise zero receive window. // The test would timeout otherwise. - for { + for readOnce := false; ; { conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second) if err != nil { t.Fatalf("expected packet was not received: %s", err) } - if *gotTCP.WindowSize == 0 { + // Read once to trigger the subsequent window update from the + // DUT to grow the right edge of the receive window from what + // was advertised in the SYN-ACK. This ensures that we test + // for the full default buffer size (1MB on gVisor at the time + // of writing this comment), thus testing for cases when the + // scaled receive window size ends up > 65535 (0xffff). + if !readOnce { + if got := dut.Recv(t, acceptFd, int32(payloadLen), 0); len(got) != payloadLen { + t.Fatalf("got dut.Recv(t, %d, %d, 0) = %d, want %d", acceptFd, payloadLen, len(got), payloadLen) + } + readOnce = true + } + windowSize := *gotTCP.WindowSize + t.Logf("got window size = %d", windowSize) + if windowSize == 0 { break } } @@ -92,10 +105,9 @@ func TestNonZeroReceiveWindow(t *testing.T) { if err != nil { t.Fatalf("expected packet was not received: %s", err) } - if ret, _, err := dut.RecvWithErrno(context.Background(), t, acceptFd, int32(payloadLen), 0); ret == -1 { - t.Fatalf("dut.RecvWithErrno(ctx, t, %d, %d, 0) = %d,_, %s", acceptFd, payloadLen, ret, err) + if got := dut.Recv(t, acceptFd, int32(payloadLen), 0); len(got) != payloadLen { + t.Fatalf("got dut.Recv(t, %d, %d, 0) = %d, want %d", acceptFd, payloadLen, len(got), payloadLen) } - if *gotTCP.WindowSize == 0 { t.Fatalf("expected non-zero receive window.") } diff --git a/test/root/BUILD b/test/root/BUILD index a9130b34f..8d9fff578 100644 --- a/test/root/BUILD +++ b/test/root/BUILD @@ -1,5 +1,4 @@ load("//tools:defs.bzl", "go_library", "go_test") -load("//tools/vm:defs.bzl", "vm_test") package(licenses = ["notice"]) @@ -24,12 +23,8 @@ go_test( ], library = ":root", tags = [ - # Requires docker and runsc to be configured before the test runs. - # Also, the test needs to be run as root. Note that below, the - # root_vm_test relies on the default runtime 'runsc' being installed by - # the default installer. - "manual", "local", + "manual", ], visibility = ["//:sandbox"], deps = [ @@ -46,10 +41,3 @@ go_test( "@org_golang_x_sys//unix:go_default_library", ], ) - -vm_test( - name = "root_vm_test", - size = "large", - shard_count = 1, - targets = [":root_test"], -) diff --git a/test/runtimes/runner/lib/lib.go b/test/runtimes/runner/lib/lib.go index 9272137ff..f2db5f9ea 100644 --- a/test/runtimes/runner/lib/lib.go +++ b/test/runtimes/runner/lib/lib.go @@ -196,3 +196,4 @@ func (f testDeps) WriteProfileTo(string, io.Writer, int) error { return nil } func (f testDeps) ImportPath() string { return "" } func (f testDeps) StartTestLog(io.Writer) {} func (f testDeps) StopTestLog() error { return nil } +func (f testDeps) SetPanicOnExit0(bool) {} diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index 135d58ae6..a5b9233f7 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -647,6 +647,7 @@ syscall_test( syscall_test( size = "medium", + add_hostinet = True, test = "//test/syscalls/linux:socket_ip_tcp_loopback_non_blocking_test", ) @@ -658,12 +659,14 @@ syscall_test( syscall_test( size = "medium", + add_hostinet = True, shard_count = most_shards, test = "//test/syscalls/linux:socket_ip_tcp_udp_generic_loopback_test", ) syscall_test( size = "medium", + add_hostinet = True, test = "//test/syscalls/linux:socket_ip_udp_loopback_non_blocking_test", ) @@ -680,6 +683,7 @@ syscall_test( syscall_test( size = "medium", + add_hostinet = True, shard_count = more_shards, # Takes too long under gotsan to run. tags = ["nogotsan"], @@ -728,6 +732,7 @@ syscall_test( ) syscall_test( + add_hostinet = True, test = "//test/syscalls/linux:socket_non_stream_blocking_local_test", ) @@ -903,6 +908,7 @@ syscall_test( ) syscall_test( + add_hostinet = True, test = "//test/syscalls/linux:udp_bind_test", ) @@ -967,6 +973,7 @@ syscall_test( ) syscall_test( + add_hostinet = True, test = "//test/syscalls/linux:proc_net_tcp_test", ) diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index a9e0b070a..4e0c8a574 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -21,6 +21,7 @@ exports_files( "socket_ip_unbound.cc", "socket_ipv4_udp_unbound_external_networking_test.cc", "socket_ipv4_udp_unbound_loopback.cc", + "socket_ipv6_udp_unbound_loopback.cc", "socket_ipv4_udp_unbound_loopback_nogotsan.cc", "tcp_socket.cc", "udp_bind.cc", @@ -944,6 +945,7 @@ cc_binary( "//test/util:eventfd_util", "//test/util:file_descriptor", "//test/util:fs_util", + "@com_google_absl//absl/container:node_hash_map", "@com_google_absl//absl/container:node_hash_set", "@com_google_absl//absl/strings", gtest, @@ -2451,6 +2453,27 @@ cc_library( ) cc_library( + name = "socket_ipv6_udp_unbound_test_cases", + testonly = 1, + srcs = [ + "socket_ipv6_udp_unbound.cc", + ], + hdrs = [ + "socket_ipv6_udp_unbound.h", + ], + deps = [ + ":ip_socket_test_util", + ":socket_test_util", + "@com_google_absl//absl/memory", + gtest, + "//test/util:posix_error", + "//test/util:save_util", + "//test/util:test_util", + ], + alwayslink = 1, +) + +cc_library( name = "socket_ipv4_udp_unbound_netlink_test_cases", testonly = 1, srcs = [ @@ -2790,6 +2813,22 @@ cc_binary( ) cc_binary( + name = "socket_ipv6_udp_unbound_loopback_test", + testonly = 1, + srcs = [ + "socket_ipv6_udp_unbound_loopback.cc", + ], + linkstatic = 1, + deps = [ + ":ip_socket_test_util", + ":socket_ipv6_udp_unbound_test_cases", + ":socket_test_util", + "//test/util:test_main", + "//test/util:test_util", + ], +) + +cc_binary( name = "socket_ipv4_udp_unbound_loopback_nogotsan_test", testonly = 1, srcs = [ @@ -3286,6 +3325,7 @@ cc_binary( ":socket_test_util", ":unix_domain_socket_test_util", gtest, + "//test/util:file_descriptor", "//test/util:test_main", "//test/util:test_util", ], diff --git a/test/syscalls/linux/getdents.cc b/test/syscalls/linux/getdents.cc index b040cdcf7..93c692dd6 100644 --- a/test/syscalls/linux/getdents.cc +++ b/test/syscalls/linux/getdents.cc @@ -32,6 +32,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/container/node_hash_map.h" #include "absl/container/node_hash_set.h" #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" @@ -381,7 +382,7 @@ TYPED_TEST(GetdentsTest, PartialBuffer) { // getdents iterates correctly despite mutation of /proc/self/fd. TYPED_TEST(GetdentsTest, ProcSelfFd) { constexpr size_t kNfds = 10; - std::unordered_map<int, FileDescriptor> fds; + absl::node_hash_map<int, FileDescriptor> fds; fds.reserve(kNfds); for (size_t i = 0; i < kNfds; i++) { FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()); diff --git a/test/syscalls/linux/mount.cc b/test/syscalls/linux/mount.cc index d65b7d031..15b645fb7 100644 --- a/test/syscalls/linux/mount.cc +++ b/test/syscalls/linux/mount.cc @@ -345,42 +345,6 @@ TEST(MountTest, RenameRemoveMountPoint) { ASSERT_THAT(rmdir(dir.path().c_str()), SyscallFailsWithErrno(EBUSY)); } -TEST(MountTest, MountFuseFilesystemNoDevice) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - SKIP_IF(IsRunningOnGvisor() && !IsFUSEEnabled()); - - auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - // Before kernel version 4.16-rc6, FUSE mount is protected by - // capable(CAP_SYS_ADMIN). After this version, it uses - // ns_capable(CAP_SYS_ADMIN) to protect. Before the 4.16 kernel, it was not - // allowed to mount fuse file systems without the global CAP_SYS_ADMIN. - int res = mount("", dir.path().c_str(), "fuse", 0, ""); - SKIP_IF(!IsRunningOnGvisor() && res == -1 && errno == EPERM); - - EXPECT_THAT(mount("", dir.path().c_str(), "fuse", 0, ""), - SyscallFailsWithErrno(EINVAL)); -} - -TEST(MountTest, MountFuseFilesystem) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); - SKIP_IF(IsRunningOnGvisor() && !IsFUSEEnabled()); - - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/fuse", O_WRONLY)); - std::string mopts = "fd=" + std::to_string(fd.get()); - - auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - - // See comments in MountFuseFilesystemNoDevice for the reason why we skip - // EPERM when running on Linux. - int res = mount("", dir.path().c_str(), "fuse", 0, ""); - SKIP_IF(!IsRunningOnGvisor() && res == -1 && errno == EPERM); - - auto const mount = - ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "fuse", 0, mopts, 0)); -} - } // namespace } // namespace testing diff --git a/test/syscalls/linux/open.cc b/test/syscalls/linux/open.cc index 77f390f3c..fcd162ca2 100644 --- a/test/syscalls/linux/open.cc +++ b/test/syscalls/linux/open.cc @@ -505,6 +505,18 @@ TEST_F(OpenTest, OpenNonDirectoryWithTrailingSlash) { EXPECT_THAT(open(bad_path.c_str(), O_RDONLY), SyscallFailsWithErrno(ENOTDIR)); } +TEST_F(OpenTest, OpenWithStrangeFlags) { + // VFS1 incorrectly allows read/write operations on such file descriptors. + SKIP_IF(IsRunningWithVFS1()); + + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_WRONLY | O_RDWR)); + EXPECT_THAT(write(fd.get(), "x", 1), SyscallFailsWithErrno(EBADF)); + char c; + EXPECT_THAT(read(fd.get(), &c, 1), SyscallFailsWithErrno(EBADF)); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/pipe.cc b/test/syscalls/linux/pipe.cc index 06d9dbf65..01ccbdcd2 100644 --- a/test/syscalls/linux/pipe.cc +++ b/test/syscalls/linux/pipe.cc @@ -71,13 +71,13 @@ class PipeTest : public ::testing::TestWithParam<PipeCreator> { // Returns true iff the pipe represents a named pipe. bool IsNamedPipe() const { return named_pipe_; } - int Size() const { + size_t Size() const { int s1 = fcntl(rfd_.get(), F_GETPIPE_SZ); int s2 = fcntl(wfd_.get(), F_GETPIPE_SZ); EXPECT_GT(s1, 0); EXPECT_GT(s2, 0); EXPECT_EQ(s1, s2); - return s1; + return static_cast<size_t>(s1); } static void TearDownTestSuite() { @@ -568,7 +568,7 @@ TEST_P(PipeTest, Streaming) { DisableSave ds; // Size() requires 2 syscalls, call it once and remember the value. - const int pipe_size = Size(); + const size_t pipe_size = Size(); const size_t streamed_bytes = 4 * pipe_size; absl::Notification notify; @@ -576,7 +576,7 @@ TEST_P(PipeTest, Streaming) { std::vector<char> buf(1024); // Don't start until it's full. notify.WaitForNotification(); - ssize_t total = 0; + size_t total = 0; while (total < streamed_bytes) { ASSERT_THAT(read(rfd_.get(), buf.data(), buf.size()), SyscallSucceedsWithValue(buf.size())); @@ -593,7 +593,7 @@ TEST_P(PipeTest, Streaming) { // page) for the check for notify.Notify() below to be correct. std::vector<char> buf(1024); RandomizeBuffer(buf.data(), buf.size()); - ssize_t total = 0; + size_t total = 0; while (total < streamed_bytes) { ASSERT_THAT(write(wfd_.get(), buf.data(), buf.size()), SyscallSucceedsWithValue(buf.size())); diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc index 575be014c..e508ce27f 100644 --- a/test/syscalls/linux/proc.cc +++ b/test/syscalls/linux/proc.cc @@ -1802,6 +1802,33 @@ TEST(ProcPidCmdline, SubprocessForkSameCmdline) { } } +TEST(ProcPidCmdline, SubprocessSeekCmdline) { + FileDescriptor fd; + ASSERT_NO_ERRNO(WithSubprocess( + [&](int pid) -> PosixError { + // Running. Open /proc/pid/cmdline. + ASSIGN_OR_RETURN_ERRNO( + fd, Open(absl::StrCat("/proc/", pid, "/cmdline"), O_RDONLY)); + return NoError(); + }, + [&](int pid) -> PosixError { + // Zombie, but seek should still succeed. + int ret = lseek(fd.get(), 0x801, 0); + if (ret < 0) { + return PosixError(errno); + } + return NoError(); + }, + [&](int pid) -> PosixError { + // Exited. + int ret = lseek(fd.get(), 0x801, 0); + if (ret < 0) { + return PosixError(errno); + } + return NoError(); + })); +} + // Test whether /proc/PID/ symlinks can be read for a running process. TEST(ProcPidSymlink, SubprocessRunning) { char buf[1]; diff --git a/test/syscalls/linux/proc_net.cc b/test/syscalls/linux/proc_net.cc index 23677e296..1cc700fe7 100644 --- a/test/syscalls/linux/proc_net.cc +++ b/test/syscalls/linux/proc_net.cc @@ -420,14 +420,14 @@ TEST(ProcNetSnmp, CheckNetStat) { int name_count = 0; int value_count = 0; std::vector<absl::string_view> lines = absl::StrSplit(contents, '\n'); - for (int i = 0; i + 1 < lines.size(); i += 2) { + for (long unsigned int i = 0; i + 1 < lines.size(); i += 2) { std::vector<absl::string_view> names = absl::StrSplit(lines[i], absl::ByAnyChar("\t ")); std::vector<absl::string_view> values = absl::StrSplit(lines[i + 1], absl::ByAnyChar("\t ")); EXPECT_EQ(names.size(), values.size()) << " mismatch in lines '" << lines[i] << "' and '" << lines[i + 1] << "'"; - for (int j = 0; j < names.size() && j < values.size(); ++j) { + for (long unsigned int j = 0; j < names.size() && j < values.size(); ++j) { if (names[j] == "TCPOrigDataSent" || names[j] == "TCPSynRetrans" || names[j] == "TCPDSACKRecv" || names[j] == "TCPDSACKOfoRecv") { ++name_count; @@ -457,14 +457,14 @@ TEST(ProcNetSnmp, CheckSnmp) { int name_count = 0; int value_count = 0; std::vector<absl::string_view> lines = absl::StrSplit(contents, '\n'); - for (int i = 0; i + 1 < lines.size(); i += 2) { + for (long unsigned int i = 0; i + 1 < lines.size(); i += 2) { std::vector<absl::string_view> names = absl::StrSplit(lines[i], absl::ByAnyChar("\t ")); std::vector<absl::string_view> values = absl::StrSplit(lines[i + 1], absl::ByAnyChar("\t ")); EXPECT_EQ(names.size(), values.size()) << " mismatch in lines '" << lines[i] << "' and '" << lines[i + 1] << "'"; - for (int j = 0; j < names.size() && j < values.size(); ++j) { + for (long unsigned int j = 0; j < names.size() && j < values.size(); ++j) { if (names[j] == "RetransSegs") { ++name_count; int64_t val; diff --git a/test/syscalls/linux/proc_net_unix.cc b/test/syscalls/linux/proc_net_unix.cc index a63067586..662c6feb2 100644 --- a/test/syscalls/linux/proc_net_unix.cc +++ b/test/syscalls/linux/proc_net_unix.cc @@ -181,7 +181,7 @@ PosixErrorOr<std::vector<UnixEntry>> ProcNetUnixEntries() { // Returns true on match, and sets 'match' to point to the matching entry. bool FindBy(std::vector<UnixEntry> entries, UnixEntry* match, std::function<bool(const UnixEntry&)> predicate) { - for (int i = 0; i < entries.size(); ++i) { + for (long unsigned int i = 0; i < entries.size(); ++i) { if (predicate(entries[i])) { *match = entries[i]; return true; diff --git a/test/syscalls/linux/proc_pid_uid_gid_map.cc b/test/syscalls/linux/proc_pid_uid_gid_map.cc index 748f7be58..af052a63c 100644 --- a/test/syscalls/linux/proc_pid_uid_gid_map.cc +++ b/test/syscalls/linux/proc_pid_uid_gid_map.cc @@ -203,7 +203,8 @@ TEST_P(ProcSelfUidGidMapTest, IdentityMapOwnID) { EXPECT_THAT( InNewUserNamespaceWithMapFD([&](int fd) { DenySelfSetgroups(); - TEST_PCHECK(write(fd, line.c_str(), line.size()) == line.size()); + TEST_PCHECK(static_cast<long unsigned int>( + write(fd, line.c_str(), line.size())) == line.size()); }), IsPosixErrorOkAndHolds(0)); } @@ -220,7 +221,8 @@ TEST_P(ProcSelfUidGidMapTest, TrailingNewlineAndNULIgnored) { DenySelfSetgroups(); // The write should return the full size of the write, even though // characters after the NUL were ignored. - TEST_PCHECK(write(fd, line.c_str(), line.size()) == line.size()); + TEST_PCHECK(static_cast<long unsigned int>( + write(fd, line.c_str(), line.size())) == line.size()); }), IsPosixErrorOkAndHolds(0)); } @@ -233,7 +235,8 @@ TEST_P(ProcSelfUidGidMapTest, NonIdentityMapOwnID) { EXPECT_THAT( InNewUserNamespaceWithMapFD([&](int fd) { DenySelfSetgroups(); - TEST_PCHECK(write(fd, line.c_str(), line.size()) == line.size()); + TEST_PCHECK(static_cast<long unsigned int>( + write(fd, line.c_str(), line.size())) == line.size()); }), IsPosixErrorOkAndHolds(0)); } diff --git a/test/syscalls/linux/raw_socket.cc b/test/syscalls/linux/raw_socket.cc index 54709371c..955bcee4b 100644 --- a/test/syscalls/linux/raw_socket.cc +++ b/test/syscalls/linux/raw_socket.cc @@ -852,6 +852,51 @@ TEST(RawSocketTest, IPv6ProtoRaw) { SyscallFailsWithErrno(EINVAL)); } +TEST(RawSocketTest, IPv6SendMsg) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + int sock; + ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_TCP), + SyscallSucceeds()); + + char kBuf[] = "hello"; + struct iovec iov = {}; + iov.iov_base = static_cast<void*>(const_cast<char*>(kBuf)); + iov.iov_len = static_cast<size_t>(sizeof(kBuf)); + + struct sockaddr_storage addr = {}; + struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(&addr); + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + struct msghdr msg = {}; + msg.msg_name = static_cast<void*>(&addr); + msg.msg_namelen = sizeof(sockaddr_in); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + ASSERT_THAT(sendmsg(sock, &msg, 0), SyscallFailsWithErrno(EINVAL)); +} + +TEST_P(RawSocketTest, ConnectOnIPv6Socket) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + int sock; + ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_TCP), + SyscallSucceeds()); + + struct sockaddr_storage addr = {}; + struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(&addr); + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + ASSERT_THAT(connect(sock, reinterpret_cast<struct sockaddr*>(&addr), + sizeof(sockaddr_in6)), + SyscallFailsWithErrno(EAFNOSUPPORT)); +} + INSTANTIATE_TEST_SUITE_P( AllInetTests, RawSocketTest, ::testing::Combine(::testing::Values(IPPROTO_TCP, IPPROTO_UDP), diff --git a/test/syscalls/linux/semaphore.cc b/test/syscalls/linux/semaphore.cc index 1c1bf6a57..0530fce44 100644 --- a/test/syscalls/linux/semaphore.cc +++ b/test/syscalls/linux/semaphore.cc @@ -20,6 +20,7 @@ #include <atomic> #include <cerrno> #include <ctime> +#include <set> #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -35,6 +36,17 @@ namespace gvisor { namespace testing { namespace { +constexpr int kSemMap = 1024000000; +constexpr int kSemMni = 32000; +constexpr int kSemMns = 1024000000; +constexpr int kSemMnu = 1024000000; +constexpr int kSemMsl = 32000; +constexpr int kSemOpm = 500; +constexpr int kSemUme = 500; +constexpr int kSemUsz = 20; +constexpr int kSemVmx = 32767; +constexpr int kSemAem = 32767; + class AutoSem { public: explicit AutoSem(int id) : id_(id) {} @@ -586,7 +598,7 @@ TEST(SemaphoreTest, SemopGetzcnt) { buf.sem_num = 0; buf.sem_op = 0; constexpr size_t kLoops = 10; - for (auto i = 0; i < kLoops; i++) { + for (size_t i = 0; i < kLoops; i++) { auto child_pid = fork(); if (child_pid == 0) { TEST_PCHECK(RetryEINTR(semop)(sem.get(), &buf, 1) == 0); @@ -693,7 +705,7 @@ TEST(SemaphoreTest, SemopGetncnt) { buf.sem_num = 0; buf.sem_op = -1; constexpr size_t kLoops = 10; - for (auto i = 0; i < kLoops; i++) { + for (size_t i = 0; i < kLoops; i++) { auto child_pid = fork(); if (child_pid == 0) { TEST_PCHECK(RetryEINTR(semop)(sem.get(), &buf, 1) == 0); @@ -774,18 +786,148 @@ TEST(SemaphoreTest, SemopGetncntOnSignal_NoRandomSave) { } TEST(SemaphoreTest, IpcInfo) { + constexpr int kLoops = 5; + std::set<int> sem_ids; struct seminfo info; + // Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions. + ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false)); + for (int i = 0; i < kLoops; i++) { + AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT)); + ASSERT_THAT(sem.get(), SyscallSucceeds()); + sem_ids.insert(sem.release()); + } + ASSERT_EQ(sem_ids.size(), kLoops); + + int max_used_index = 0; + EXPECT_THAT(max_used_index = semctl(0, 0, IPC_INFO, &info), + SyscallSucceeds()); + + std::set<int> sem_ids_before_max_index; + for (int i = 0; i <= max_used_index; i++) { + struct semid_ds ds = {}; + int sem_id = semctl(i, 0, SEM_STAT, &ds); + // Only if index i is used within the registry. + if (sem_ids.find(sem_id) != sem_ids.end()) { + struct semid_ds ipc_stat_ds; + ASSERT_THAT(semctl(sem_id, 0, IPC_STAT, &ipc_stat_ds), SyscallSucceeds()); + EXPECT_EQ(ds.sem_perm.__key, ipc_stat_ds.sem_perm.__key); + EXPECT_EQ(ds.sem_perm.uid, ipc_stat_ds.sem_perm.uid); + EXPECT_EQ(ds.sem_perm.gid, ipc_stat_ds.sem_perm.gid); + EXPECT_EQ(ds.sem_perm.cuid, ipc_stat_ds.sem_perm.cuid); + EXPECT_EQ(ds.sem_perm.cgid, ipc_stat_ds.sem_perm.cgid); + EXPECT_EQ(ds.sem_perm.mode, ipc_stat_ds.sem_perm.mode); + EXPECT_EQ(ds.sem_otime, ipc_stat_ds.sem_otime); + EXPECT_EQ(ds.sem_ctime, ipc_stat_ds.sem_ctime); + EXPECT_EQ(ds.sem_nsems, ipc_stat_ds.sem_nsems); + + // Remove the semaphore set's read permission. + struct semid_ds ipc_set_ds; + ipc_set_ds.sem_perm.uid = getuid(); + ipc_set_ds.sem_perm.gid = getgid(); + // Keep the semaphore set's write permission so that it could be removed. + ipc_set_ds.sem_perm.mode = 0200; + ASSERT_THAT(semctl(sem_id, 0, IPC_SET, &ipc_set_ds), SyscallSucceeds()); + ASSERT_THAT(semctl(i, 0, SEM_STAT, &ds), SyscallFailsWithErrno(EACCES)); + + sem_ids_before_max_index.insert(sem_id); + } + } + EXPECT_EQ(sem_ids_before_max_index.size(), kLoops); + for (const int sem_id : sem_ids) { + ASSERT_THAT(semctl(sem_id, 0, IPC_RMID), SyscallSucceeds()); + } + ASSERT_THAT(semctl(0, 0, IPC_INFO, &info), SyscallSucceeds()); + EXPECT_EQ(info.semmap, kSemMap); + EXPECT_EQ(info.semmni, kSemMni); + EXPECT_EQ(info.semmns, kSemMns); + EXPECT_EQ(info.semmnu, kSemMnu); + EXPECT_EQ(info.semmsl, kSemMsl); + EXPECT_EQ(info.semopm, kSemOpm); + EXPECT_EQ(info.semume, kSemUme); + EXPECT_EQ(info.semusz, kSemUsz); + EXPECT_EQ(info.semvmx, kSemVmx); + EXPECT_EQ(info.semaem, kSemAem); +} + +TEST(SemaphoreTest, SemInfo) { + constexpr int kLoops = 5; + constexpr int kSemSetSize = 3; + std::set<int> sem_ids; + struct seminfo info; + // Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions. + ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false)); + for (int i = 0; i < kLoops; i++) { + AutoSem sem(semget(IPC_PRIVATE, kSemSetSize, 0600 | IPC_CREAT)); + ASSERT_THAT(sem.get(), SyscallSucceeds()); + sem_ids.insert(sem.release()); + } + ASSERT_EQ(sem_ids.size(), kLoops); + int max_used_index = 0; + EXPECT_THAT(max_used_index = semctl(0, 0, SEM_INFO, &info), + SyscallSucceeds()); + EXPECT_EQ(info.semmap, kSemMap); + EXPECT_EQ(info.semmni, kSemMni); + EXPECT_EQ(info.semmns, kSemMns); + EXPECT_EQ(info.semmnu, kSemMnu); + EXPECT_EQ(info.semmsl, kSemMsl); + EXPECT_EQ(info.semopm, kSemOpm); + EXPECT_EQ(info.semume, kSemUme); + // There could be semaphores existing in the system during the test, which + // prevents the test from getting a exact number, but the test could expect at + // least the number of sempahroes it creates in the begining of the test. + EXPECT_GE(info.semusz, sem_ids.size()); + EXPECT_EQ(info.semvmx, kSemVmx); + EXPECT_GE(info.semaem, sem_ids.size() * kSemSetSize); + + std::set<int> sem_ids_before_max_index; + for (int i = 0; i <= max_used_index; i++) { + struct semid_ds ds = {}; + int sem_id = semctl(i, 0, SEM_STAT, &ds); + // Only if index i is used within the registry. + if (sem_ids.find(sem_id) != sem_ids.end()) { + struct semid_ds ipc_stat_ds; + ASSERT_THAT(semctl(sem_id, 0, IPC_STAT, &ipc_stat_ds), SyscallSucceeds()); + EXPECT_EQ(ds.sem_perm.__key, ipc_stat_ds.sem_perm.__key); + EXPECT_EQ(ds.sem_perm.uid, ipc_stat_ds.sem_perm.uid); + EXPECT_EQ(ds.sem_perm.gid, ipc_stat_ds.sem_perm.gid); + EXPECT_EQ(ds.sem_perm.cuid, ipc_stat_ds.sem_perm.cuid); + EXPECT_EQ(ds.sem_perm.cgid, ipc_stat_ds.sem_perm.cgid); + EXPECT_EQ(ds.sem_perm.mode, ipc_stat_ds.sem_perm.mode); + EXPECT_EQ(ds.sem_otime, ipc_stat_ds.sem_otime); + EXPECT_EQ(ds.sem_ctime, ipc_stat_ds.sem_ctime); + EXPECT_EQ(ds.sem_nsems, ipc_stat_ds.sem_nsems); + + // Remove the semaphore set's read permission. + struct semid_ds ipc_set_ds; + ipc_set_ds.sem_perm.uid = getuid(); + ipc_set_ds.sem_perm.gid = getgid(); + // Keep the semaphore set's write permission so that it could be removed. + ipc_set_ds.sem_perm.mode = 0200; + ASSERT_THAT(semctl(sem_id, 0, IPC_SET, &ipc_set_ds), SyscallSucceeds()); + ASSERT_THAT(semctl(i, 0, SEM_STAT, &ds), SyscallFailsWithErrno(EACCES)); + + sem_ids_before_max_index.insert(sem_id); + } + } + EXPECT_EQ(sem_ids_before_max_index.size(), kLoops); + for (const int sem_id : sem_ids) { + ASSERT_THAT(semctl(sem_id, 0, IPC_RMID), SyscallSucceeds()); + } - EXPECT_EQ(info.semmap, 1024000000); - EXPECT_EQ(info.semmni, 32000); - EXPECT_EQ(info.semmns, 1024000000); - EXPECT_EQ(info.semmnu, 1024000000); - EXPECT_EQ(info.semmsl, 32000); - EXPECT_EQ(info.semopm, 500); - EXPECT_EQ(info.semume, 500); - EXPECT_EQ(info.semvmx, 32767); - EXPECT_EQ(info.semaem, 32767); + ASSERT_THAT(semctl(0, 0, SEM_INFO, &info), SyscallSucceeds()); + EXPECT_EQ(info.semmap, kSemMap); + EXPECT_EQ(info.semmni, kSemMni); + EXPECT_EQ(info.semmns, kSemMns); + EXPECT_EQ(info.semmnu, kSemMnu); + EXPECT_EQ(info.semmsl, kSemMsl); + EXPECT_EQ(info.semopm, kSemOpm); + EXPECT_EQ(info.semume, kSemUme); + // Apart from semapahores that are not created by the test, we can't determine + // the exact number of semaphore sets and semaphores, as a result, semusz and + // semaem range from 0 to a random number. Since the numbers are always + // non-negative, the test will not check the reslts of semusz and semaem. + EXPECT_EQ(info.semvmx, kSemVmx); } } // namespace diff --git a/test/syscalls/linux/socket.cc b/test/syscalls/linux/socket.cc index e680d3dd7..32f583581 100644 --- a/test/syscalls/linux/socket.cc +++ b/test/syscalls/linux/socket.cc @@ -46,7 +46,7 @@ TEST(SocketTest, ProtocolUnix) { {AF_UNIX, SOCK_SEQPACKET, PF_UNIX}, {AF_UNIX, SOCK_DGRAM, PF_UNIX}, }; - for (int i = 0; i < ABSL_ARRAYSIZE(tests); i++) { + for (long unsigned int i = 0; i < ABSL_ARRAYSIZE(tests); i++) { ASSERT_NO_ERRNO_AND_VALUE( Socket(tests[i].domain, tests[i].type, tests[i].protocol)); } @@ -59,7 +59,7 @@ TEST(SocketTest, ProtocolInet) { {AF_INET, SOCK_DGRAM, IPPROTO_UDP}, {AF_INET, SOCK_STREAM, IPPROTO_TCP}, }; - for (int i = 0; i < ABSL_ARRAYSIZE(tests); i++) { + for (long unsigned int i = 0; i < ABSL_ARRAYSIZE(tests); i++) { ASSERT_NO_ERRNO_AND_VALUE( Socket(tests[i].domain, tests[i].type, tests[i].protocol)); } @@ -87,7 +87,7 @@ TEST(SocketTest, UnixSocketStat) { ASSERT_THAT(stat(addr.sun_path, &statbuf), SyscallSucceeds()); // Mode should be S_IFSOCK. - EXPECT_EQ(statbuf.st_mode, S_IFSOCK | sock_perm & ~mask); + EXPECT_EQ(statbuf.st_mode, S_IFSOCK | (sock_perm & ~mask)); // Timestamps should be equal and non-zero. // TODO(b/158882152): Sockets currently don't implement timestamps. diff --git a/test/syscalls/linux/socket_bind_to_device_distribution.cc b/test/syscalls/linux/socket_bind_to_device_distribution.cc index 5ed57625c..06419772f 100644 --- a/test/syscalls/linux/socket_bind_to_device_distribution.cc +++ b/test/syscalls/linux/socket_bind_to_device_distribution.cc @@ -168,7 +168,7 @@ TEST_P(BindToDeviceDistributionTest, Tcp) { std::vector<std::unique_ptr<ScopedThread>> listen_threads( listener_fds.size()); - for (int i = 0; i < listener_fds.size(); i++) { + for (long unsigned int i = 0; i < listener_fds.size(); i++) { listen_threads[i] = absl::make_unique<ScopedThread>( [&listener_fds, &accept_counts, &connects_received, i, kConnectAttempts]() { @@ -235,7 +235,7 @@ TEST_P(BindToDeviceDistributionTest, Tcp) { listen_thread->Join(); } // Check that connections are distributed correctly among listening sockets. - for (int i = 0; i < accept_counts.size(); i++) { + for (long unsigned int i = 0; i < accept_counts.size(); i++) { EXPECT_THAT( accept_counts[i], EquivalentWithin(static_cast<int>(kConnectAttempts * @@ -308,7 +308,7 @@ TEST_P(BindToDeviceDistributionTest, Udp) { std::vector<std::unique_ptr<ScopedThread>> receiver_threads( listener_fds.size()); - for (int i = 0; i < listener_fds.size(); i++) { + for (long unsigned int i = 0; i < listener_fds.size(); i++) { receiver_threads[i] = absl::make_unique<ScopedThread>( [&listener_fds, &packets_per_socket, &packets_received, i]() { do { @@ -366,7 +366,7 @@ TEST_P(BindToDeviceDistributionTest, Udp) { receiver_thread->Join(); } // Check that packets are distributed correctly among listening sockets. - for (int i = 0; i < packets_per_socket.size(); i++) { + for (long unsigned int i = 0; i < packets_per_socket.size(); i++) { EXPECT_THAT( packets_per_socket[i], EquivalentWithin(static_cast<int>(kConnectAttempts * diff --git a/test/syscalls/linux/socket_generic.cc b/test/syscalls/linux/socket_generic.cc index 70cc86b16..a28ee2233 100644 --- a/test/syscalls/linux/socket_generic.cc +++ b/test/syscalls/linux/socket_generic.cc @@ -853,5 +853,21 @@ TEST_P(AllSocketPairTest, SetAndGetBooleanSocketOptions) { } } +TEST_P(AllSocketPairTest, GetSocketOutOfBandInlineOption) { + // We do not support disabling this option. It is always enabled. + SKIP_IF(!IsRunningOnGvisor()); + + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + int enable = -1; + socklen_t enableLen = sizeof(enable); + + int want = 1; + ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_OOBINLINE, &enable, + &enableLen), + SyscallSucceeds()); + ASSERT_EQ(enableLen, sizeof(enable)); + EXPECT_EQ(enable, want); +} + } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_udp_generic.cc b/test/syscalls/linux/socket_ip_udp_generic.cc index f69f8f99f..1694e188a 100644 --- a/test/syscalls/linux/socket_ip_udp_generic.cc +++ b/test/syscalls/linux/socket_ip_udp_generic.cc @@ -15,6 +15,9 @@ #include "test/syscalls/linux/socket_ip_udp_generic.h" #include <errno.h> +#ifdef __linux__ +#include <linux/in6.h> +#endif // __linux__ #include <netinet/in.h> #include <netinet/tcp.h> #include <poll.h> @@ -356,6 +359,58 @@ TEST_P(UDPSocketPairTest, SetAndGetIPPKTINFO) { EXPECT_EQ(get_len, sizeof(get)); } +// Test getsockopt for a socket which is not set with IP_RECVORIGDSTADDR option. +TEST_P(UDPSocketPairTest, ReceiveOrigDstAddrDefault) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + int get = -1; + socklen_t get_len = sizeof(get); + int level = SOL_IP; + int type = IP_RECVORIGDSTADDR; + if (sockets->first_addr()->sa_family == AF_INET6) { + level = SOL_IPV6; + type = IPV6_RECVORIGDSTADDR; + } + ASSERT_THAT(getsockopt(sockets->first_fd(), level, type, &get, &get_len), + SyscallSucceedsWithValue(0)); + EXPECT_EQ(get_len, sizeof(get)); + EXPECT_EQ(get, kSockOptOff); +} + +// Test setsockopt and getsockopt for a socket with IP_RECVORIGDSTADDR option. +TEST_P(UDPSocketPairTest, SetAndGetReceiveOrigDstAddr) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + int level = SOL_IP; + int type = IP_RECVORIGDSTADDR; + if (sockets->first_addr()->sa_family == AF_INET6) { + level = SOL_IPV6; + type = IPV6_RECVORIGDSTADDR; + } + + // Check getsockopt before IP_PKTINFO is set. + int get = -1; + socklen_t get_len = sizeof(get); + + ASSERT_THAT(setsockopt(sockets->first_fd(), level, type, &kSockOptOn, + sizeof(kSockOptOn)), + SyscallSucceedsWithValue(0)); + + ASSERT_THAT(getsockopt(sockets->first_fd(), level, type, &get, &get_len), + SyscallSucceedsWithValue(0)); + EXPECT_EQ(get, kSockOptOn); + EXPECT_EQ(get_len, sizeof(get)); + + ASSERT_THAT(setsockopt(sockets->first_fd(), level, type, &kSockOptOff, + sizeof(kSockOptOff)), + SyscallSucceedsWithValue(0)); + + ASSERT_THAT(getsockopt(sockets->first_fd(), level, type, &get, &get_len), + SyscallSucceedsWithValue(0)); + EXPECT_EQ(get, kSockOptOff); + EXPECT_EQ(get_len, sizeof(get)); +} + // Holds TOS or TClass information for IPv4 or IPv6 respectively. struct RecvTosOption { int level; @@ -438,7 +493,7 @@ TEST_P(UDPSocketPairTest, TClassRecvMismatch) { // This should only test AF_INET6 sockets for the mismatch behavior. SKIP_IF(GetParam().domain != AF_INET6); // IPV6_RECVTCLASS is only valid for SOCK_DGRAM and SOCK_RAW. - SKIP_IF(GetParam().type != SOCK_DGRAM | GetParam().type != SOCK_RAW); + SKIP_IF((GetParam().type != SOCK_DGRAM) | (GetParam().type != SOCK_RAW)); auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound.cc b/test/syscalls/linux/socket_ipv4_udp_unbound.cc index b3f54e7f6..e557572a7 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound.cc +++ b/test/syscalls/linux/socket_ipv4_udp_unbound.cc @@ -2222,6 +2222,90 @@ TEST_P(IPv4UDPUnboundSocketTest, SetAndReceiveIPPKTINFO) { EXPECT_EQ(received_pktinfo.ipi_addr.s_addr, htonl(INADDR_LOOPBACK)); } +// Test that socket will receive IP_RECVORIGDSTADDR control message. +TEST_P(IPv4UDPUnboundSocketTest, SetAndReceiveIPReceiveOrigDstAddr) { + auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto receiver_addr = V4Loopback(); + int level = SOL_IP; + int type = IP_RECVORIGDSTADDR; + + ASSERT_THAT( + bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), + receiver_addr.addr_len), + SyscallSucceeds()); + + // Retrieve the port bound by the receiver. + socklen_t receiver_addr_len = receiver_addr.addr_len; + ASSERT_THAT(getsockname(receiver->get(), + reinterpret_cast<sockaddr*>(&receiver_addr.addr), + &receiver_addr_len), + SyscallSucceeds()); + EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); + + ASSERT_THAT( + connect(sender->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), + receiver_addr.addr_len), + SyscallSucceeds()); + + // Get address and port bound by the sender. + sockaddr_storage sender_addr_storage; + socklen_t sender_addr_len = sizeof(sender_addr_storage); + ASSERT_THAT(getsockname(sender->get(), + reinterpret_cast<sockaddr*>(&sender_addr_storage), + &sender_addr_len), + SyscallSucceeds()); + ASSERT_EQ(sender_addr_len, sizeof(struct sockaddr_in)); + + // Enable IP_RECVORIGDSTADDR on socket so that we get the original destination + // address of the datagram as auxiliary information in the control message. + ASSERT_THAT( + setsockopt(receiver->get(), level, type, &kSockOptOn, sizeof(kSockOptOn)), + SyscallSucceeds()); + + // Prepare message to send. + constexpr size_t kDataLength = 1024; + msghdr sent_msg = {}; + iovec sent_iov = {}; + char sent_data[kDataLength]; + sent_iov.iov_base = sent_data; + sent_iov.iov_len = kDataLength; + sent_msg.msg_iov = &sent_iov; + sent_msg.msg_iovlen = 1; + sent_msg.msg_flags = 0; + + ASSERT_THAT(RetryEINTR(sendmsg)(sender->get(), &sent_msg, 0), + SyscallSucceedsWithValue(kDataLength)); + + msghdr received_msg = {}; + iovec received_iov = {}; + char received_data[kDataLength]; + char received_cmsg_buf[CMSG_SPACE(sizeof(sockaddr_in))] = {}; + size_t cmsg_data_len = sizeof(sockaddr_in); + received_iov.iov_base = received_data; + received_iov.iov_len = kDataLength; + received_msg.msg_iov = &received_iov; + received_msg.msg_iovlen = 1; + received_msg.msg_controllen = CMSG_LEN(cmsg_data_len); + received_msg.msg_control = received_cmsg_buf; + + ASSERT_THAT(RecvMsgTimeout(receiver->get(), &received_msg, 1 /*timeout*/), + IsPosixErrorOkAndHolds(kDataLength)); + + cmsghdr* cmsg = CMSG_FIRSTHDR(&received_msg); + ASSERT_NE(cmsg, nullptr); + EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(cmsg_data_len)); + EXPECT_EQ(cmsg->cmsg_level, level); + EXPECT_EQ(cmsg->cmsg_type, type); + + // Check the data + sockaddr_in received_addr = {}; + memcpy(&received_addr, CMSG_DATA(cmsg), sizeof(received_addr)); + auto orig_receiver_addr = reinterpret_cast<sockaddr_in*>(&receiver_addr.addr); + EXPECT_EQ(received_addr.sin_addr.s_addr, orig_receiver_addr->sin_addr.s_addr); + EXPECT_EQ(received_addr.sin_port, orig_receiver_addr->sin_port); +} + // Check that setting SO_RCVBUF below min is clamped to the minimum // receive buffer size. TEST_P(IPv4UDPUnboundSocketTest, SetSocketRecvBufBelowMin) { diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc index 875016812..9a9ddc297 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc +++ b/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc @@ -177,7 +177,7 @@ TEST_P(IPv4UDPUnboundSocketNetlinkTest, ReuseAddrSubnetDirectedBroadcast) { // Broadcasts from each socket should be received by every socket (including // the sending socket). - for (int w = 0; w < socks.size(); w++) { + for (long unsigned int w = 0; w < socks.size(); w++) { auto& w_sock = socks[w]; ASSERT_THAT( RetryEINTR(sendto)(w_sock->get(), send_buf, kSendBufSize, 0, @@ -187,7 +187,7 @@ TEST_P(IPv4UDPUnboundSocketNetlinkTest, ReuseAddrSubnetDirectedBroadcast) { << "write socks[" << w << "]"; // Check that we received the packet on all sockets. - for (int r = 0; r < socks.size(); r++) { + for (long unsigned int r = 0; r < socks.size(); r++) { auto& r_sock = socks[r]; struct pollfd poll_fd = {r_sock->get(), POLLIN, 0}; diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound.cc b/test/syscalls/linux/socket_ipv6_udp_unbound.cc new file mode 100644 index 000000000..08526468e --- /dev/null +++ b/test/syscalls/linux/socket_ipv6_udp_unbound.cc @@ -0,0 +1,131 @@ +// Copyright 2020 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "test/syscalls/linux/socket_ipv6_udp_unbound.h" + +#include <arpa/inet.h> +#include <netinet/in.h> +#ifdef __linux__ +#include <linux/in6.h> +#endif // __linux__ +#include <net/if.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/un.h> + +#include <cstdio> +#include <cstring> + +#include "gtest/gtest.h" +#include "absl/memory/memory.h" +#include "test/syscalls/linux/ip_socket_test_util.h" +#include "test/syscalls/linux/socket_test_util.h" +#include "test/util/posix_error.h" +#include "test/util/save_util.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +// Test that socket will receive IP_RECVORIGDSTADDR control message. +TEST_P(IPv6UDPUnboundSocketTest, SetAndReceiveIPReceiveOrigDstAddr) { + auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto receiver_addr = V6Loopback(); + int level = SOL_IPV6; + int type = IPV6_RECVORIGDSTADDR; + + ASSERT_THAT( + bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), + receiver_addr.addr_len), + SyscallSucceeds()); + + // Retrieve the port bound by the receiver. + socklen_t receiver_addr_len = receiver_addr.addr_len; + ASSERT_THAT(getsockname(receiver->get(), + reinterpret_cast<sockaddr*>(&receiver_addr.addr), + &receiver_addr_len), + SyscallSucceeds()); + EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); + + ASSERT_THAT( + connect(sender->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), + receiver_addr.addr_len), + SyscallSucceeds()); + + // Get address and port bound by the sender. + sockaddr_storage sender_addr_storage; + socklen_t sender_addr_len = sizeof(sender_addr_storage); + ASSERT_THAT(getsockname(sender->get(), + reinterpret_cast<sockaddr*>(&sender_addr_storage), + &sender_addr_len), + SyscallSucceeds()); + ASSERT_EQ(sender_addr_len, sizeof(struct sockaddr_in6)); + + // Enable IP_RECVORIGDSTADDR on socket so that we get the original destination + // address of the datagram as auxiliary information in the control message. + ASSERT_THAT( + setsockopt(receiver->get(), level, type, &kSockOptOn, sizeof(kSockOptOn)), + SyscallSucceeds()); + + // Prepare message to send. + constexpr size_t kDataLength = 1024; + msghdr sent_msg = {}; + iovec sent_iov = {}; + char sent_data[kDataLength]; + sent_iov.iov_base = sent_data; + sent_iov.iov_len = kDataLength; + sent_msg.msg_iov = &sent_iov; + sent_msg.msg_iovlen = 1; + sent_msg.msg_flags = 0; + + ASSERT_THAT(RetryEINTR(sendmsg)(sender->get(), &sent_msg, 0), + SyscallSucceedsWithValue(kDataLength)); + + msghdr received_msg = {}; + iovec received_iov = {}; + char received_data[kDataLength]; + char received_cmsg_buf[CMSG_SPACE(sizeof(sockaddr_in6))] = {}; + size_t cmsg_data_len = sizeof(sockaddr_in6); + received_iov.iov_base = received_data; + received_iov.iov_len = kDataLength; + received_msg.msg_iov = &received_iov; + received_msg.msg_iovlen = 1; + received_msg.msg_controllen = CMSG_LEN(cmsg_data_len); + received_msg.msg_control = received_cmsg_buf; + + ASSERT_THAT(RecvMsgTimeout(receiver->get(), &received_msg, 1 /*timeout*/), + IsPosixErrorOkAndHolds(kDataLength)); + + cmsghdr* cmsg = CMSG_FIRSTHDR(&received_msg); + ASSERT_NE(cmsg, nullptr); + EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(cmsg_data_len)); + EXPECT_EQ(cmsg->cmsg_level, level); + EXPECT_EQ(cmsg->cmsg_type, type); + + // Check that the received address in the control message matches the expected + // receiver's address. + sockaddr_in6 received_addr = {}; + memcpy(&received_addr, CMSG_DATA(cmsg), sizeof(received_addr)); + auto orig_receiver_addr = + reinterpret_cast<sockaddr_in6*>(&receiver_addr.addr); + EXPECT_EQ(memcmp(&received_addr.sin6_addr, &orig_receiver_addr->sin6_addr, + sizeof(in6_addr)), + 0); + EXPECT_EQ(received_addr.sin6_port, orig_receiver_addr->sin6_port); +} + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound.h b/test/syscalls/linux/socket_ipv6_udp_unbound.h new file mode 100644 index 000000000..71e160f73 --- /dev/null +++ b/test/syscalls/linux/socket_ipv6_udp_unbound.h @@ -0,0 +1,29 @@ +// 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. + +#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_H_ +#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_H_ + +#include "test/syscalls/linux/socket_test_util.h" + +namespace gvisor { +namespace testing { + +// Test fixture for tests that apply to IPv6 UDP sockets. +using IPv6UDPUnboundSocketTest = SimpleSocketTest; + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_H_ diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound_loopback.cc b/test/syscalls/linux/socket_ipv6_udp_unbound_loopback.cc new file mode 100644 index 000000000..058336ecc --- /dev/null +++ b/test/syscalls/linux/socket_ipv6_udp_unbound_loopback.cc @@ -0,0 +1,32 @@ +// Copyright 2020 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <vector> + +#include "test/syscalls/linux/ip_socket_test_util.h" +#include "test/syscalls/linux/socket_ipv6_udp_unbound.h" +#include "test/syscalls/linux/socket_test_util.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +INSTANTIATE_TEST_SUITE_P( + IPv6UDPSockets, IPv6UDPUnboundSocketTest, + ::testing::ValuesIn(ApplyVec<SocketKind>(IPv6UDPUnboundSocket, + AllBitwiseCombinations(List<int>{ + 0, SOCK_NONBLOCK})))); + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/socket_unix_unbound_filesystem.cc b/test/syscalls/linux/socket_unix_unbound_filesystem.cc index cab912152..a035fb095 100644 --- a/test/syscalls/linux/socket_unix_unbound_filesystem.cc +++ b/test/syscalls/linux/socket_unix_unbound_filesystem.cc @@ -12,12 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include <fcntl.h> #include <stdio.h> #include <sys/un.h> #include "gtest/gtest.h" #include "test/syscalls/linux/socket_test_util.h" #include "test/syscalls/linux/unix_domain_socket_test_util.h" +#include "test/util/file_descriptor.h" #include "test/util/test_util.h" namespace gvisor { @@ -70,6 +72,20 @@ TEST_P(UnboundFilesystemUnixSocketPairTest, GetSockNameLength) { strlen(want_addr.sun_path) + 1 + sizeof(want_addr.sun_family)); } +TEST_P(UnboundFilesystemUnixSocketPairTest, OpenSocketWithTruncate) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(), + sockets->first_addr_size()), + SyscallSucceeds()); + + const struct sockaddr_un *addr = + reinterpret_cast<const struct sockaddr_un *>(sockets->first_addr()); + EXPECT_THAT(chmod(addr->sun_path, 0777), SyscallSucceeds()); + EXPECT_THAT(open(addr->sun_path, O_RDONLY | O_TRUNC), + SyscallFailsWithErrno(ENXIO)); +} + INSTANTIATE_TEST_SUITE_P( AllUnixDomainSockets, UnboundFilesystemUnixSocketPairTest, ::testing::ValuesIn(ApplyVec<SocketPairKind>( diff --git a/test/syscalls/linux/tuntap.cc b/test/syscalls/linux/tuntap.cc index 97d554e72..5ac337d1a 100644 --- a/test/syscalls/linux/tuntap.cc +++ b/test/syscalls/linux/tuntap.cc @@ -324,8 +324,9 @@ TEST_F(TuntapTest, PingKernel) { }; while (1) { inpkt r = {}; - int n = read(fd.get(), &r, sizeof(r)); - EXPECT_THAT(n, SyscallSucceeds()); + int nread = read(fd.get(), &r, sizeof(r)); + EXPECT_THAT(nread, SyscallSucceeds()); + long unsigned int n = static_cast<long unsigned int>(nread); if (n < sizeof(pihdr)) { std::cerr << "Ignored packet, protocol: " << r.pi.pi_protocol @@ -383,8 +384,9 @@ TEST_F(TuntapTest, SendUdpTriggersArpResolution) { }; while (1) { inpkt r = {}; - int n = read(fd.get(), &r, sizeof(r)); - EXPECT_THAT(n, SyscallSucceeds()); + int nread = read(fd.get(), &r, sizeof(r)); + EXPECT_THAT(nread, SyscallSucceeds()); + long unsigned int n = static_cast<long unsigned int>(nread); if (n < sizeof(pihdr)) { std::cerr << "Ignored packet, protocol: " << r.pi.pi_protocol diff --git a/test/syscalls/linux/udp_socket.cc b/test/syscalls/linux/udp_socket.cc index 90ef8bf21..21727a2e7 100644 --- a/test/syscalls/linux/udp_socket.cc +++ b/test/syscalls/linux/udp_socket.cc @@ -14,6 +14,8 @@ #include <arpa/inet.h> #include <fcntl.h> +#include <netinet/icmp6.h> +#include <netinet/ip_icmp.h> #include <ctime> @@ -779,6 +781,94 @@ TEST_P(UdpSocketTest, ConnectAndSendNoReceiver) { SyscallFailsWithErrno(ECONNREFUSED)); } +#ifdef __linux__ +TEST_P(UdpSocketTest, RecvErrorConnRefused) { + // We will simulate an ICMP error and verify that we do receive that error via + // recvmsg(MSG_ERRQUEUE). + ASSERT_NO_ERRNO(BindLoopback()); + // Close the socket to release the port so that we get an ICMP error. + ASSERT_THAT(close(bind_.release()), SyscallSucceeds()); + + // Set IP_RECVERR socket option to enable error queueing. + int v = kSockOptOn; + socklen_t optlen = sizeof(v); + int opt_level = SOL_IP; + int opt_type = IP_RECVERR; + if (GetParam() != AddressFamily::kIpv4) { + opt_level = SOL_IPV6; + opt_type = IPV6_RECVERR; + } + ASSERT_THAT(setsockopt(sock_.get(), opt_level, opt_type, &v, optlen), + SyscallSucceeds()); + + // Connect to loopback:bind_addr_ which should *hopefully* not be bound by an + // UDP socket. There is no easy way to ensure that the UDP port is not bound + // by another conncurrently running test. *This is potentially flaky*. + const int kBufLen = 300; + ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); + char buf[kBufLen]; + RandomizeBuffer(buf, sizeof(buf)); + // Send from sock_ to an unbound port. This should cause ECONNREFUSED. + EXPECT_THAT(send(sock_.get(), buf, sizeof(buf), 0), + SyscallSucceedsWithValue(sizeof(buf))); + + // Dequeue error using recvmsg(MSG_ERRQUEUE). + char got[kBufLen]; + struct iovec iov; + iov.iov_base = reinterpret_cast<void*>(got); + 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)); + 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_controllen = control_buf_len; + msg.msg_name = reinterpret_cast<void*>(&remote); + msg.msg_namelen = addrlen_; + ASSERT_THAT(recvmsg(sock_.get(), &msg, MSG_ERRQUEUE), + SyscallSucceedsWithValue(kBufLen)); + + // Check the contents of msg. + EXPECT_EQ(memcmp(got, buf, sizeof(buf)), 0); // iovec check + EXPECT_NE(msg.msg_flags & MSG_ERRQUEUE, 0); + EXPECT_EQ(memcmp(&remote, bind_addr_, addrlen_), 0); + + // Check the contents of the control message. + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + ASSERT_NE(cmsg, nullptr); + EXPECT_EQ(CMSG_NXTHDR(&msg, cmsg), nullptr); + EXPECT_EQ(cmsg->cmsg_level, opt_level); + EXPECT_EQ(cmsg->cmsg_type, opt_type); + + // Check the contents of socket error. + struct sock_extended_err* sock_err = + (struct sock_extended_err*)CMSG_DATA(cmsg); + EXPECT_EQ(sock_err->ee_errno, ECONNREFUSED); + if (GetParam() == AddressFamily::kIpv4) { + EXPECT_EQ(sock_err->ee_origin, SO_EE_ORIGIN_ICMP); + EXPECT_EQ(sock_err->ee_type, ICMP_DEST_UNREACH); + EXPECT_EQ(sock_err->ee_code, ICMP_PORT_UNREACH); + } else { + EXPECT_EQ(sock_err->ee_origin, SO_EE_ORIGIN_ICMP6); + EXPECT_EQ(sock_err->ee_type, ICMP6_DST_UNREACH); + EXPECT_EQ(sock_err->ee_code, ICMP6_DST_UNREACH_NOPORT); + } + + // Now verify that the socket error was cleared by recvmsg(MSG_ERRQUEUE). + int err; + optlen = sizeof(err); + ASSERT_THAT(getsockopt(sock_.get(), SOL_SOCKET, SO_ERROR, &err, &optlen), + SyscallSucceeds()); + ASSERT_EQ(err, 0); + ASSERT_EQ(optlen, sizeof(err)); +} +#endif // __linux__ + TEST_P(UdpSocketTest, ZerolengthWriteAllowed) { // TODO(gvisor.dev/issue/1202): Hostinet does not support zero length writes. SKIP_IF(IsRunningWithHostinet()); |